【问题标题】:T-SQL: Best way to replace NULL with most recent non-null value?T-SQL:用最近的非空值替换 NULL 的最佳方法?
【发布时间】:2018-01-05 02:55:27
【问题描述】:

假设我有这张桌子:

+----+-------+
| id | value |
+----+-------+
|  1 |     5 |
|  2 |     4 |
|  3 |     1 |
|  4 |  NULL |
|  5 |  NULL |
|  6 |    14 |
|  7 |  NULL |
|  8 |     0 |
|  9 |     3 |
| 10 |  NULL |
+----+-------+

我想编写一个查询,将任何NULL 值替换为表中该列中不为空的最后一个值

我想要这个结果:

+----+-------+
| id | value |
+----+-------+
|  1 |     5 |
|  2 |     4 |
|  3 |     1 |
|  4 |     1 |
|  5 |     1 |
|  6 |    14 |
|  7 |    14 |
|  8 |     0 |
|  9 |     3 |
| 10 |     3 |
+----+-------+

如果不存在先前的值,则 NULL 是可以的。理想情况下,即使使用ORDER BY,它也应该能够工作。例如,如果我ORDER BY [id] DESC:

+----+-------+
| id | value |
+----+-------+
| 10 |  NULL |
|  9 |     3 |
|  8 |     0 |
|  7 |     0 |
|  6 |    14 |
|  5 |    14 |
|  4 |    14 |
|  3 |     1 |
|  2 |     4 |
|  1 |     5 |
+----+-------+

如果我ORDER BY [value] DESC 更好:

+----+-------+
| id | value |
+----+-------+
|  6 |    14 |
|  1 |     5 |
|  2 |     4 |
|  9 |     3 |
|  3 |     1 |
|  8 |     0 |
|  4 |     0 |
|  5 |     0 |
|  7 |     0 |
| 10 |     0 |
+----+-------+

