6

假设我从 mysql 数据库表中获得以下结果集:

+----+------+-------+
| ID | type | value |
+----+------+-------+
|  8 |    A |  1435 |
|  9 |    B |  7348 | 
| 10 |    A |  1347 | 
| 11 |    A |  3478 | 
| 12 |    A |  4589 | 
| 13 |    B |  6789 |
+----+------+-------+

我想删除行 ID 8 并将字段“值”中的值向下推,这样每一行都具有前一个条目的值,但仅影响字段“类型”与行相同的那些被删除(在这种情况下为“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”的行不受影响。

所以问题是:有什么方法可以执行这种值的“移位”,而不必逐行查询和更新每一行?在一个理想的世界里,我会做一个查询来做移位,然后另一个来删除行,但我不太确定这是否可能。

(我也不想使用触发器,因为我打算将所有应用程序逻辑封装在应用程序本身中)

4

3 回答 3

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上查看。

于 2012-05-31T13:37:25.740 回答
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;

最后,您的任务通过以下 2 个查询完成,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上检查此查询(不是更新)。

于 2012-05-31T13:49:35.553 回答
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

希望这可以帮助

于 2012-05-31T13:06:01.563 回答