【问题标题】:MySQL Update with derived tables and ORDER BYMySQL 使用派生表和 ORDER BY 更新
【发布时间】:2015-01-16 18:37:27
【问题描述】:

这个问题是来自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 |
+---+---+----+            +----+---+------+------+------------------+

左表是原始 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

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

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

有谁知道我怎样才能得到没有间隙的左表?

感谢您的帮助。

【问题讨论】:

    标签: mysql sql-update derived-table


    【解决方案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,此函数从数据库中获取一个前面的值。

    在 (id=2, t=2) 的情况下会出现问题,因为该对象 id 没有前面的值。在任何情况下 - 编辑提供的函数并添加所需的逻辑。

    【讨论】:

    • 非常感谢。这段代码将成为许多其他存储函数的基础。
    • 大小写(id=2,t=2)没有问题。它给出了 NULL,这就是我想要的,因为没有先验信息。
    • 如果我有多个变量 var0、var1、var2 等,我试图弄清楚如何推广此函数。我尝试使用 CREATE FUNCTION fillGap(gapID INT, verID INT, xvar CHAR(1)) ..... WHERE id=gapID t &lt;= verID AND xvar IS NOT NULL)。然后:UPDATE x1 SET var0 = fillGap(id, t) WHERE var0 IS NULL。它进行任何更新。我想问题是由于第一个子句中的参数 xvar 造成的。我想分配变量的名称而不是内容。
    • 对不起,我无法理解您有很多列或很多表要作为变量传递?
    • 对不起,我没有说清楚。我有一张有很多列的桌子。更新部分应该是UPDATE x1 SET var0 = fillGap(id, t, var0) WHERE var0 IS NULL; resp。 UPDATE x1 SET var1 = fillGap(id, t, var1) WHERE var1 IS NULL;etc..
    【解决方案2】:

    感谢 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');
    

    【讨论】:

      猜你喜欢
      • 2019-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-27
      • 2017-09-12
      • 2014-05-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多