0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

prisma で一括操作したかった時のメモ

Posted at

transaction と bulk

  • トランザクション: 一連のデータベース操作。操作の整合性を保証
  • バルク操作: 大量のデータを一度に処理するための方法。処理の効率を高める

基本的なトランザクションの使用例

try {
  await prisma.$transaction(async (prisma) => {
    // 1つ目の操作: ユーザーを作成
    const user = await prisma.user.create({
      data: {
        name: 'Alice',
        email: 'alice@example.com',
      },
    });

    // 2つ目の操作: ユーザーのプロフィールを作成
    await prisma.profile.create({
      data: {
        userId: user.id,
        bio: 'Hello, I am Alice!',
      },
    });

    // 3つ目の操作: さらに他の操作をここで実行できます
  });

  console.log('Transaction completed successfully.');
} catch (error) {
  console.error('Transaction failed: ', error);
} finally {
  await prisma.$disconnect();
}

バルク操作

Prismaの標準のAPIにはバルク操作を直接サポートする機能はありません。
「トランザクション」もしくは「Raw SQL」を使用して一括操作できる
Raw SQL: prisma.$executeRaw または prisma.$queryRaw を利用して、SQLの構文を直接使用する方法
prisma.$executeRaw: データベースに対する変更操作(INSERT, UPDATE, DELETE など)を実行し、結果として変更された行数などを返す。結果セットは返さない。
prisma.$queryRaw: データベースからデータを取得する操作(SELECT など)を実行し、結果セットを返す。

bulk insert トランザクション

async function bulkInsert() {
  const data = [
    { id: 1, name: 'Item1' },
    { id: 2, name: 'Item2' },
    { id: 3, name: 'Item3' },
  ];

  try {
    await prisma.$transaction(
      data.map(item =>
        prisma.your_table_name.create({
          data: item,
        })
      )
    );
    console.log('Bulk insert completed successfully.');
  } catch (error) {
    console.error('Bulk insert failed: ', error);
  } finally {
    await prisma.$disconnect();
  }
}

以下のようなSQL文を生成

BEGIN;
INSERT INTO your_table_name (id, name) VALUES (1, 'Item1');
INSERT INTO your_table_name (id, name) VALUES (2, 'Item2');
INSERT INTO your_table_name (id, name) VALUES (3, 'Item3');
COMMIT;

createMany 使えよ

async function bulkInsert() {
  const employees = [
    { name: 'Alice', position: 'Engineer' },
    { name: 'Bob', position: 'Manager' },
    { name: 'Charlie', position: 'Analyst' },
  ];

  try {
    await prisma.employee.createMany({
      data: employees,
      skipDuplicates: true, // 重複をスキップするオプション。主キーが重複した場合にエラーを防ぐ
    });
    console.log('Bulk insert completed successfully.');
  } catch (error) {
    console.error('Bulk insert failed: ', error);
  } finally {
    await prisma.$disconnect();
  }
}

以下のようなSQL文を生成

BEGIN;
INSERT INTO your_table_name (id, name) VALUES (1, 'Item1'), (2, 'Item2'), (3, 'Item3'), (4, 'Item4')
COMMIT;

bulk update トランザクション

async function bulkUpdate() {
  const updates = [
    { id: 1, data: { name: 'UpdatedName1' } },
    { id: 2, data: { name: 'UpdatedName2' } },
    { id: 3, data: { name: 'UpdatedName3' } },
  ];

  try {
    await prisma.$transaction(
      updates.map(update =>
        prisma.your_table_name.update({
          where: { id: update.id },
          data: update.data,
        })
      )
    );
    console.log('Bulk update completed successfully.');
  } catch (error) {
    console.error('Bulk update failed: ', error);
  } finally {
    await prisma.$disconnect();
  }
}

以下のようなSQL文を生成

BEGIN
SELECT id FROM your_table_name WHERE (id = ? AND 1=1)
UPDATE your_table_name SET name = 'UpdatedName1' WHERE (id IN (1) AND (id = 1 AND 1=1))
SELECT id, name FROM your_table_name WHERE id = 1 LIMIT ? OFFSET ?
SELECT id FROM your_table_name WHERE (id = ? AND 1=1)
UPDATE your_table_name SET name = 'UpdatedName2' WHERE (id IN (2) AND (id = 2 AND 1=1))
SELECT id, name FROM your_table_name WHERE id = 2 LIMIT ? OFFSET ?
SELECT id FROM your_table_name WHERE (id = ? AND 1=1)
UPDATE your_table_name SET name = 'UpdatedName3' WHERE (id IN (3) AND (id = 3 AND 1=1))
SELECT id, name FROM your_table_name WHERE id = 3 LIMIT ? OFFSET ?
COMMIT