认为这可能涉及某种分析函数 - 以某种方式对 value 列进行分区 - 但我不确定在哪里查看。

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    您可以使用运行总和来设置组,并使用 max 来填充空值。

    select id,max(value) over(partition by grp) as value
    from (select id,value,sum(case when value is not null then 1 else 0 end) over(order by id) as grp
          from tbl
         ) t
    

    over() 子句更改为order by value desc 以获得问题中的第二个结果。

    【讨论】:

      【解决方案2】:

      Itzik Ben-Gan 在这里介绍了最好的方法:The Last non NULL Puzzle

      下面是一个解决方案,它可以在我的系统上处理 1000 万行并在 20 秒内完成

      SELECT
        id,
        value1,
        CAST(
        SUBSTRING(
        MAX(CAST(id AS binary(4)) + CAST(value1 AS binary(4)))
        OVER (ORDER BY id
        ROWS UNBOUNDED PRECEDING),
        5, 4)
        AS int) AS lastval
      FROM dbo.T1;
      

      此解决方案假定您的 id 列已编入索引

      【讨论】:

        【解决方案3】:

        你也可以尝试使用correlated子查询

        select id,
               case when value is not null then value else
               (select top 1 value from table 
                where id < t.id and value is not null  order by id desc) end value  
        from table t
        

        结果:

        id  value
        1   5
        2   4
        3   1
        4   1
        5   1
        6   14
        7   14
        8   0
        9   3
        10  3
        

        【讨论】:

          【解决方案4】:

          如果 NULL 是分散的,我会使用 WHILE 循环来填充它们

          但是,如果 NULL 位于较长的连续字符串中,则有更快的方法来做到这一点。

          所以这是一种方法:

          首先找到我们要更新的记录。这条记录有NULL,前一条记录没有NULL

          SELECT C.VALUE, N.ID  
          FROM TABLE C
          INNER JOIN TABLE N
          ON C.ID + 1 = N.ID
          WHERE C.VALUE IS NOT NULL
          AND N.VALUE IS NULL;
          

          用它来更新:(这个语法有点模糊,但你明白了)

          UPDATE N
          SET VALUE = C.Value
          FROM TABLE C
          INNER JOIN TABLE N
          ON C.ID + 1 = N.ID
          WHERE C.VALUE IS NOT NULL
          AND N.VALUE IS NULL;
          

          .. 现在继续做,直到你用完行

          -- This is needed to set @@ROWCOUNT to non zero
          SELECT 1;
          
          
          WHILE @@ROWCOUNT <> 0
          BEGIN 
          
          UPDATE N
          SET VALUE = C.Value
          FROM TABLE C
          INNER JOIN TABLE N
          ON C.ID + 1 = N.ID
          WHERE C.VALUE IS NOT NULL
          AND N.VALUE IS NULL;
          
          END
          

          另一种方法是使用类似的查询来获取要更新的 ID 的范围。如果您的 NULLS 通常针对连续的 id,这会工作得更快

          【讨论】:

            【解决方案5】:

            这是使用OUTER APPLY的一种简单方法

            CREATE TABLE #table(id INT, value INT)
            INSERT INTO #table VALUES 
            (1,5),
            (2,4),
            (3,1),
            (4,NULL),
            (5,NULL),
            (6,14),
            (7,NULL),
            (8,0),
            (9,3),
            (10,NULL)
            
            SELECT t.id, ISNULL(t.value, t3.value) value
            FROM #table t
            OUTER APPLY(SELECT id FROM #table WHERE id = t.id AND VALUE IS NULL) t2
            OUTER APPLY(SELECT TOP 1 value 
                        FROM #table WHERE id <= t2.id AND VALUE IS NOT NULL ORDER BY id DESC) t3
            

            输出:

            id  VALUE
            ---------
            1   5
            2   4
            3   1
            4   1
            5   1
            6   14
            7   14
            8   0
            9   3
            10  3
            

            【讨论】:

              【解决方案6】:

              使用此示例数据:

              if object_id('tempdb..#t1') is not null drop table #t1;
              create table #t1 (id int primary key, [value] int null);
              insert #t1 values(1,5),(2,4),(3,1),(4,NULL),(5,NULL),(6,14),(7,NULL),(8,0),(9,3),(10,NULL);
              

              我想出了:

              with x(id, [value], grouper) as (
              select *, row_number() over (order by id)-sum(iif([value] is null,1,0)) over (order by id)
              from #t1)
              select id, min([value]) over (partition by grouper)
              from x;
              

              但是,我注意到 Vamsi Prabhala 比我更胜一筹……我的解决方案与他发布的相同。 (啊!)。所以我想我会尝试一个递归解决方案。这是递归 cte 的一种非常有效的用法(如果 ID 已编入索引):

              with sorted as (select *, seqid = row_number() over (order by id) from #t1),
              firstRecord as (select top(1) * from #t1 order by id),
              prev as
              (
                select t.id, t.[value], lastid = 1, lastvalue = null
                from sorted t
                where t.id = 1
                union all
                select t2.id, t2.[value], lastid+1, isnull(prev.[value],lastvalue)
                from sorted t2
                join prev on t2.id = prev.lastid+1
              )
              select id, [value]=isnull([value],lastvalue)--, *
              from prev;
              

              通常我不喜欢递归 cte(简称 rCte),但在这种情况下,它提供了一个优雅的解决方案,并且比使用窗口聚合函数(求和、最小值...)更快。注意执行计划,底部的 rcte。 rCTE 通过两次索引查找来完成,其中一次只针对一行。与窗口聚合解决方案不同,rcte 不需要排序。使用statistics io on 运行它; rcte 产生的 IO 少得多。

              说了这么多,不要使用这些解决方案中的任何一个,TheGameiswar 发布的内容将是迄今为止表现最好的。他在正确索引的 id 列上的解决方案将很快减轻。

              【讨论】:

                【解决方案7】:

                可以使用以下UPDATE语句,使用前请测试

                update #table
                set value = newvalue
                from (
                    select 
                    s.id, s.value,
                    (select top 1 t.value from #table t where t.id <= s.id and t.value is not null order by t.id desc) as newvalue
                    from #table S
                ) u
                where #table.id = u.id and #table.value is null
                

                【讨论】:

                  【解决方案8】:

                  别担心..这里是你的答案:)

                  SELECT *
                  INTO   #TempIsNOtNull
                  FROM   YourTable
                  WHERE  value IS NOT NULL
                  
                  
                  SELECT *
                  INTO   #TempIsNull
                  FROM   YourTable
                  WHERE  value IS NULL
                  
                  
                  UPDATE YourTable
                  SEt           YourTable.value      =      UpdateDtls.value
                  FROM   YourTable
                  JOIN   (
                            SELECT OuterTab1.id,
                                         #TempIsNOtNull.value
                            FROM   #TempIsNull  OuterTab1
                            CROSS  JOIN #TempIsNOtNull
                            WHERE  OuterTab1.id - #TempIsNOtNull.id > 0
                                   AND (OuterTab1.id - #TempIsNOtNull.id)  = ( SELECT  TOP 1 
                      OuterTab1.id - #TempIsNOtNull.id
                  
                     FROM       #TempIsNull  InnerTab
                  
                   CROSS       JOIN #TempIsNOtNull
                  
                   WHERE       OuterTab1.id - #TempIsNOtNull.id > 0
                  
                   AND OuterTab1.id     =      InnerTab.id
                  
                   ORDER BY (OuterTab1.id - #TempIsNOtNull.id) ASC) ) AS UpdateDtls
                   ON     (YourTable.id   =  UpdateDtls.id)
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2011-06-25
                    • 2021-07-17
                    • 2017-08-23
                    • 1970-01-01
                    • 2010-10-04
                    • 2011-10-26
                    • 1970-01-01
                    • 2019-03-29
                    相关资源
                    最近更新 更多