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

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

## 基本的なトランザクションの使用例
```js
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 トランザクション
```js
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文を生成
```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 使えよ
```js
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文を生成
```sql
BEGIN;
INSERT INTO your_table_name (id, name) VALUES (1, 'Item1'), (2, 'Item2'), (3, 'Item3'), (4, 'Item4')
COMMIT;
```

### bulk update トランザクション
```js
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文を生成
```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 使えよ
```js
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文を生成
```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 をもっとうまく使う
```js
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文を生成
```sql
BEGIN;

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

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

COMMIT;
```

### prisma ではできない理想
```sql
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 トランザクション
```js
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);
```
サンプル
```js
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文を生成
```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
```js
// 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のログ機能を使用する
クエリのログをコンソールに出力する機能
```js
// Prisma Clientを設定する際に、logオプションを使用してログレベルを指定。
const prisma = new PrismaClient({
  log: ['query', 'info', 'warn', 'error']
});
```


# references
* [トランザクションとバッチクエリ (リファレンス) | Prisma ドキュメント](https://www.prisma.io/docs/orm/prisma-client/queries/transactions)
* [Prisma で Upsert Many はないのか](https://qiita.com/dosukoi_man/items/ca3d7202a8762169edd0)