集計、グループ化、および要約
Prisma Clientを使用すると、レコードのカウント、数値フィールドの集計、および個別のフィールド値の選択ができます。
集計
Prisma Clientを使用すると、モデルの**数値**フィールド(Int
やFloat
など)でaggregate
を実行できます。次のクエリは、すべてのユーザーの平均年齢を返します。
const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
})
console.log('Average age:' + aggregations._avg.age)
集計は、フィルタリングおよび順序付けと組み合わせることができます。たとえば、次のクエリはユーザーの平均年齢を返します。
age
昇順で並べ替えemail
にprisma.io
が含まれる場合- 上位10ユーザーに制限
const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
where: {
email: {
contains: 'prisma.io',
},
},
orderBy: {
age: 'asc',
},
take: 10,
})
console.log('Average age:' + aggregations._avg.age)
集計値はnullableです
2.21.0以降では、**nullableフィールド**の集計はnumber
またはnull
を返すことができます。これはcount
を除き、レコードが見つからない場合は常に0を返します。
スキーマでage
がnullableである次のクエリを考えてください。
const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
_count: {
age: true,
},
})
{
_avg: {
age: null
},
_count: {
age: 9
}
}
クエリは、次のいずれかのシナリオで{ _avg: { age: null } }
を返します。
- ユーザーがいない
- すべてのユーザーの
age
フィールドの値がnull
である
これにより、真の集計値(ゼロの場合もある)とデータなしを区別できます。
グループ化
Prisma ClientのgroupBy()
を使用すると、country
やcountry
とcity
など、1つ以上のフィールド値で**レコードをグループ化**し、特定の都市に住む人々の平均年齢を見つけるなど、各グループで**集計を実行**できます。groupBy()
は2.20.0以降でGAになりました。
次のビデオでは、groupBy()
を使用して、大陸別のCOVID-19症例の合計を要約しています。
次の例では、すべてのユーザーをcountry
フィールドでグループ化し、国ごとのプロファイルビューの合計数を返します。
const groupUsers = await prisma.user.groupBy({
by: ['country'],
_sum: {
profileViews: true,
},
})
by
オプションに単一の要素がある場合は、次の省略形構文を使用してクエリを表現できます。
const groupUsers = await prisma.user.groupBy({
by: 'country',
})
groupBy()
とフィルタリング
groupBy()
は、where
とhaving
の2つのレベルのフィルタリングをサポートしています。
where
でレコードをフィルタリング
where
を使用して、**グループ化する前に**すべてのレコードをフィルタリングします。次の例では、ユーザーを国別にグループ化し、プロファイルビューを合計しますが、メールアドレスにprisma.io
が含まれるユーザーのみを含めます。
const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: {
email: {
contains: 'prisma.io',
},
},
_sum: {
profileViews: true,
},
})
having
でグループをフィルタリング
having
を使用して、個々のレコードではなく、フィールドの合計または平均などの集計値で**グループ全体**をフィルタリングします。たとえば、平均profileViews
が100を超えるグループのみを返します。
const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: {
email: {
contains: 'prisma.io',
},
},
_sum: {
profileViews: true,
},
having: {
profileViews: {
_avg: {
gt: 100,
},
},
},
})
having
のユースケース
having
の主なユースケースは、集計でフィルタリングすることです。where
を使用して、グループ化前にデータセットのサイズを可能な限り縮小することをお勧めします。なぜなら、そうすることで、✔データベースが返す必要のあるレコード数が減少し、✔インデックスが使用されるからです。
たとえば、次のクエリは、スウェーデンまたはガーナ出身ではないすべてのユーザーをグループ化します。
const fd = await prisma.user.groupBy({
by: ['country'],
where: {
country: {
notIn: ['Sweden', 'Ghana'],
},
},
_sum: {
profileViews: true,
},
having: {
profileViews: {
_min: {
gte: 10,
},
},
},
})
次のクエリは技術的には同じ結果を達成しますが、グループ化後にガーナ出身のユーザーを除外します。これは何のメリットもなく、推奨されるプラクティスではありません。
const groupUsers = await prisma.user.groupBy({
by: ['country'],
where: {
country: {
not: 'Sweden',
},
},
_sum: {
profileViews: true,
},
having: {
country: {
not: 'Ghana',
},
profileViews: {
_min: {
gte: 10,
},
},
},
})
**注意**:
having
内では、集計値またはby
で利用可能なフィールドでのみフィルタリングできます。
groupBy()
と順序付け
groupBy()
とorderBy
を組み合わせる場合、次の制約が適用されます。
by
に存在するフィールドをorderBy
できます。- 集計を
orderBy
できます(2.21.0以降でプレビュー)。 skip
またはtake
をgroupBy()
で使用する場合は、クエリにorderBy
も含める必要があります。
集計グループで並べ替え
**集計グループで並べ替え**できます。Prisma ORMは、バージョン2.21.0のリレーショナルデータベースおよび3.4.0のMongoDBで、集計グループでのorderBy
の使用をサポートしました。
次の例では、各city
グループをそのグループ内のユーザー数でソートします(最大のグループが最初)。
const groupBy = await prisma.user.groupBy({
by: ['city'],
_count: {
city: true,
},
orderBy: {
_count: {
city: 'desc',
},
},
})
フィールドで並べ替え
次のクエリは、グループを国で並べ替え、最初の2つのグループをスキップし、3番目と4番目のグループを返します。
const groupBy = await prisma.user.groupBy({
by: ['country'],
_sum: {
profileViews: true,
},
orderBy: {
country: 'desc',
},
skip: 2,
take: 2,
})
groupBy()
FAQ
groupBy()
でselect
を使用できますか?
groupBy()
でselect
を使用することはできません。ただし、by
に含まれるすべてのフィールドは自動的に返されます。
groupBy()
でwhere
とhaving
を使用することの違いは何ですか?
where
はグループ化する前にすべてのレコードをフィルタリングし、having
はグループ全体をフィルタリングし、そのグループ内の特定のフィールドの平均または合計などの集計フィールド値でのフィルタリングをサポートします。
groupBy()
とdistinct
の違いは何ですか?
distinct
とgroupBy()
の両方で、1つ以上のユニークなフィールド値でレコードをグループ化します。groupBy()
を使用すると、各グループ内のデータを集計できます。たとえば、デンマークからの投稿の平均閲覧数を返すことができます。一方、distinctはそうではありません。
カウント
レコードをカウント
count()
を使用して、レコード数またはnull
以外のフィールド値をカウントします。次の例のクエリは、すべてのユーザーをカウントします。
const userCount = await prisma.user.count()
リレーションをカウント
リレーションのカウント(たとえば、ユーザーの投稿数)を返すには、次のようにネストされたselect
で_count
パラメーターを使用します。
const usersWithCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
_count
パラメーター
- トップレベルの
include
またはselect
内で使用できます - レコードを返す任意のクエリ(
delete
、update
、findFirst
を含む)で使用できます - 複数のリレーションカウントを返すことができます
- リレーションカウントをフィルタリングできます(バージョン4.3.0以降)。
include
でリレーションカウントを返す
次のクエリは、各ユーザーの投稿数を結果に含めます。
const usersWithCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
select
でリレーションカウントを返す
次のクエリは、select
を使用して各ユーザーの投稿数と他のフィールドなしを返します。
const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: { posts: true },
},
},
})
複数のリレーションカウントを返す
次のクエリは、各ユーザーのposts
とrecipes
のカウントを返し、他のフィールドはありません。
const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: {
posts: true,
recipes: true,
},
},
},
})
リレーションカウントをフィルタリング
where
を使用して、_count
出力型によって返されるフィールドをフィルタリングします。スカラーフィールド、リレーションフィールド、および複合型のフィールドでこれを行うことができます。
たとえば、次のクエリは、タイトルが「Hello!」のすべてのユーザー投稿を返します。
// Count all user posts with the title "Hello!"
await prisma.user.findMany({
select: {
_count: {
select: {
posts: { where: { title: 'Hello!' } },
},
},
},
})
次のクエリは、「Alice」という名前の作成者からのコメントがあるすべてのユーザー投稿を検索します。
// Count all user posts that have comments
// whose author is named "Alice"
await prisma.user.findMany({
select: {
_count: {
select: {
posts: {
where: { comments: { some: { author: { is: { name: 'Alice' } } } } },
},
},
},
},
})
非null
フィールド値をカウント
2.15.0以降では、すべてのレコードと、null
以外のフィールド値のすべてのインスタンスをカウントできます。次のクエリは、次のカウントを返します。
- すべての
User
レコード(_all
) - すべての非
null
name
値(個別の値ではなく、null
ではない値のみ)
const userCount = await prisma.user.count({
select: {
_all: true, // Count all records
name: true, // Count all non-null field values
},
})
フィルター処理されたカウント
count
はフィルタリングをサポートしています。次の例のクエリは、100を超えるプロファイルビューを持つすべてのユーザーをカウントします。
const userCount = await prisma.user.count({
where: {
profileViews: {
gte: 100,
},
},
})
次の例のクエリは、特定のユーザーの投稿をカウントします。
const postCount = await prisma.post.count({
where: {
authorId: 29,
},
})
個別の選択
Prisma Clientを使用すると、distinct
を使用して、findMany
クエリに対するPrismaクエリ応答から重複する行をフィルタリングできます。distinct
は、テーブルの行にある特定のユニークな値の組み合わせを識別するために、select
と組み合わせて使用されることがよくあります。
次の例は、個別のname
フィールド値を持つすべてのUser
レコードのすべてのフィールドを返します。
const result = await prisma.user.findMany({
where: {},
distinct: ['name'],
})
次の例は、個別のrole
フィールド値(たとえば、ADMIN
とUSER
)を返します。
const distinctRoles = await prisma.user.findMany({
distinct: ['role'],
select: {
role: true,
},
})
distinct
の内部構造
Prisma Clientのdistinct
オプションは、SQL SELECT DISTINCT
を使用しません。代わりに、distinct
は以下を使用します。
SELECT
クエリ- 個別の選択を行うためのインメモリ後処理
これは、distinct
クエリの一部としてselect
とinclude
を**サポート**するためにこのように設計されました。
次の例では、**ゲームごとの各プレイヤーの最高スコア**を返すために、gameId
とplayerId
で個別の選択を行い、score
で順序付けします。クエリは、追加データを含めるためにinclude
とselect
を使用します。
score
を選択します(Play
のフィールド)。- 関連するプレイヤー名を選択します(
Play
とUser
のリレーション)。 - 関連するゲーム名を選択します(
Play
とGame
のリレーション)。
サンプルスキーマを展開
model User {
id Int @id @default(autoincrement())
name String?
play Play[]
}
model Game {
id Int @id @default(autoincrement())
name String?
play Play[]
}
model Play {
id Int @id @default(autoincrement())
score Int? @default(0)
playerId Int?
player User? @relation(fields: [playerId], references: [id])
gameId Int?
game Game? @relation(fields: [gameId], references: [id])
}
const distinctScores = await prisma.play.findMany({
distinct: ['playerId', 'gameId'],
orderBy: {
score: 'desc',
},
select: {
score: true,
game: {
select: {
name: true,
},
},
player: {
select: {
name: true,
},
},
},
})
select
とdistinct
がない場合、クエリは次を返します。
[
{
gameId: 2,
playerId: 5
},
{
gameId: 2,
playerId: 10
}
]