実現したいこと
MySQLで「既存ならIDを取得し, なければ保存してからIDを取得」を複数一括でやりたいです。
既存判定はユニーク制約のついたカラム(name, kind)
について走査します。
前提
テーブルは以下とします。
SQL
1-- テーブル 2CREATE TABLE IF NOT EXISTS test_users ( 3 id INT PRIMARY KEY AUTO_INCREMENT, 4 name VARCHAR(255), 5 kind VARCHAR(255), 6 age INT, 7 UNIQUE KEY unique_name_kind (name, kind) 8); 9 10-- 初期データ 11INSERT INTO test_users 12 (name, kind, age) 13VALUES 14 ('John', 'TypeA', 25), 15 ('John', 'TypeB', 30), 16 ('Alice', 'TypeB', 28);
発生している問題
「一括」ができません。「一回のクエリ」でやりたいのです…。
該当のソースコード
まずこちらを書きました。しかし見た感じのクエリは$sql
の一つですが、execute()
を複数回やっているので「一括」とは呼べないよなぁと思い、質問させて頂きました。
PHP
1 // 方法1 2 // execute() を複数回 3 // ON DUPLICATE KEY UPDATE は LAST_INSERT_ID を指定 4 final public function bulkInsertAndIfDuplicateGetIds1(array $users): array 5 { 6 // 返り値のID配列 7 $ids = []; 8 9 // INSERT文 10 $sql = 'INSERT INTO test_users (name, kind, age) 11 VALUES (:name, :kind, :age) 12 ON DUPLICATE KEY UPDATE 13 id = LAST_INSERT_ID(id)'; 14 15 try { 16 $stmt = $this->pdo->prepare($sql); 17 18 foreach ($users as $user) { 19 $stmt->execute($user); 20 $ids[] = $this->pdo->lastInsertId(); 21 } 22 23 return $ids; 24 } catch (\PDOException $e) { 25 echo $e->getMessage(); 26 } 27 }
試したこと
execute()
を1回とし、array_fill()
を使い「一括」を試みました。しかし「既存ならIDを取得」ができず、新規IDのみの取得となりました。
PHP
1 // 方法2 2 // execute() を1回 3 // ON DUPLICATE KEY UPDATE は LAST_INSERT_ID を指定 4 final public function bulkInsertAndIfDuplicateGetIds2(array $users): array 5 { 6 // 返り値のID配列 7 $ids = []; 8 9 // INSERT文 10 $placeholders = implode(', ', array_fill(0, count($users), '(?, ?, ?)')); 11 $sql = 'INSERT INTO test_users (name, kind, age) 12 VALUES ' . $placeholders . ' 13 ON DUPLICATE KEY UPDATE 14 id = LAST_INSERT_ID(id)'; 15 16 try { 17 $stmt = $this->pdo->prepare($sql); 18 19 $params = []; 20 foreach ($users as $user) { 21 $params = array_merge($params, array_values($user)); 22 } 23 $stmt->execute($params); 24 25 for ($i = 0; $i < count($users); $i++) { 26 $ids[] = $this->pdo->lastInsertId(); 27 } 28 29 return $ids; 30 } catch (\PDOException $e) { 31 echo $e->getMessage(); 32 } 33 }
続いて、ON DUPLICATE KEY UPDATE
で LAST_INSERT_ID
を指定することが悪いのかと考え、ユニークキーを指定してみるように変更しました。しかしこちらも上と同様に新規IDのみの取得となりました。
PHP
1 // 方法3 2 // execute() を1回 3 // ON DUPLICATE KEY UPDATE は ユニークキーを指定 4 final public function bulkInsertAndIfDuplicateGetIds3(array $users): array 5 { 6 // 返り値のID配列 7 $ids = []; 8 9 // INSERT文 10 $placeholders = implode(', ', array_fill(0, count($users), '(?, ?, ?)')); 11 $sql = 'INSERT INTO test_users (name, kind, age) 12 VALUES ' . $placeholders . ' 13 ON DUPLICATE KEY UPDATE 14 name = VALUES(name), 15 kind = VALUES(kind)'; 16 17 try { 18 $stmt = $this->pdo->prepare($sql); 19 20 $params = []; 21 foreach ($users as $user) { 22 $params = array_merge($params, array_values($user)); 23 } 24 $stmt->execute($params); 25 26 for ($i = 0; $i < count($users); $i++) { 27 $ids[] = $this->pdo->lastInsertId(); 28 } 29 30 return $ids; 31 } catch (\PDOException $e) { 32 echo $e->getMessage(); 33 } 34 }
実行用のコードはこちらのリンクにございます。(__construct()
内のnew PDO()
をご指定頂ければ実行できます。)
https://3v4l.org/pZvuD
もしかして本件において「一括」というのは無理な話なのでしょうか…?
もし無理なのだとしたら、最速とか皆様のオススメなどを知りたいです。
ツールのバージョン
PHP は 8.2 です。
MySQL はやや古めで 5.7 です。
よろしくお願い致します。
追記
「IDを取得」と「一括」について意味がわかりにくいと指摘を受けましたのでご説明させていただきます。
「IDを取得」については bulkInsertAndIfDuplicateGetIds1()
が成功しています。リンク34行目で $result1
に ["1","5","3"]
が得れることで「IDを取得」が出来ているという意味です。(既存であった 1,3 を得て、さらに新規に 5 も得れています。)
あとはこれを「一括」でやりたいという意図で、 bulkInsertAndIfDuplicateGetIds2()
, bulkInsertAndIfDuplicateGetIds3()
を試しました。
そして「一括」かどうかは下記の違いを意図しておりました。
SQL
1-- 「一括」である 2INSERT INTO test_datas(xxx) 3VALUES ('aaa'), ('bbb'); 4 5-- 「一括」ではない 6INSERT INTO test_datas(xxx) 7VALUES ('aaa'); 8INSERT INTO test_datas(xxx) 9VALUES ('bbb');
なので bulkInsertAndIfDuplicateGetIds1()
は「一括」ではありませんが、「IDを取得」はできています。
逆に bulkInsertAndIfDuplicateGetIds2()
, bulkInsertAndIfDuplicateGetIds3()
は「一括」ですが、しかし「IDを取得」はできておらず、 ["4","4","4"]
になってしまいます。
以上のように「IDを取得」と「一括」をイメージしており、この両立を目指しているという次第です。

回答2件
あなたの回答
tips
プレビュー