1

我有一个(在我看来)棘手的 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 包或第三个东西。

4

6 回答 6

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 添加随机值以确保 order by 正确)

编辑

如果您有多个具有空白值的连续行,则上述方法将不起作用 - 例如

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

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 统计信息等。

于 2012-12-06T13:36:03.047 回答
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 fiddle http://sqlfiddle.com/#!4/3b530/4

于 2012-12-06T14:15:27.790 回答
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,实际的表达要复杂得多。我需要这个问题来澄清在其他时间使用这些值来估算值的规则。

于 2012-12-06T14:16:34.333 回答
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。

于 2012-12-06T13:58:10.283 回答
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
于 2012-12-06T14:46:13.167 回答
0

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

在 2008R2 中,我也必须合并重复项。

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

根据您的示例,创建一个“ATT1”为空白的表。然后在“SubId”上使用带有内部联接的更新查询来填充您需要的数据

于 2014-04-28T13:46:25.687 回答