带有派生表和ORDER BY的MySQL Update

时间:2014-11-19 09:44:06

标签: mysql sql-update derived-table

这个问题是来自Link的后续问题。我在不同的时间点t有一个人(id)和一个特征(var0)的表。在某些时间点,特征缺失,我想用前一个值填补空白。以下是表格的示例:

+---+---+----+            +----+---+------+------+------------------+
|id | t |var0|            | id | t | var0 | var1 | @prev_id   := id |
+---+---+----+            +----+---+------+------+------------------+
| 1 | 1 | a  |            |  1 | 1 | a    | a    |                1 |
| 1 | 3 | \N |            |  1 | 3 | \N   | a    |                1 |
| 1 | 7 | \N |            |  1 | 7 | \N   | a    |                1 |
| 1 | 8 | b  |            |  1 | 8 | b    | b    |                1 |
| 1 | 9 | \N |            |  1 | 9 | \N   | b    |                1 |
| 2 | 2 | \N |            |  2 | 2 | \N   | \N   |                2 |
| 2 | 4 | u  |            |  2 | 4 | u    | u    |                2 |
| 2 | 5 | u  |            |  2 | 5 | u    | u    |                2 |
| 2 | 6 | \N |            |  2 | 6 | \N   | u    |                2 |
| 2 | 7 | \N |            |  2 | 7 | u    | u    |                2 |
| 2 | 8 | v  |            |  2 | 8 | v    | v    |                2 |
| 2 | 9 | \N |            |  2 | 9 | \N   | v    |                2 |
+---+---+----+            +----+---+------+------+------------------+

左表是orignal x1表,右表是请求表。以下是获取结果的代码:

DROP TABLE IF EXISTS test01.x1;
CREATE TABLE test01.x1 (
  id   INTEGER
, t    INTEGER
, var0 CHAR(1)
) ENGINE = InnoDB 
DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci
;

INSERT INTO test01.x1(id,t,var0) VALUES
( 1,1,'a' )
,(1,3,NULL)
,(1,7,NULL)
,(1,8,'b' )
,(1,9,NULL)
,(2,2,NULL)
,(2,4,'u' )
,(2,5,'u' )
,(2,6,NULL)
,(2,7,'u')
,(2,8,'v' )
,(2,9,NULL)
;

DROP TABLE IF EXISTS test01.x2;
CREATE TABLE test01.x2
SELECT id, t
       , var0
       , @prev_var0 := CAST(IF(id = @prev_id AND var0 IS NULL AND @prev_var0 IS NOT NULL 
                           , @prev_var0
                               , var0 
                               ) AS CHAR
                            )  var1
       , @prev_id   := id
FROM test01.x1, (SELECT @prev_id    := NULL
                    ,@prev_var0 := NULL
        ) init
ORDER BY id, t
;

ALTER TABLE test01.x2 MODIFY var1 CHAR(1) DEFAULT NULL;


DROP TABLE IF EXISTS test01.x2;
CREATE TABLE test01.x2
SELECT * FROM test01.x1;


UPDATE test01.x1, (SELECT   @prev_id    := NULL
                          , @prev_var0  := NULL
          ) init
SET var0 = @prev_vr0 := IF(id = @prev_id AND var0 IS NULL AND @prev_var0 IS NOT NULL 
                           , @prev_var0
                               , var0 
                               )
    , @prev_id   := id

ORDER BY id, t

我会对另一种解决方案感兴趣。而不是创建一个新表x2我想更新表x1的var0。我试过这个:

UPDATE test01.x1, (SELECT   @prev_id    := NULL
                          , @prev_var0  := NULL
          ) init
SET var0 = @prev_vr0 := IF(id = @prev_id AND var0 IS NULL AND @prev_var0 IS NOT NULL 
                           , @prev_var0
                               , var0 
                               )
    , @prev_id   := id                 
ORDER BY id, t

但是有两个原因导致它不起作用(也许还有其他原因):

  • 多表UPDATE(参见Link
  • 不允许使用ORDER BY
  • @prev_id:= id不起作用。显然,在SET语句中,不可能直接为用户定义的变量赋值。

有没有人知道如何让左桌无间隙?

感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

您始终可以使用存储过程或函数:

声明存储函数:

DELIMITER //
 CREATE FUNCTION fillGap(
   gapID INT, verID INT
 ) RETURNS VARCHAR(255)
 BEGIN
   DECLARE gapValue VARCHAR(255);

 -- gets the value
 SELECT var0
 FROM x1
  WHERE id = gapID AND t <= verID AND var0 IS NOT NULL 
  ORDER BY t DESC
  LIMIT 1
 INTO
  gapValue;

  RETURN gapValue;
END //
DELIMITER ;

然后你可以在UPDATE语句中调用它:

UPDATE x1 SET var0 = fillGap(id, t) WHERE var0 IS NULL

此函数从数据库中获取一个前值,假设t是版本号,id是object_id。

问题将出现在case(id = 2,t = 2)中,因为此对象id没有前面的值。在任何情况下 - 编辑提供的功能并添加所需的逻辑。

答案 1 :(得分:0)

感谢Artjoman我可以创建一个存储过程来解决我的问题。它不像Artjoman的存储函数那么优雅,但它允许将表名和列名传递给过程。任何改进或替代方案都表示赞赏。

首先,我将列var0复制到具有两列的测试中:

ALTER TABLE test01.x1 ADD var1 CHAR(1) DEFAULT NULL;
UPDATE test01.x1 SET var1 = var0;     

存储过程是:

DROP PROCEDURE IF EXISTS sp_fillGap;
DELIMITER //
CREATE PROCEDURE sp_fillGap(IN xtable VARCHAR(64)
                             , IN xvar VARCHAR(64)
                             )
BEGIN

SET @query1 = CONCAT('CREATE TABLE xt1 SELECT * FROM ',xtable,' WHERE ',xvar,' IS NOT NULL;');
SET @query2 = CONCAT('CREATE TABLE xt2 SELECT * FROM ',xtable,' WHERE ',xvar,' IS NOT NULL;');

SET @query1a = CONCAT('DROP TABLE IF EXISTS xt1;');
SET @query2a = CONCAT('DROP TABLE IF EXISTS xt2;');

SET @query3 = CONCAT('UPDATE ',xtable,' a'
                     ,' SET a.',xvar,'  = (SELECT b.',xvar
                                       ,' FROM xt1 b' 
                                   ,' WHERE a.id = b.id'
                                                -- select the last of the former cases
                                       ,'       AND b.t = (SELECT MAX(c.t)'
                                       ,'                  FROM xt2 c'
                                       ,'                  WHERE a.id = c.id'
                                       ,'                        AND c.t <= a.t'
                                       ,'                  )'
                     ,'                   );'
                     );

    PREPARE stmt1a FROM @query1a;
    EXECUTE stmt1a;

PREPARE stmt2a FROM @query2a;
EXECUTE stmt2a;

PREPARE stmt1 FROM @query1;
EXECUTE stmt1;

PREPARE stmt2 FROM @query2;
EXECUTE stmt2;

PREPARE stmt3 FROM @query3;
EXECUTE stmt3;

EXECUTE stmt1a;
EXECUTE stmt2a;

END //
DELIMITER ;

测试:

CALL sp_fillGap('test01.x1','var0');
CALL sp_fillGap('test01.x1','var1');