クエリ
クエリは、データベースからデータを取得または変更する方法を記述するオブジェクトです。クエリを記述するための複数の Method と、それらを実行する終端 Method を持ちます。データベースアダプターは、データベース固有の機能をサポートするために、クエリ API をさまざまな方法で拡張できます。
Database.query(T)
または Session.query(T)
を使用してクエリを作成できます。パフォーマンスが向上するため、Session の使用を推奨します。
@entity.name('user') class User { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date; birthdate?: Date; visits: number = 0; constructor(public username: string) { } } const database = new Database(...); //[ { username: 'User1' }, { username: 'User2' }, { username: 'User2' } ] const users = await database.query(User).select('username').find();
フィルター
フィルターを適用して結果セットを絞り込むことができます。
//シンプルなフィルター const users = await database.query(User).filter({name: 'User1'}).find(); //複数フィルター。すべて AND const users = await database.query(User).filter({name: 'User1', id: 2}).find(); //範囲フィルター: $gt, $lt, $gte, $lte(より大きい、より小さい、...) //WHERE created < NOW() と同等 const users = await database.query(User).filter({created: {$lt: new Date}}).find(); //WHERE id > 500 と同等 const users = await database.query(User).filter({id: {$gt: 500}}).find(); //WHERE id >= 500 と同等 const users = await database.query(User).filter({id: {$gte: 500}}).find(); //集合フィルター: $in, $nin(IN, NOT IN) //WHERE id IN (1, 2, 3) と同等 const users = await database.query(User).filter({id: {$in: [1, 2, 3]}}).find(); //正規表現フィルター const users = await database.query(User).filter({username: {$regex: /User[0-9]+/}}).find(); //グルーピング: $and, $nor, $or //WHERE (username = 'User1') OR (username = 'User2') と同等 const users = await database.query(User).filter({ $or: [{username: 'User1'}, {username: 'User2'}] }).find(); //ネストされたグルーピング //WHERE username = 'User1' OR (username = 'User2' and id > 0) と同等 const users = await database.query(User).filter({ $or: [{username: 'User1'}, {username: 'User2', id: {$gt: 0}}] }).find(); //ネストされたグルーピング //WHERE username = 'User1' AND (created < NOW() OR id > 0) と同等 const users = await database.query(User).filter({ $and: [{username: 'User1'}, {$or: [{created: {$lt: new Date}, id: {$gt: 0}}]}] }).find();
等価
大なり/小なり
正規表現
グルーピング AND/OR
IN
Select
データベースから受け取るフィールドを絞り込むには、select('field1')
を使用します。
const user = await database.query(User).select('username').findOne(); const user = await database.query(User).select('id', 'username').findOne();
select
を使ってフィールドを絞り込んだ時点で、結果はエンティティのインスタンスではなく、オブジェクトリテラルのみになることに注意してください。
const user = await database.query(User).select('username').findOne(); user instanceof User; //false
並び順
orderBy(field, order)
でエントリの並び順を変更できます。
orderBy
は複数回実行でき、順序を段階的に詳細化できます。
const users = await session.query(User).orderBy('created', 'desc').find(); const users = await session.query(User).orderBy('created', 'asc').find();
ページネーション
itemsPerPage()
と page()
を使って、結果をページングできます。ページは 1 から開始します。
const users = await session.query(User).itemsPerPage(50).page(1).find();
代替の limit
と skip
を使えば手動でページングできます。
const users = await session.query(User).limit(5).skip(10).find();
[#database-join]
結合
デフォルトでは、エンティティからの参照はクエリに含まれず、ロードもされません。参照をロードせずにクエリに結合を含めるには、join()
(LEFT JOIN)または innerJoin()
を使用します。参照をロードしつつクエリに結合を含めるには、joinWith()
または innerJoinWith()
を使用します。
以下の例はすべて、次のモデルスキーマを前提としています。
@entity.name('group') class Group { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date; constructor(public username: string) { } } @entity.name('user') class User { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date; group?: Group & Reference; constructor(public username: string) { } }
//グループが割り当てられたユーザーのみを選択(INNER JOIN) const users = await session.query(User).innerJoin('group').find(); for (const user of users) { user.group; //参照がロードされていないため error }
//グループが割り当てられたユーザーのみを選択(INNER JOIN)し、リレーションをロード const users = await session.query(User).innerJoinWith('group').find(); for (const user of users) { user.group.name; //動作します }
結合クエリを修正するには、同じ Method を use
プレフィックス付きで使用します: useJoin
, useInnerJoin
, useJoinWith
, useInnerJoinWith
。結合クエリの修正を終了し、親クエリに戻るには end()
を使用します。
//名前が 'admins' のグループが割り当てられているユーザーのみを選択(INNER JOIN) const users = await session.query(User) .useInnerJoinWith('group') .filter({name: 'admins'}) .end() // 親クエリに戻る .find(); for (const user of users) { user.group.name; //常に admin }
集計
集計 Method により、レコードのカウントやフィールドの集計が可能です。
次の例は、このモデルスキーマを前提としています。
@entity.name('file') class File { id: number & PrimaryKey & AutoIncrement = 0; created: Date = new Date; downloads: number = 0; category: string = 'none'; constructor(public path: string & Index) { } }
groupBy
は指定したフィールドで結果をグルーピングします。
await database.persist( cast<File>({path: 'file1', category: 'images'}), cast<File>({path: 'file2', category: 'images'}), cast<File>({path: 'file3', category: 'pdfs'}) ); //[ { category: 'images' }, { category: 'pdfs' } ] await session.query(File).groupBy('category').find();
利用できる集計 Method は複数あります: withSum
, withAverage
, withCount
, withMin
, withMax
, withGroupConcat
。いずれも第1引数にフィールド名を取り、別名を変更するための任意の第2引数を取ります。
// まず、いくつかのレコードを更新します: await database.query(File).filter({path: 'images/file1'}).patchOne({$inc: {downloads: 15}}); await database.query(File).filter({path: 'images/file2'}).patchOne({$inc: {downloads: 5}}); //[{ category: 'images', downloads: 20 },{ category: 'pdfs', downloads: 0 }] await session.query(File).groupBy('category').withSum('downloads').find(); //[{ category: 'images', downloads: 10 },{ category: 'pdfs', downloads: 0 }] await session.query(File).groupBy('category').withAverage('downloads').find(); //[ { category: 'images', amount: 2 }, { category: 'pdfs', amount: 1 } ] await session.query(File).groupBy('category').withCount('id', 'amount').find();
Returning
patch
および delete
による変更時に、returning
で追加のフィールドを要求できます。
注意: すべてのデータベースアダプターがフィールドをアトミックに返すわけではありません。データ整合性を確保するためにトランザクションを使用してください。
await database.query(User).patchMany({visits: 0}); //{ modified: 1, returning: { visits: [ 5 ] }, primaryKeys: [ 1 ] } const result = await database.query(User) .filter({username: 'User1'}) .returning('username', 'visits') .patchOne({$inc: {visits: 5}});
Find
指定したフィルターに一致するエントリの配列を返します。
const users: User[] = await database.query(User).filter({username: 'Peter'}).find();
FindOne
指定したフィルターに一致するアイテムを返します。
アイテムが見つからない場合、ItemNotFound
Error がスローされます。
const users: User = await database.query(User).filter({username: 'Peter'}).findOne();
FindOneOrUndefined
指定したフィルターに一致するエントリを返します。 エントリが見つからない場合は、undefined が返されます。
const query = database.query(User).filter({username: 'Peter'}); const users: User|undefined = await query.findOneOrUndefined();
FindField
指定したフィルターに一致するフィールドのリストを返します。
const usernames: string[] = await database.query(User).findField('username');
FindOneField
指定したフィルターに一致するフィールドのリストを返します。
エントリが見つからない場合、ItemNotFound
Error がスローされます。
const username: string = await database.query(User).filter({id: 3}).findOneField('username');
Patch
Patch は、クエリで記述されたレコードをパッチする変更クエリです。patchOne
と patchMany
の Method がクエリを終了し、パッチを実行します。
patchMany
は、指定したフィルターに一致するすべてのレコードをデータベースで変更します。フィルターが設定されていない場合は、テーブル全体が変更されます。1件のみ変更するには patchOne
を使用します。
await database.query(User).filter({username: 'Peter'}).patch({username: 'Peter2'}); await database.query(User).filter({username: 'User1'}).patchOne({birthdate: new Date}); await database.query(User).filter({username: 'User1'}).patchOne({$inc: {visits: 1}}); await database.query(User).patchMany({visits: 0});
Delete
deleteMany
は、指定したフィルターに一致するすべてのエントリをデータベースから削除します。
フィルターが設定されていない場合は、テーブル全体が削除されます。1件のみ削除するには deleteOne
を使用します。
const result = await database.query(User) .filter({visits: 0}) .deleteMany(); const result = await database.query(User).filter({id: 4}).deleteOne();
Has
データベースに少なくとも1件のエントリが存在するかどうかを返します。
const userExists: boolean = await database.query(User).filter({username: 'Peter'}).has();
Count
エントリ数を返します。
const userCount: number = await database.query(User).count();
Lift
クエリのリフトとは、クエリに新しい機能を追加することを意味します。これは通常、プラグインや複雑なアーキテクチャで、大きなクエリ Class を複数の使いやすく再利用可能な Class に分割するために使用されます。
import { FilterQuery, Query } from '@deepkit/orm'; class UserQuery<T extends {birthdate?: Date}> extends Query<T> { hasBirthday() { const start = new Date(); start.setHours(0,0,0,0); const end = new Date(); end.setHours(23,59,59,999); return this.filter({$and: [{birthdate: {$gte: start}}, {birthdate: {$lte: end}}]} as FilterQuery<T>); } } await session.query(User).lift(UserQuery).hasBirthday().find();