我有一个(在我看来)棘手的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包或第三个。
答案 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”上使用带内连接的更新查询来填充所需的数据