updateMany 使えよ

app.get('/bulk_test/update2', async(req,res)=>{
  const updates = [
    { id: 2, data: { score: 83 } },
    { id: 5, data: { score: 91 } },
  ];
  try {
    for (const update of updates) {
      await prisma.your_table_name.updateMany({
        where: { id: update.id },
        data: update.data,
      });
    }
    console.log('Bulk update completed successfully.');
  } catch (error) {
    console.error('Bulk update failed: ', error);
  } finally {
    res.send("BULK !")
  }
});

以下のようなSQL文を生成

BEGIN
UPDATE your_table_name SET score = 83 WHERE id = 2
COMMIT
BEGIN
UPDATE your_table_name SET score = 91 WHERE id = 5
COMMIT

updateMany は複数レコードに同じデータで更新することが出来る関数であり、複数レコードに異なるデータで更新する関数ではない。

updateMany をもっとうまく使う

try {
  await prisma.$transaction([
    prisma.item.updateMany({
      where: { id: 2 },
      data: { score: 84 },
    }),
    prisma.item.updateMany({
      where: { id: 5 },
      data: { score: 92 },
    }),
  ]);
  console.log('Bulk update completed successfully.');
} catch (error) {
  console.error('Bulk update failed: ', error);
} finally {
  await prisma.$disconnect();
}

以下のようなSQL文を生成

BEGIN;

UPDATE "Item"
SET "score" = 84
WHERE "id" = 2;

UPDATE "Item"
SET "score" = 92
WHERE "id" = 5;

COMMIT;

prisma ではできない理想

UPDATE your_table_name
SET data = JSON_SET(data, '$.score', CASE id
  WHEN 2 THEN 83
  WHEN 5 THEN 91
  WHEN 7 THEN 92
  WHEN 8 THEN 78
  ELSE data
END)
WHERE id IN (2, 5, 7, 8);

upsert トランザクション

const upserts = data.map(item => prisma.your_table_name.upsert({
  where: { id: item.id },
  update: { /* 更新するフィールド */ },
  create: { /* 新規作成するフィールド */ },
}));

const result = await prisma.$transaction(upserts);
console.log(result.length);

サンプル

const userList = [
  { email: 'alice@example.com', name: 'Alice' },
  { email: 'bob@example.com', name: 'Bob' },
  { email: 'charlie@example.com', name: 'Charlie' },
];
const upserts = userList.map(item => prisma.your_table_name.upsert({
  where: { email: item.email, },
  update: { name: user.name, },
  create: {
    email: user.email,
    name: user.name,
  },
}));
await prisma.$transaction(upserts);

以下のようなSQL文を生成

-- For email = 'alice@example.com'
INSERT INTO `User` (`email`, `name`)
VALUES ('alice@example.com', 'Alice')
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);

-- For email = 'bob@example.com'
INSERT INTO `User` (`email`, `name`)
VALUES ('bob@example.com', 'Bob')
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);

-- For email = 'charlie@example.com'
INSERT INTO `User` (`email`, `name`)
VALUES ('charlie@example.com', 'Charlie')
ON DUPLICATE KEY UPDATE `name` = VALUES(`name`);

upsert Raw SQL

// const data = [/* 挿入または更新するデータの配列 */];
const data = [
  { id: 1, field1: 'Item11', field2: 'Item21' },
  { id: 2, field1: 'Item12', field2: 'Item22' },
  { id: 3, field1: 'Item13', field2: 'Item23' },
];

const query = `
  INSERT INTO your_table (id, field1, field2)
  VALUES ${data.map(item => `(${item.id}, '${item.field1}', '${item.field2}')`).join(', ')}
  ON DUPLICATE KEY UPDATE
    field1 = VALUES(field1),
    field2 = VALUES(field2)
`;

try {
  await prisma.$executeRaw(query);
  console.log('Bulk upsert completed successfully.');
} catch (error) {
  console.error('Bulk upsert failed: ', error);
} finally {
  await prisma.$disconnect();
}

Prismaが生成するSQL文を確認する

Prismaのログ機能を使用する

クエリのログをコンソールに出力する機能

// Prisma Clientを設定する際に、logオプションを使用してログレベルを指定。
const prisma = new PrismaClient({
  log: ['query', 'info', 'warn', 'error']
});

references

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?