一种方式: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 ],
...
];
右侧表达式中更新的字段可以是一个或多个,必须始终相同(在这种情况下始终为 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 中,我认为您可以使用 multiple 更轻松地做到这一点INSERT ON DUPLICATE KEY UPDATE
,假设 id 是主键,请记住不存在的条件(“id = 777”,没有 777)将被插入到表中,如果出现错误,可能会导致错误,例如,查询中未指定其他必需的列(声明为 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 和 NOW(),然后只插入我们拥有的那些值)。
第三种方式: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);