棘手的SQL。合并行

时间:2012-12-06 13:23:00

标签: sql sql-server sql-server-2008-r2

我有一个(在我看来)棘手的SQL问题。

我有一张订阅表。每个订阅都有一个ID和一组属性,这些属性会随着时间的推移而变化。当属性值更改时,将使用订阅密钥和新值创建新行 - 但仅适用于已更改的属性。未更改的属性的值保留为空。它看起来像这样(我省略了我用来正确排序结果的ValidTo和ValidFrom日期):

SubID  Att1  Att2   
 1      J
 1            L 
 1      B
 1            H
 1      A     H

我需要转换这个表,以便得到以下结果:

SubID  Att1  Att2   
 1      J     
 1      J     L
 1      B     L
 1      B     H
 1      A     H

所以基本上;如果属性为空,则取该属性的先前值。 任何解决方案都会......我的意思是我必须做什么才能得到结果:表格顶部的视图,创建新表格的SSIS包或第三个。

6 个答案:

答案 0 :(得分:1)

假设(基于您提到SSIS的事实),您可以使用OUTER APPLY来获取上一行:

DECLARE @T TABLE (SubID INT, Att1 CHAR(1), Att2 CHAR(2), ValidFrom DATETIME);
INSERT @T VALUES
    (1, 'J', '', '20121201'),
    (1, '', 'l', '20121202'),
    (1, 'B', '', '20121203'),
    (1, '', 'H', '20121204'),
    (1, 'A', 'H', '20121205');

SELECT  T.SubID,
        Att1 = COALESCE(NULLIF(T.att1, ''), prev.Att1, ''),
        Att2 = COALESCE(NULLIF(T.att2, ''), prev.Att2, '')
FROM    @T T
        OUTER APPLY
        (   SELECT  TOP 1 Att1, Att2
            FROM    @T prev
            WHERE   prev.SubID = T.SubID
            AND     prev.ValidFrom < t.ValidFrom
            ORDER BY ValidFrom DESC
        ) prev
ORDER BY T.ValidFrom;

(我必须为ValidFrom添加随机值以确保顺序正确)

修改

如果您有多个具有空值的连续行,则上述操作无效 - 例如

DECLARE @T TABLE (SubID INT, Att1 CHAR(1), Att2 CHAR(2), ValidFrom DATETIME);
INSERT @T VALUES
    (1, 'J', '', '20121201'),
    (1, '', 'l', '20121202'),
    (1, 'B', '', '20121203'),
    (1, '', 'H', '20121204'),
    (1, '', 'J', '20121205'),
    (1, 'A', 'H', '20121206');

如果可能发生这种情况,您将需要两个OUTER APPLY s:

SELECT  T.SubID,
        Att1 = COALESCE(NULLIF(T.att1, ''), prevAtt1.Att1, ''),
        Att2 = COALESCE(NULLIF(T.att2, ''), prevAtt2.Att2, '')
FROM    @T T
        OUTER APPLY
        (   SELECT  TOP 1 Att1
            FROM    @T prev
            WHERE   prev.SubID = T.SubID
            AND     prev.ValidFrom < t.ValidFrom
            AND     COALESCE(prev.Att1 , '') != ''
            ORDER BY ValidFrom DESC
        ) prevAtt1
        OUTER APPLY
        (   SELECT  TOP 1 Att2
            FROM    @T prev
            WHERE   prev.SubID = T.SubID
            AND     prev.ValidFrom < t.ValidFrom
            AND     COALESCE(prev.Att2 , '') != ''
            ORDER BY ValidFrom DESC
        ) prevAtt2
ORDER BY T.ValidFrom;

但是,由于每个OUTER APPLY只返回一个值,我会将其更改为相关的子查询,因为无论是否需要,上面都会为每一行评估PrevAtt1.Att1和'PrevAtt2.Att2'。但是,如果将其更改为:

SELECT  T.SubID,
        Att1 = COALESCE(
                    NULLIF(T.att1, ''), 
                    (   SELECT  TOP 1 Att1
                        FROM    @T prev
                        WHERE   prev.SubID = T.SubID
                        AND     prev.ValidFrom < t.ValidFrom
                        AND     COALESCE(prev.Att1 , '') != ''
                        ORDER BY ValidFrom DESC
                    ), ''),
        Att2 = COALESCE(
                    NULLIF(T.att2, ''), 
                    (   SELECT  TOP 1 Att2
                        FROM    @T prev
                        WHERE   prev.SubID = T.SubID
                        AND     prev.ValidFrom < t.ValidFrom
                        AND     COALESCE(prev.Att2 , '') != ''
                        ORDER BY ValidFrom DESC
                    ), '')
