一方向: SET x=CASE..END (任意の SQL)
はい、これを行うことができますが、クエリのレイテンシが非常に大きい場合を除き、パフォーマンスが向上するとは思えません。
クエリが検索値でインデックス化されている場合 (たとえばid
、主キーの場合)、目的のタプルの検索は非常に高速で、最初のクエリの後、テーブルはメモリに保持されます。
したがって、この場合の複数の UPDATE はそれほど悪いことではありません。
一方、条件が完全なテーブル スキャンを必要とし、さらに悪いことに、テーブルのメモリへの影響が大きい場合は、UPDATE の評価が単純な UPDATE よりもコストがかかるとしても、単一の複雑なクエリを使用する方が適切です (これは、内部的に最適化されます)。
この後者の場合、次のことができます。
UPDATE table SET posX=CASE
WHEN id=id[1] THEN posX[1]
WHEN id=id[2] THEN posX[2]
...
ELSE posX END [, posY = CASE ... END]
WHERE id IN (id[1], id[2], id[3]...);
総コストは、おおよそ次のように計算されます: NUM_QUERIES * ( COST_QUERY_SETUP + COST_QUERY_PERFORMANCE )。この方法では、NUM_QUERIES を (N 個の個別の ID から 1 に) 減少させますが、COST_QUERY_PERFORMANCE は増加します (MySQL 5.28 では約 3 倍、MySQL 8 ではまだテストしていません)。
それ以外の場合は、id でインデックスを作成するか、アーキテクチャを変更してみます。
これは PHP の例です。完全なテーブル スキャンが必要な条件があり、それをキーとして使用できると仮定します。
// Multiple update rules
$updates = [
"fldA='01' AND fldB='X'" => [ 'fldC' => 12, 'fldD' => 15 ],
"fldA='02' AND fldB='X'" => [ 'fldC' => 60, 'fldD' => 15 ],
...
];
右側の式で更新されるフィールドは 1 つまたは複数の可能性があり、常に同じでなければなりません (この場合は常に fldC と fldD)。この制限は取り除くことができますが、アルゴリズムを変更する必要があります。
次に、ループを介して単一のクエリを作成できます。
$where = [ ];
$set = [ ];
foreach ($updates as $when => $then) {
$where[] = "({$when})";
foreach ($then as $fld => $value) {
if (!array_key_exists($fld, $set)) {
$set[$fld] = [ ];
}
$set[$fld][] = $value;
}
}
$set1 = [ ];
foreach ($set as $fld => $values) {
$set2 = "{$fld} = CASE";
foreach ($values as $i => $value) {
$set2 .= " WHEN {$where[$i]} THEN {$value}";
}
$set2 .= ' END';
$set1[] = $set2;
}
// Single query
$sql = 'UPDATE table SET '
. implode(', ', $set1)
. ' WHERE '
. implode(' OR ', $where);
別の方法: ON DUPLICATE KEY UPDATE (MySQL)
MySQL では、存在しない条件 ("id = 777" with 777) がテーブルに挿入され、おそらくエラーが発生することを念頭に置いて、id が主キーであると仮定するINSERT ON DUPLICATE KEY UPDATE
と、 multipleを使用してこれをより簡単に実行できると思います。たとえば、他の必要な列 (NOT NULL と宣言) がクエリで指定されていません。
INSERT INTO tbl (id, posx, posy, bazinga)
VALUES (id1, posY1, posY1, 'DELETE'),
...
ON DUPLICATE KEY SET posx=VALUES(posx), posy=VALUES(posy);
DELETE FROM tbl WHERE bazinga='DELETE';
上記の 'bazinga' トリックにより、ID が存在しないために無意識に挿入された可能性のある行を削除できます (ただし、他のシナリオでは、挿入された行を残しておきたい場合があります)。
たとえば、収集された一連のセンサーからの定期的な更新ですが、一部のセンサーは送信されていない可能性があります。
INSERT INTO monitor (id, value)
VALUES (sensor1, value1), (sensor2, 'N/A'), ...
ON DUPLICATE KEY UPDATE value=VALUES(value), reading=NOW();
(これは不自然なケースです。おそらく、テーブルをロックし、すべてのセンサーを N/A に UPDATE して NOW() にしてから、存在する値のみを INSERT する方が合理的です)。
3 番目の方法: CTE (PostgreSQL、SQLite3 については不明)
これは概念的には INSERT MySQL トリックとほぼ同じです。書かれているように、PostgreSQL 9.6 で動作します。
WITH updated(id, posX, posY) AS (VALUES
(id1, posX1, posY1),
(id2, posX2, posY2),
...
)
UPDATE myTable
SET
posX = updated.posY,
posY = updated.posY
FROM updated
WHERE (myTable.id = updated.id);