メインコンテンツにスキップ

集計、グループ化、および要約

Prisma Clientを使用すると、レコードのカウント、数値フィールドの集計、および個別のフィールド値の選択ができます。

集計

Prisma Clientを使用すると、モデルの**数値**フィールド(IntFloatなど)でaggregateを実行できます。次のクエリは、すべてのユーザーの平均年齢を返します。

const aggregations = await prisma.user.aggregate({
_avg: {
age: true,
},
})

console.log('Average age:' + aggregations._avg.age)

集計は、フィルタリングおよび順序付けと組み合わせることができます。たとえば、次のクエリはユーザーの平均年齢を返します。

  • age昇順で並べ替え
  • emailprisma.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,
},
})
表示CLI結果
{
_avg: {
age: null
},
_count: {
age: 9
}
}

クエリは、次のいずれかのシナリオで{ _avg: { age: null } }を返します。

  • ユーザーがいない
  • すべてのユーザーのageフィールドの値がnullである

これにより、真の集計値(ゼロの場合もある)とデータなしを区別できます。

グループ化

Prisma ClientのgroupBy()を使用すると、countrycountrycityなど、1つ以上のフィールド値で**レコードをグループ化**し、特定の都市に住む人々の平均年齢を見つけるなど、各グループで**集計を実行**できます。groupBy()2.20.0以降でGAになりました。

次のビデオでは、groupBy()を使用して、大陸別のCOVID-19症例の合計を要約しています。

次の例では、すべてのユーザーをcountryフィールドでグループ化し、国ごとのプロファイルビューの合計数を返します。

const groupUsers = await prisma.user.groupBy({
by: ['country'],
_sum: {
profileViews: true,
},
})
表示CLI結果

byオプションに単一の要素がある場合は、次の省略形構文を使用してクエリを表現できます。

const groupUsers = await prisma.user.groupBy({
by: 'country',
})

groupBy()とフィルタリング

groupBy()は、wherehavingの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またはtakegroupBy()で使用する場合は、クエリに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',
},
},
})
表示CLI結果

フィールドで並べ替え

次のクエリは、グループを国で並べ替え、最初の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()wherehavingを使用することの違いは何ですか?

whereはグループ化する前にすべてのレコードをフィルタリングし、havingはグループ全体をフィルタリングし、そのグループ内の特定のフィールドの平均または合計などの集計フィールド値でのフィルタリングをサポートします。

groupBy()distinctの違いは何ですか?

distinctgroupBy()の両方で、1つ以上のユニークなフィールド値でレコードをグループ化します。groupBy()を使用すると、各グループ内のデータを集計できます。たとえば、デンマークからの投稿の平均閲覧数を返すことができます。一方、distinctはそうではありません。

カウント

レコードをカウント

count()を使用して、レコード数またはnull以外のフィールド値をカウントします。次の例のクエリは、すべてのユーザーをカウントします。

const userCount = await prisma.user.count()

リレーションをカウント

情報

この機能は、バージョン3.0.1以降で一般的に利用可能です。3.0.1より前のバージョンでこの機能を使用するには、プレビュー機能selectRelationCountを有効にする必要があります。

リレーションのカウント(たとえば、ユーザーの投稿数)を返すには、次のようにネストされたselect_countパラメーターを使用します。

const usersWithCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
表示CLI結果

_countパラメーター

includeでリレーションカウントを返す

次のクエリは、各ユーザーの投稿数を結果に含めます。

const usersWithCount = await prisma.user.findMany({
include: {
_count: {
select: { posts: true },
},
},
})
表示CLI結果

selectでリレーションカウントを返す

次のクエリは、selectを使用して各ユーザーの投稿数と他のフィールドなしを返します。

const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: { posts: true },
},
},
})
表示CLI結果

複数のリレーションカウントを返す

次のクエリは、各ユーザーのpostsrecipesのカウントを返し、他のフィールドはありません。

const usersWithCount = await prisma.user.findMany({
select: {
_count: {
select: {
posts: true,
recipes: true,
},
},
},
})
表示CLI結果

リレーションカウントをフィルタリング

情報

この機能は、バージョン4.16.0以降で一般的に利用可能です。バージョン4.3.0から4.15.0でこの機能を使用するには、プレビュー機能filteredRelationCountを有効にする必要があります。

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
  • すべての非nullname値(個別の値ではなく、nullではない値のみ)
const userCount = await prisma.user.count({
select: {
_all: true, // Count all records
name: true, // Count all non-null field values
},
})
表示CLI結果

フィルター処理されたカウント

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フィールド値(たとえば、ADMINUSER)を返します。

const distinctRoles = await prisma.user.findMany({
distinct: ['role'],
select: {
role: true,
},
})
表示CLI結果

distinctの内部構造

Prisma Clientのdistinctオプションは、SQL SELECT DISTINCTを使用しません。代わりに、distinctは以下を使用します。

  • SELECTクエリ
  • 個別の選択を行うためのインメモリ後処理

これは、distinctクエリの一部としてselectincludeを**サポート**するためにこのように設計されました。

次の例では、**ゲームごとの各プレイヤーの最高スコア**を返すために、gameIdplayerIdで個別の選択を行い、scoreで順序付けします。クエリは、追加データを含めるためにincludeselectを使用します。

  • scoreを選択します(Playのフィールド)。
  • 関連するプレイヤー名を選択します(PlayUserのリレーション)。
  • 関連するゲーム名を選択します(PlayGameのリレーション)。
サンプルスキーマを展開
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,
},
},
},
})
表示CLI結果

selectdistinctがない場合、クエリは次を返します。

[
{
gameId: 2,
playerId: 5
},
{
gameId: 2,
playerId: 10
}
]