Closed12
Prisma の Select distinct についての調査
ピン留めされたアイテム
概要
- Select distinct は重複を排除しながら値を取得する機能
- Prisma はデフォルトでは
SELECT DISTINCT
SQLを発行しない -
SELECT DISTINCT
を使わずにどうやって重複を排除しているのか気になるので調査する
環境
"dependencies": {
"@prisma/client": "^5.21.1"
},
"devDependencies": {
"prisma": "^5.21.1",
},
DB は docker compose で立ててる。以下は docker-compose.yml の設定
docker-compose.yaml
services:
db:
image: postgres:15.6
environment:
POSTGRES_USER: prisma-user
POSTGRES_PASSWORD: prisma-password
TZ: Asia/Tokyo
ports:
- 5432:5432
ピン留めされたアイテム
結論
- 2024/10/28 現在、バグがあり Prisma の Select distinct は使い物にならない
- やるなら
prisma.$queryRaw
を使った方が良さそう- TypedSQL があるのでなおさら
- Prisma が生成するクエリは必ず見ておくべき
テーブルの作成
prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Tag {
id Int @id @default(autoincrement())
name String
}
普通に取得してみる
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const createSeed = async () => {
const isExist = await prisma.tag.findFirst({ select: { id: true } });
if (isExist) return;
await prisma.tag.createMany({
data: Array(100)
.fill(0)
.map((_, i) => {
// 5 回に一回は同じ名前のタグを作る
return { name: `tag__${i % 5 === 0 ? "hoge" : i}` };
}),
});
};
await createSeed();
const data = await prisma.tag.findMany({
select: { name: true },
distinct: ["name"],
take: 6,
skip: 0,
});
console.log({ data });
取得結果
{
data: [
{ name: 'tag__hoge' },
{ name: 'tag__1' },
{ name: 'tag__2' },
{ name: 'tag__3' },
{ name: 'tag__4' },
{ name: 'tag__6' }
]
}
問題無さそう。
生成されるクエリ
SELECT
"public"."Tag"."id",
"public"."Tag"."name"
FROM
"public"."Tag"
WHERE
1 = 1
ORDER BY
"public"."Tag"."id" ASC
OFFSET
$1
全件取ってきてるヤバイ...
distinct を指定しなかったら
SELECT
"public"."Tag"."id",
"public"."Tag"."name"
FROM
"public"."Tag"
WHERE
1 = 1
ORDER BY
"public"."Tag"."id" ASC
LIMIT
$1
OFFSET
$2
LIMIT
が指定されているので、take
が SQL に反映されるッポイ。
分かったこと
- 重複処理は全件取ってきて、そこから重複を排除している
- 吐き出される SQL を見ると distinct がある時は
LIMIT
が付与されない - 安易に使うとヤバそう
nativeDistinct
を使って取得してみる
Prisma v5.7.0 から nativeDistinct
という SQL の distinct を使用するオプションが追加された。
設定
prisma/schema.prisma
generator client {
provider = "prisma-client-js"
+ previewFeatures = ["nativeDistinct"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Tag {
id Int @id @default(autoincrement())
name String
}
コード
これを使って取得してみる。コード自体は上で書いたヤツと同じ 👇
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const createSeed = async () => {
const isExist = await prisma.tag.findFirst({ select: { id: true } });
if (isExist) return;
await prisma.tag.createMany({
data: Array(100)
.fill(0)
.map((_, i) => {
// 5 回に一回は同じ名前のタグを作る
return { name: `tag__${i % 5 === 0 ? "hoge" : i}` };
}),
});
};
await createSeed();
const data = await prisma.tag.findMany({
select: { name: true },
distinct: ["name"],
take: 6,
skip: 0,
});
console.log({ data });
取得結果
{
data: [
{ name: 'tag__hoge' },
{ name: 'tag__1' },
{ name: 'tag__2' },
{ name: 'tag__3' },
{ name: 'tag__4' },
{ name: 'tag__6' }
]
}
問題無い。
生成されるクエリ
SELECT
"public"."Tag"."id",
"public"."Tag"."name"
FROM
"public"."Tag"
WHERE
1 = 1
ORDER BY
"public"."Tag"."id" ASC
OFFSET
$1
変わってないが???
take
を外して確認
SELECT DISTINCT
ON ("public"."Tag"."name") "public"."Tag"."id",
"public"."Tag"."name"
FROM
"public"."Tag"
WHERE
1 = 1
OFFSET
$1
DISTINCT
がちゃんと生成されてる。
バグっぽい
Issue 見た感じ、関連してそうなヤツがあった 👇
このスクラップは1ヶ月前にクローズされました