FROM    @T T
ORDER BY T.ValidFrom;

子查询仅在需要时(即,当Att1或Att2为空时)而不是每行进行评估。执行计划没有显示这一点,事实上后者的“实际执行计划”似乎更加密集,几乎肯定不会。但与往常一样,关键是测试,运行数据并查看哪些数据表现最佳,并检查IO统计信息以进行读取等。

答案 1 :(得分:1)

这个可以在oracle 11g中运行

  select  SUBID
         ,NVL(ATT1,LAG(ATT1) over(order by ValidTo)) ATT1
         ,NVL(ATT2,lag(ATT2) over(order by ValidTo)) ATT2 
  from table_name

我同意Gordon Linoff和Jack Douglas的说法。这个代码有限制,因为当插入多个带空值的记录时。   但是下面的代码将处理..

select SUBID
      ,NVL(ATT1,LAG(ATT1 ignore nulls) over(order by VALIDTO)) ATT1
      ,NVL(ATT2,LAG(ATT2 ignore nulls) over(order by VALIDTO)) ATT2
from Table_name

请看sql小提琴 http://sqlfiddle.com/#!4/3b530/4

答案 2 :(得分:1)

您可以使用相关子查询执行此操作:

select t.subid,
       (select t2.att1 from t t2 where t2.rowid <= t.rowid and t2.att1 is not null order by rowid desc limit 1) as att1,
       (select t2.att2 from t t2 where t2.rowid <= t.rowid and t2.att2 is not null order by rowid desc limit 1) as att1
from t

这假设您有一个rowid或等效项(例如创建的日期时间),它指定了行的顺序。它还使用limit来限制结果。在其他数据库中,这可能会使用top。 (而Oracle使用稍微复杂的表达式。)

我会用ValidTo写这个。但是,因为有ValidTo和ValidFrom,实际表达式要复杂得多。我需要提出这个问题,以澄清在其他时间使用这些值来规定值的规则。

答案 3 :(得分:0)

with Tricky1 as (
    Select SubID, Att1, Att2, row_number() over(order by ValidFrom) As rownum 
    From Tricky
)
select T1.SubID, T1.Att1, T2.Att2
from Tricky1 T1
cross join Tricky1 T2
where (ABS(T1.rownum-T2.rownum) = 1 or (T1.rownum = 1 and T2.rownum = 1))
and T1.Att1 is not null
;

另外,当SQL没有先前值的概念here.

时,请查看以前的值。

答案 4 :(得分:0)

我从未接触过SQL Server,但我读到它支持分析函数,就像Oracle一样。

> select * from MYTABLE order by ValidFrom;

     SUBID A A VALIDFROM
---------- - - -------------------
         1 J   2012-12-06 15:14:51
         2 j   2012-12-06 15:15:20
         1   L 2012-12-06 15:15:31
         2   l 2012-12-06 15:15:39
         1 B   2012-12-06 15:15:48
         2 b   2012-12-06 15:15:55
         1   H 2012-12-06 15:16:03
         2   h 2012-12-06 15:16:09
         1 A H 2012-12-06 15:16:20
         2 a h 2012-12-06 15:16:29


select
  t.SubID
 ,last_value(t.Att1 ignore nulls)over(partition by t.SubID order by t.ValidFrom rows between unbounded preceding and current row) as Att1
 ,last_value(t.Att2 ignore nulls)over(partition by t.SubID order by t.ValidFrom rows between unbounded preceding and current row) as Att2
 ,t.ValidFrom
from MYTABLE t;

     SUBID A A VALIDFROM
---------- - - -------------------
         1 J   2012-12-06 15:45:33
         1 J L 2012-12-06 15:45:41
         1 B L 2012-12-06 15:45:49
         1 B H 2012-12-06 15:45:58
         1 A H 2012-12-06 15:46:06
         2 j   2012-12-06 15:45:38
         2 j l 2012-12-06 15:45:44
         2 b l 2012-12-06 15:45:53
         2 b h 2012-12-06 15:46:02
         2 a h 2012-12-06 15:46:09

答案 5 :(得分:0)

我现在已经有一段时间了。我找到了一种相当简单的方法。不是最好的解决方案,因为我知道必须有其他方式,但在这里它。

我必须在2008R2中整合重复项。

因此,如果您可以尝试创建一个包含一组重复记录的表。

根据您的示例创建一个表,其中'ATT1'为空。然后在“SubId”上使用带内连接的更新查询来填充所需的数据