Closed12

Prisma の Select distinct についての調査

ピン留めされたアイテム
uttkuttk

概要

  • Select distinct は重複を排除しながら値を取得する機能
  • Prisma はデフォルトでは SELECT DISTINCT SQLを発行しない
  • SELECT DISTINCT を使わずにどうやって重複を排除しているのか気になるので調査する
uttkuttk

環境

"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
ピン留めされたアイテム
uttkuttk

結論

  • 2024/10/28 現在、バグがあり Prisma の Select distinct は使い物にならない
  • やるなら prisma.$queryRaw を使った方が良さそう
  • Prisma が生成するクエリは必ず見ておくべき
uttkuttk

テーブルの作成

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
}
uttkuttk

普通に取得してみる

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 });
uttkuttk

取得結果

{
  data: [
    { name: 'tag__hoge' },
    { name: 'tag__1' },
    { name: 'tag__2' },
    { name: 'tag__3' },
    { name: 'tag__4' },
    { name: 'tag__6' }
  ]
}

問題無さそう。

uttkuttk

生成されるクエリ

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 に反映されるッポイ。

uttkuttk

分かったこと

  • 重複処理は全件取ってきて、そこから重複を排除している
  • 吐き出される SQL を見ると distinct がある時は LIMIT が付与されない
  • 安易に使うとヤバそう
uttkuttk

nativeDistinct を使って取得してみる

Prisma v5.7.0 から nativeDistinct という SQL の distinct を使用するオプションが追加された。

https://github.com/prisma/prisma/releases/tag/5.7.0

設定

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 });
uttkuttk

取得結果

{
  data: [
    { name: 'tag__hoge' },
    { name: 'tag__1' },
    { name: 'tag__2' },
    { name: 'tag__3' },
    { name: 'tag__4' },
    { name: 'tag__6' }
  ]
}

問題無い。

uttkuttk

生成されるクエリ

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 がちゃんと生成されてる。

このスクラップは1ヶ月前にクローズされました