如果我从mysql数据库表中得到以下结果集:
+----+------+-------+
| ID | type | value |
+----+------+-------+
| 8 | A | 1435 |
| 9 | B | 7348 |
| 10 | A | 1347 |
| 11 | A | 3478 |
| 12 | A | 4589 |
| 13 | B | 6789 |
+----+------+-------+
我想删除行ID 8并将字段'value'中的值向下推,这样每行都有前一个条目的值,但只影响字段'type'相同的那些值删除行(在这种情况下为“A”)。
也就是说,删除行id 8最终会产生以下结果:
+----+------+-------+
| ID | type | value |
+----+------+-------+
| - | - | - | *
| 9 | B | 7348 | |
| 10 | A | 1435 | * |
| 11 | A | 1347 | * |
| 12 | A | 3478 | * |
| 13 | B | 6789 | V
+----+------+-------+
ID 10继承了ID 8的值,然后ID 11继承自ID 10,依此类推。但请注意,类型为“B”的行不受影响。
所以问题:是否有任何方法可以执行值的“移位”而无需逐个查询和更新每一行?在理想的世界中,我会做一个查询来做移位,然后另一个查询来删除行,但我不确定这是否可行。
(另外我宁愿不使用Triggers,因为我打算在应用程序本身中封装所有应用程序逻辑)
答案 0 :(得分:2)
SET @remove_id = 8;
SELECT ID, type, value FROM (
SELECT ID,
type,
CAST(IF(type <> @type OR ISNULL(@val), value, @val) AS UNSIGNED)
AS value,
@type := IF(ID = @remove_id, type, @type),
@val := IF(type = @type, value, @val)
FROM my_table JOIN (SELECT @type := NULL, @val := NULL) AS z
ORDER BY ID ASC
) AS t
WHERE ID <> @remove_id
在sqlfiddle上查看。
<强>更新强>
我没有意识到你确实想要更新基础表。为此,您可以使用一些轻微的hackery在UPDATE
语句中有效地执行相同的操作(不能直接分配给用户变量,因此请将列的新值和空字符串的串联分配给列通过获取新分配的用户变量的前0个字符形成:
SET @remove_id = 8, @type = NULL, @val = NULL;
UPDATE my_table SET
value = IF(
type <> @type OR ISNULL(@val),
value,
CONCAT(@val, LEFT(@val := value, 0))
),
type = CONCAT(type, LEFT(
@type := IF(
ID <> @remove_id,
@type,
CONCAT(type, LEFT(@val := value, 0))
)
, 0))
ORDER BY ID ASC;
DELETE FROM my_table WHERE ID = @remove_id;
在sqlfiddle上查看。
答案 1 :(得分:2)
使用窗口/分析功能可以非常轻松地完成此任务。由于MySQL没有这样的功能,可以使用以下限制进行模拟:
我已将id
用于此目的。
以下查询将使用type
作为分区指示符来限制表中的每一行:
SELECT t.id, t.type, t.value,
(SELECT count(*) FROM testbed WHERE type = t.type AND id <= t.id) AS rownum
FROM testbed t
ORDER BY t.type, t.id;
我仅为可见性添加了ORDER BY
,在最终查询中不需要。
下一个查询允许您加入2个结果,并有办法以所需的方式“移动值”:
SELECT c.id AS c_id, c.type AS c_type, c.value AS c_value,
p.id AS p_id, p.type AS p_type, p.value AS p_value
FROM (SELECT t.id, t.type, t.value,
(SELECT count(*) FROM testbed
WHERE type = t.type AND id <= t.id) AS rownum
FROM testbed t) AS c
LEFT JOIN (SELECT t.id, t.type, t.value,
(SELECT count(*) FROM testbed
WHERE type = t.type AND id <= t.id) AS rownum
FROM testbed t) AS p
ON c.type = p.type AND c.rownum = p.rownum + 1
ORDER BY c.type, c.id;
最后,您的任务通过以下两个查询UPDATE
和DELETE
完成:
UPDATE testbed AS t
JOIN (
SELECT c.id AS c_id, c.type AS c_type, c.value AS c_value,
p.id AS p_id, p.type AS p_type, p.value AS p_value
FROM (SELECT t.id, t.type, t.value,
(SELECT count(*) FROM testbed
WHERE type = t.type AND id <= t.id) AS rownum
FROM testbed t) AS c
LEFT JOIN (SELECT t.id, t.type, t.value,
(SELECT count(*) FROM testbed
WHERE type = t.type AND id <= t.id) AS rownum
FROM testbed t) AS p
ON c.type = p.type AND c.rownum = p.rownum + 1
) AS s
ON t.id = s.c_id
SET t.value = s.p_value
WHERE t.value = 'A'; -- you can use more complex predicate(s) here
DELETE FROM testbed WHERE id = 8; -- make sure both predicate(s) match
您可以在SQL Fiddle(而不是更新)上查看此查询。
答案 2 :(得分:0)
我建议你使用InnoDB表,这样你就可以在一个事务中运行2个查询。 我会这样做:
Step 1: Start a transaction on the table
Step 2: Get the record that is to be deleted (e.g. ID #8)
Step 3: Issue a query DELETE FROM tablename WHERE `ID`=$record_id
Step 4: Issue a query UPDATE tablename SET `value`='former_value' WHERE `type`='former_type' LIMIT 1
Step 5: if all operations were successful, commit the transaction else rollback
Step 6: End the transaction
希望这有帮助