Open3

Prismaの便利な使い方

TakashiAiharaTakashiAihara

Atomic number operations

TL;DR

prisma.stock.update({
  where: { id },
  data: {
    quantity: {
      increment: quantity, // ← これ
    },
  },
});

概要

https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#atomic-number-operations

SQLだと簡単な インクリメント。

UPDATE stock
SET quantity = quantity + 1
WHERE id = id

的なのを実現してくれるやつ。

何がうれしい

↓こうやりがち

prisma.stock.update({
  where: { id },
  data: {
    quantity: existsQuantity + quantity, // ← ここ
  },
});

既存レコードを持ってないとUpdateできない!ってなるのを回避してくれる。

補足

下記のようなのが用意されているので、公式docへGO。

  • increment
  • decrement
  • multiply
  • divide
  • set
TakashiAiharaTakashiAihara

存在したらCreate、無かったらFindでレコードを返す

TL;DR

upsert を使う。
updateプロパティ を空にするだけ。

await prisma["hoge"].upsert({
  where: { id: data.id },
  update: {},
  create: data,
});

詳細は後述するが、無用なUPDATEが走ることもなかった。

背景

createOrFindみたいなものを実現するため、「findをして、無ければcreate」というロジックを組んでいた。
ただ、小さいとはいえ分岐が入ってくるし美しくなかった。
いろいろ試行錯誤した結果、前述の書き方に行き着いた。

調査

とはいえ、もしupdateが毎回走るのであれば、DB側の負荷増大は免れない。
分岐を自分で作ったほうがDBに優しい構造になる。
ので、テストコードで調査。

↓こんな感じにした。

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient({
  log: ["query", "info", "warn", "error"],
});

const applySeed = async (seed: { code: string }, schemaName: "jan") => {
  await prisma[schemaName].upsert({
    where: { code: seed.code },
    update: {},
    create: seed,
  });
};

applySeed({ code: "100" }, "jan")
  .then(async () => {
    await prisma.$disconnect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

※ 製作中のRepoからぶんどって改変した。スキーマ名とか汎用性とかはご容赦。

ログを吐かせつつ、ダミーデータでupsert。

初回実行

@acme/db:db:test: prisma:info Starting a postgresql pool with 9 connections.
@acme/db:db:test: prisma:query BEGIN
@acme/db:db:test: prisma:query SELECT "public"."Jan"."code" FROM "public"."Jan" WHERE ("public"."Jan"."code" = $1 AND 1=1) OFFSET $2
@acme/db:db:test: prisma:query INSERT INTO "public"."Jan" ("code","updatedAt","createdAt") VALUES ($1,$2,$3) RETURNING "public"."Jan"."code"
@acme/db:db:test: prisma:query SELECT "public"."Jan"."code", "public"."Jan"."updatedAt", "public"."Jan"."createdAt" FROM "public"."Jan" WHERE "public"."Jan"."code" = $1 LIMIT $2 OFFSET $3
@acme/db:db:test: prisma:query COMMIT

INSERTが走っている。

二回目実行

@acme/db:db:test: prisma:info Starting a postgresql pool with 9 connections.
@acme/db:db:test: prisma:query BEGIN
@acme/db:db:test: prisma:query SELECT "public"."Jan"."code" FROM "public"."Jan" WHERE ("public"."Jan"."code" = $1 AND 1=1) OFFSET $2
@acme/db:db:test: prisma:query SELECT "public"."Jan"."code" FROM "public"."Jan" WHERE ("public"."Jan"."code" = $1 AND 1=1)
@acme/db:db:test: prisma:query SELECT "public"."Jan"."code", "public"."Jan"."updatedAt", "public"."Jan"."createdAt" FROM "public"."Jan" WHERE "public"."Jan"."code" = $1 LIMIT $2 OFFSET $3
@acme/db:db:test: prisma:query COMMIT

SELECTのみ、UPDATEは走らなかった。
これで、upsertを createOrFindとして利用しても問題ないことが分かった。

補足

この方法は、@@id もしくは @@unique にあたるValueが分かっている場合にのみ使える。