假设我的SQL Server(2012)数据库中有以下“值”表:
表1:
Id Col1 Col2 Col3 Col4
我想创建第二个“覆盖”表,它将存储值以覆盖原始值,以防用户需要这样做。因此,根据上表,覆盖表将如下所示:
覆盖:
FK_Id Col1 Col2 Col3 Col4 When_Inserted
Overrides.FK_Id
将Table1.Id
作为外键引用。
因此,例如,假设我的Overrides
表中包含以下行,并在Table1
中使用Id=1
覆盖行:
FK_Id: Col1: Col2: Col3: Col4: When_Inserted:
1 Val1_1 Val2_1 Expected_Val3 NULL 1-Jan
1 NULL Val2_2 NULL NULL 2-Jan
1 NULL Expected_Val2 NULL NULL 3-Jan
1 Expected_Val1 NULL NULL NULL 4-Jan
然后,基于When_Inserted
列 - 希望最新的插入优先,我希望覆盖如下:
FK_Id: Col1: Col2: Col3: Col4:
1 Expected_Val1 Expected_Val2 Expected_Val3 NULL
我正在尝试一种智能的方法来创建这个SQL,并提出了一个相当丑陋的解决方案:
SELECT
FK_Id
,(
SELECT TOP 1
Col1
FROM
Overrides O1
WHERE
Col1 IS NOT NULL
AND O1.FK_Id = O.FK_Id
ORDER BY
O1.When_Inserted DESC
) Col1
.... <same for each of the other columns> ....
FROM
Overrides O
GROUP BY
FK_Id
我确信必须有更好的方式,更清洁,更有效率。
答案 0 :(得分:4)
使用common table expression row_number()
(最新的第一个),cross apply()
取消对列的拆分,过滤每列的最新内容(rn = 1
),最后{{ 3}}回到相同的形式:
;with cte as (
select o.fk_id, v.Col, v.Value, o.When_Inserted
, rn = row_number() over (partition by o.fk_id, v.col order by o.when_inserted desc)
from overrides o
cross apply (values('Col1',Col1),('Col2',Col2),('Col3',Col3),('Col4',Col4)
) v (Col,Value)
where v.value is not null
)
select fk_id, col1, col2, col3, col4
from (
select fk_id, col, value
from cte
where rn = 1
) s
pivot (max(Value) for Col in (col1,col2,col3,col4)) p
rextester演示:pivot()
返回:
+-------+---------------+---------------+---------------+------+
| fk_id | col1 | col2 | col3 | col4 |
+-------+---------------+---------------+---------------+------+
| 1 | Expected_Val1 | Expected_Val2 | Expected_Val3 | NULL |
+-------+---------------+---------------+---------------+------+
查看样本的io统计数据:
unpivot / pivot版本:
Table 'Worktable'. Scan count 0, logical reads 0
Table 'overrides'. Scan count 1, logical reads 1
first_value over()
版本:
Table 'Worktable'. Scan count 20, logical reads 100
Table 'overrides'. Scan count 1, logical reads 1
select top 1
子查询版本:
Table 'overrides'. Scan count 5, logical reads 5
Table 'Worktable'. Scan count 0, logical reads 0
答案 1 :(得分:1)
您可以使用first_value()
:
select distinct fkid,
first_value(col1) over (partition by fkid
order by (case when col1 is not null then 1 else 2 end),
when_inserted desc
) as col1,
first_value(col2) over (partition by fkid
order by (case when col2 is not null then 1 else 2 end),
when_inserted desc
) as col2,
. . .
from t;
select distinct
是因为SQL Server没有与聚合函数相同的功能。
答案 2 :(得分:0)
看我的解决方案是完全不同的。
IMHO
,我的脚本performance
会更好,只要它在所有correct output
中提供sample data
。
我在我的脚本中使用了自动生成的ID,但如果您没有身份ID,则可以使用ROW_NUMBER
。我的剧本很容易理解。
declare @t table(id int identity(1,1),FK_Id int,Col1 varchar(50),Col2 varchar(50)
,Col3 varchar(50),Col4 varchar(50),When_Inserted date)
insert into @t VALUES
(1 ,'Val1_1' ,'Val2_1' ,'Expected_Val3', NULL , '2017-01-1')
,(1 ,NULL ,'Val2_2' , NULL , NULL, '2017-01-2')
,(1 ,NULL ,'Expected_Val2', NULL , NULL, '2017-01-3')
,(1 ,'Expected_Val1' , NULL , NULL , NULL, '2017-01-4')
;
WITH CTE
AS (
SELECT *
,CASE
WHEN col1 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col1
)
END col1Code
,CASE
WHEN col2 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col2
)
END col2Code
,CASE
WHEN col3 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col3
)
END col3Code
,CASE
WHEN col4 IS NULL
THEN NULL
ELSE CONCAT (
cast(id AS VARCHAR(10))
,'_'
,col4
)
END col4Code
FROM @t
)
,CTE1
AS (
SELECT FK_Id
,max(col1Code) col1Code
,max(col2Code) col2Code
,max(col3Code) col3Code
,max(col4Code) col4Code
FROM cte
GROUP BY FK_Id
)
SELECT FK_Id
,SUBSTRING(col1Code, charindex('_', col1Code) + 1, len(col1Code)) col1Code
,SUBSTRING(col2Code, charindex('_', col2Code) + 1, len(col2Code)) col2Code
,SUBSTRING(col3Code, charindex('_', col3Code) + 1, len(col2Code)) col3Code
,SUBSTRING(col4Code, charindex('_', col4Code) + 1, len(col4Code)) col4Code
FROM cte1 c1