【问题标题】:Replace NULL with values用值替换 NULL
【发布时间】:2014-05-09 21:21:48
【问题描述】:

这是我的挑战: 我有一个日志表,每次更改记录时都会添加一条新记录,但会为每条记录中的每个未更改值放置一个 NULL 值。换句话说,仅设置了更改的值,每行中其余未更改的字段仅具有 NULL 值。 现在我想用它上面的不是 NULL 值的值替换每个 NULL 值,如下所示:

源表:Task_log

ID  Owner       Status      Flag
1   Bob         Registrar   T
2   Sue         NULL        NULL
3   NULL        NULL        F
4   Frank       Admission   T
5   NULL        NULL        F
6   NULL        NULL        T

所需的输出表:Task_log

ID  Owner       Status      Flag
1   Bob         Registrar   T
2   Sue         Registrar   T
3   Sue         Registrar   F
4   Frank       Admission   T
5   Frank       Admission   F
6   Frank       Admission   T

如何编写一个查询来生成所需的输出表?

【问题讨论】:

  • 您使用的是哪个版本的 SQLServer?
  • 为什么您对编写查询来修复垃圾数据而不是修复实际插入记录的代码感兴趣?现在不是使用创可贴的好时机……解决根本问题。
  • 我使用的是 SQL Server 2012。我无法访问代码,因此我无法解决根本问题,因此我必须创造性地编写一个很酷的查询来产生所需的输出。
  • 我自己没有使用过它们,所以我还不能写出完整的答案,但是看看 LAG, @987654322 @LAST_VALUE(需要 Sql Server 2012 及更高版本)

标签: sql sql-server sql-server-2012


【解决方案1】:

SQLServer 2012新增的一个窗口函数是FIRST_VALUE,名字很直接,可以通过OVER子句进行分区,使用前需要将数据块中的每一列进行划分,一个块用于找到值时开始列。

With Block As (
  Select ID
       , Owner
       , OBlockID = SUM(Case When Owner Is Null Then 0 Else 1 End) 
                    OVER (ORDER BY ID)
       , Status
       , SBlockID = SUM(Case When Status Is Null Then 0 Else 1 End) 
                    OVER (ORDER BY ID)
       , Flag
       , FBlockID = SUM(Case When Flag Is Null Then 0 Else 1 End) 
                    OVER (ORDER BY ID)
  From   Task_log
)
Select ID
     , Owner = FIRST_VALUE(Owner) OVER (PARTITION BY OBlockID ORDER BY ID)
     , Status = FIRST_VALUE(Status) OVER (PARTITION BY SBlockID ORDER BY ID)
     , Flag = FIRST_VALUE(Flag) OVER (PARTITION BY FBlockID ORDER BY ID)
FROM   Block

SQLFiddle演示

UPDATE 查询很容易派生

【讨论】:

  • 你不需要FIRST_VALUE,一个简单的MIN(Owner) OVER (PARTITION BY OBlockID) 也可以。如果 SQL Server 支持 LEAD 的 Standard SQL IGNORE NULLS 选项,那将非常简单
【解决方案2】:

正如我在评论中提到的,我会尝试修复创建记录的过程,而不是修复垃圾数据。如果这不是一个选项,下面的代码应该会让你指向正确的方向。

UPDATE   t1
set      t1.owner = COALESCE(t1.owner, t2.owner),
         t1.Status = COALESCE(t1.status, t2.status),
         t1.Flag = COALESCE(t1.flag, t2.flag)
FROM     Task_log as t1
INNER JOIN    Task_log as t2
         ON t1.id = (t1.id + 1)
where    t1.owner is null 
         OR t1.status is null 
         OR t1.flag is null

【讨论】:

  • 如果表格中的数据有超过一个行间距,第二行仍会显示NULL,例如OP数据中ID为6的行将保持不变
【解决方案3】:

我能想到几种方法。

您可以将COALESCE 与数组聚合函数结合使用。不幸的是,它看起来不像 SQL Server 本身支持 array_agg (虽然有些 nice people have developed some workarounds)。

您还可以对每一列使用子选择。

SELECT id,
       (SELECT TOP 1 FROM (SELECT owner FROM ... WHERE id = outer_id AND owner IS NOT NULL order by ID desc )) AS owner,
       -- other columns

你也可以用窗口函数做一些事情。

【讨论】:

    【解决方案4】:

    一个普通的解决方案是:

    select id , owner , coalesce(owner, ( select owner from t t2 where id = (select max(id) from t t3 where id < t1.id and owner is not null)) ) as new_owner , flag , coalesce(flag, ( select flag from t t2 where id = (select max(id) from t t3 where id < t1.id and flag is not null)) ) as new_flag from t t1

    相当低效,但应该适用于大多数 DBMS

    【讨论】:

    • Lennart 你给我指出了正确的方向。这个解决方案对我有用:
    • 您为我指明了正确的方向。这个解决方案对我有用: declare @a numeric(3) declare @b numeric(30) declare @c varchar(20) declare @d varchar(1) ;WITH q AS ( select top 10000000* from [TABLE_NAME] order by [ FIELD1, FIELD2] ) 更新 q set @a = coalesce(id, @a), -- 设置 status_id 变量 @b = coalesce(owner_id, @b), @c = coalesce(status_id, @c), @d = coalesce (student_read_flag, @d), id = coalesce(id, @a), owner_id = coalesce(owner_id, @b), status_id = coalesce(assigned_to_employee_id, @c), student_read_flag = coalesce(student_read_flag, @d)
    猜你喜欢
    • 1970-01-01
    • 2019-12-10
    • 2021-10-24
    • 2020-12-08
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多