🫠

Prismaで本番用のデータをDBに挿入した時にAuto Increment周りのエラーで困ったこととその対処案

2024/11/09に公開

背景

  • 仕事でNestJS + Prismaの案件があった。DBはPostgreSQL。
  • プロダクトの本番環境にデータを入れる際に、困ったことがあったので共有したい。

やろうとしたこと

お客様からcsvを頂いて、そのデータをテーブルにinsertしようとしていた。実際には以下のようなコードを書いていた。

// csv読み込み
const csvFilepath = join(process.cwd(), "./prisma/csv/test.csv");
fs.createReadStream(csvFilepath).pipe(
  csv.parse(async (err, data) => {
  
    // err handling
    if (!!err) {
      console.error(err);
      return;
    }
    
    // 上手いこといったらPrismaを使ってupsertしていく
    for (const [
      idStr,
      nameStr,
    ] of data) {
      const id = Number(idStr);
      const name = nameStr;
      const result = await prisma.test.upsert({
        where: { id },
        update: {
          name,
        },
        create: {
          id,
          name,
        },
      });
    }
  })
);

起こったこと

  • 初期データのinsert自体はうまくいったが、次以降にデータをinsertすると500を返すようになった。
  • 主キーを固定にして、挿入していたためにautoincrementが更新されていなかった
    • Invalid create() Unique constraint failed on the fields: (id)

解決案の候補

案1

初期データの主キーを指定せずにinsertする(autoincrementの自動採番に任せる)

  • テーブルごとに他のテーブルのリレーションも全てcsvに書いてあるので厳しい
    • 例:userのid とリレーションのあるuser_idがpostテーブルのcsvに固定値に書かれている状況で変えたくない事情がある

User

id name
1 taro
2 hanako

Post

id user_id content
1 1 こんにちは
2 1 おやすみなさい

案2:データ挿入時にautoincrementをリセットする

  • 今回やったのはこれ

実際に行ったこと

下記を挿入する

await prisma.$executeRaw`
      SELECT setval(pg_get_serial_sequence('"test"', 'id'), MAX(id)) FROM "Test";
    `;

何をやっているのか:

  1. await prisma.$executeRaw
    prisma.$executeRawは生のSQLクエリを実行するメソッド
  2. SQLクエリの内容
SELECT setval(pg_get_serial_sequence('"test"', 'id'), MAX(id)) FROM "Test";
pg_get_serial_sequence('"test"', 'id')

この関数は、指定されたテーブル(ここでは"test")とカラム(ここではid)に関連するシーケンスの名前を取得する。上記の例だと"test"はテーブル名で、idはシーケンスを持つカラム名。

MAX(id)

MAX(id)は、"Test"テーブル内のidカラムの最大値を取得する。

setval(sequence_name, value)

setval関数は、指定されたシーケンスの現在の値を設定します。
第一引数にはシーケンス名が入り、第二引数には設定したい値が入る。

要するに、
ここでは、pg_get_serial_sequenceで取得したシーケンス名と、MAX(id)で取得した最大のid値を使ってシーケンスの値を設定している。

"Test"テーブルのidカラムに関連するシーケンスの値を、テーブル内の最大のid値に設定すること。新しいレコードを挿入する際に、idカラムの値が重複しないようにしている。

感想

かなり泥臭い方法だと思うのでより良い方法があればそちらを使いたい

Discussion