将记录展平为单行,其中值不为空
Flatten records into a single row where values are not null
我知道有一堆 "Flatten" 个问题,但它们似乎不符合这个要求。
给定一个 table 包含 4 列的数据,所有列都可以有空值,我需要能够指定一个特定的深度值并返回一条记录,该记录将在该深度和更低的深度进行搜索(朝着 1)填补空白。
COL1 COL2 COL3 COL4 DEPTH
--------- ------------- ------------ ------ -----------
NULL NULL Manager NULL 9
NULL NULL NULL NULL 8
Jack NULL NULL 36 7
NULL NULL Employed 28 6
James NULL NULL 15 5
NULL Ericson NULL NULL 4
NULL NULL NULL 23 3
Jack NULL NULL NULL 2
John Smith Unemployed 45 1
例如请求深度为 5,应该 return:
COL1 COL2 COL3 COL4 DEPTH
--------- ----------- -------------- ------ -----
James Ericson Unemployed 15 5
示例设置:
DECLARE @Table TABLE
(
[COL1] varchar(30) NULL,
[COL2] varchar(30) NULL,
[COL3] varchar(30) NULL,
[COL4] varchar(30) NULL,
[DEPTH] int NOT NULL
);
INSERT INTO @Table
SELECT Null , Null , 'Manager' , Null , 9 UNION ALL
SELECT Null , Null , Null , Null , 8 UNION ALL
SELECT 'Jack' , Null , Null , '36' , 7 UNION ALL
SELECT Null , Null , 'Employed' , '28' , 6 UNION ALL
SELECT 'James' , Null , Null , '15' , 5 UNION ALL
SELECT Null , 'Ericson' , Null , Null , 4 UNION ALL
SELECT Null , Null , Null , '23' , 3 UNION ALL
SELECT 'Jack' , Null , Null , Null , 2 UNION ALL
SELECT 'John' , 'Smith' , 'Unemployed' , '45' , 1;
SELECT * FROM @Table ORDER BY DEPTH DESC;
当前工作代码:
DECLARE @Depth int = 5;
SELECT
[COL1] = ( SELECT TOP(1) [COL1] FROM @Table WHERE [DEPTH] <= @Depth AND [COL1] IS NOT Null ORDER BY DEPTH DESC ),
[COL2] = ( SELECT TOP(1) [COL2] FROM @Table WHERE [DEPTH] <= @Depth AND [COL2] IS NOT Null ORDER BY DEPTH DESC ),
[COL3] = ( SELECT TOP(1) [COL3] FROM @Table WHERE [DEPTH] <= @Depth AND [COL3] IS NOT Null ORDER BY DEPTH DESC ),
[COL4] = ( SELECT TOP(1) [COL4] FROM @Table WHERE [DEPTH] <= @Depth AND [COL4] IS NOT Null ORDER BY DEPTH DESC );
是否有更好的方法来检索数据?我尝试了一些方法,但没有其他方法有效,更不用说更好了。
您可能会使用 XML 技巧:
DECLARE @tbl TABLE(COL1 VARCHAR(100),COL2 VARCHAR(100),COL3 VARCHAR(100),COL4 INT,DEPTH INT);
INSERT INTO @tbl VALUES
(NULL,NULL ,'Manager',NULL,9)
,(NULL,NULL ,NULL ,NULL,8)
,('Jack',NULL ,NULL ,36 ,7)
,(NULL,NULL ,'Employed',28 ,6)
,('James',NULL ,NULL ,15 ,5)
,(NULL,'Ericson',NULL ,NULL,4)
,(NULL,NULL ,NULL ,23 ,3)
,('Jack',NULL ,NULL ,NULL,2)
,('John','Smith' ,'Unemployed',45 ,1);
DECLARE @dpth INT=5;
WITH DataAsXml(TheXml) AS
(
SELECT t.*
FROM @tbl t
WHERE t.DEPTH<=@dpth
ORDER BY t.DEPTH DESC
FOR XML PATH('row'),TYPE
)
SELECT TheXml.value('(/row/COL1/text())[1]','varchar(100)') AS COL1
,TheXml.value('(/row/COL2/text())[1]','varchar(100)') AS COL2
,TheXml.value('(/row/COL3/text())[1]','varchar(100)') AS COL3
,TheXml.value('(/row/COL4/text())[1]','int') AS COL4
,TheXml.value('(/row/DEPTH/text())[1]','int') AS DEPTH
FROM DataAsXml;
中级 XML 看起来像这样:
<row>
<COL1>James</COL1>
<COL4>15</COL4>
<DEPTH>5</DEPTH>
</row>
<row>
<COL2>Ericson</COL2>
<DEPTH>4</DEPTH>
</row>
<row>
<COL4>23</COL4>
<DEPTH>3</DEPTH>
</row>
<row>
<COL1>Jack</COL1>
<DEPTH>2</DEPTH>
</row>
<row>
<COL1>John</COL1>
<COL2>Smith</COL2>
<COL3>Unemployed</COL3>
<COL4>45</COL4>
<DEPTH>1</DEPTH>
</row>
如您所见,XML 默认情况下将忽略 NULL 值。该代码将按 降序 顺序对列表进行排序。使用 XQuery 获取第一个值 将return 最顶部的非空值。
我真的很喜欢 Shnugo 的解决方案,但当我看到它时,我想到了另一个,公认的更麻烦的解决方案 - 使用几个常见的 table 表达式和条件聚合 - 所以我'我把它贴在这里只是因为我不想让我花在它上面的时间感觉像一个完整的腰部:
DECLARE @Depth int = 5;
WITH CTE1 AS
(
SELECT COL1, COL2, COL3, COL4, DEPTH,
SIGN(SUM(IIF(COL1 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull1,
SIGN(SUM(IIF(COL2 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull2,
SIGN(SUM(IIF(COL3 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull3,
SIGN(SUM(IIF(COL4 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull4
FROM @Table
WHERE DEPTH <= @Depth
), CTE2 AS
(
SELECT COL1, COL2, COL3, COL4, DEPTH,
SUM(NonNull1) OVER(ORDER BY Depth DESC) As S1,
SUM(NonNull2) OVER(ORDER BY Depth DESC) As S2,
SUM(NonNull3) OVER(ORDER BY Depth DESC) As S3,
SUM(NonNull4) OVER(ORDER BY Depth DESC) As S4
FROM CTE1
)
SELECT MAX(IIF(S1 = 1, Col1, NULL)) As Col1,
MAX(IIF(S2 = 1, Col2, NULL)) As Col2,
MAX(IIF(S3 = 1, Col3, NULL)) As Col3,
MAX(IIF(S4 = 1, Col4, NULL)) As Col4,
MAX(DEPTH) As Depth
FROM CTE2
第一个 cte 添加包含 0 的列,直到出现第一个非空值,然后是 1。
第二个 cte 对这些列求和,因此 S1、S2 等'将包含 0、1、2...等'。
final 仅选择 S1、S2 等等于 1 的值 - 这是每列的第一个非空值。
我将把它添加为另一个答案,因为它遵循一个完全不同的想法:
DECLARE @tbl TABLE(COL1 VARCHAR(100),COL2 VARCHAR(100),COL3 VARCHAR(100),COL4 INT,DEPTH INT);
INSERT INTO @tbl VALUES
(NULL,NULL ,'Manager',NULL,9)
,(NULL,NULL ,NULL ,NULL,8)
,('Jack',NULL ,NULL ,36 ,7)
,(NULL,NULL ,'Employed',28 ,6)
,('James',NULL ,NULL ,15 ,5)
,(NULL,'Ericson',NULL ,NULL,4)
,(NULL,NULL ,NULL ,23 ,3)
,('Jack',NULL ,NULL ,NULL,2)
,('John','Smith' ,'Unemployed',45 ,1);
DECLARE @dpth INT=5;
--递归 CTE 将遍历行以获取(并保持)第一个非空值:
WITH cte AS
(
SELECT t.*
,ROW_NUMBER() OVER(ORDER BY t.DEPTH DESC) AS RowIndex
FROM @tbl t
WHERE t.DEPTH<=@dpth
)
,recCTE AS
(
SELECT 1 AS rwInx, cte.* FROM cte WHERE RowIndex=1
UNION ALL
SELECT rc.rwInx+1
,ISNULL(rc.COL1,c.COL1) AS COL1
,ISNULL(rc.COL2,c.COL2) AS COL2
,ISNULL(rc.COL3,c.COL3) AS COL3
,ISNULL(rc.COL4,c.COL4) AS COL4
,ISNULL(rc.DEPTH,c.DEPTH) AS DEPTH
,c.RowIndex
FROM cte c
INNER JOIN recCTE rc ON c.RowIndex=rc.rwInx+1
)
SELECT TOP 1 *
FROM recCTE
ORDER BY RowIndex DESC;
提示:去掉TOP 1
观察列表的递进"filling"。
我知道有一堆 "Flatten" 个问题,但它们似乎不符合这个要求。
给定一个 table 包含 4 列的数据,所有列都可以有空值,我需要能够指定一个特定的深度值并返回一条记录,该记录将在该深度和更低的深度进行搜索(朝着 1)填补空白。
COL1 COL2 COL3 COL4 DEPTH
--------- ------------- ------------ ------ -----------
NULL NULL Manager NULL 9
NULL NULL NULL NULL 8
Jack NULL NULL 36 7
NULL NULL Employed 28 6
James NULL NULL 15 5
NULL Ericson NULL NULL 4
NULL NULL NULL 23 3
Jack NULL NULL NULL 2
John Smith Unemployed 45 1
例如请求深度为 5,应该 return:
COL1 COL2 COL3 COL4 DEPTH
--------- ----------- -------------- ------ -----
James Ericson Unemployed 15 5
示例设置:
DECLARE @Table TABLE
(
[COL1] varchar(30) NULL,
[COL2] varchar(30) NULL,
[COL3] varchar(30) NULL,
[COL4] varchar(30) NULL,
[DEPTH] int NOT NULL
);
INSERT INTO @Table
SELECT Null , Null , 'Manager' , Null , 9 UNION ALL
SELECT Null , Null , Null , Null , 8 UNION ALL
SELECT 'Jack' , Null , Null , '36' , 7 UNION ALL
SELECT Null , Null , 'Employed' , '28' , 6 UNION ALL
SELECT 'James' , Null , Null , '15' , 5 UNION ALL
SELECT Null , 'Ericson' , Null , Null , 4 UNION ALL
SELECT Null , Null , Null , '23' , 3 UNION ALL
SELECT 'Jack' , Null , Null , Null , 2 UNION ALL
SELECT 'John' , 'Smith' , 'Unemployed' , '45' , 1;
SELECT * FROM @Table ORDER BY DEPTH DESC;
当前工作代码:
DECLARE @Depth int = 5;
SELECT
[COL1] = ( SELECT TOP(1) [COL1] FROM @Table WHERE [DEPTH] <= @Depth AND [COL1] IS NOT Null ORDER BY DEPTH DESC ),
[COL2] = ( SELECT TOP(1) [COL2] FROM @Table WHERE [DEPTH] <= @Depth AND [COL2] IS NOT Null ORDER BY DEPTH DESC ),
[COL3] = ( SELECT TOP(1) [COL3] FROM @Table WHERE [DEPTH] <= @Depth AND [COL3] IS NOT Null ORDER BY DEPTH DESC ),
[COL4] = ( SELECT TOP(1) [COL4] FROM @Table WHERE [DEPTH] <= @Depth AND [COL4] IS NOT Null ORDER BY DEPTH DESC );
是否有更好的方法来检索数据?我尝试了一些方法,但没有其他方法有效,更不用说更好了。
您可能会使用 XML 技巧:
DECLARE @tbl TABLE(COL1 VARCHAR(100),COL2 VARCHAR(100),COL3 VARCHAR(100),COL4 INT,DEPTH INT);
INSERT INTO @tbl VALUES
(NULL,NULL ,'Manager',NULL,9)
,(NULL,NULL ,NULL ,NULL,8)
,('Jack',NULL ,NULL ,36 ,7)
,(NULL,NULL ,'Employed',28 ,6)
,('James',NULL ,NULL ,15 ,5)
,(NULL,'Ericson',NULL ,NULL,4)
,(NULL,NULL ,NULL ,23 ,3)
,('Jack',NULL ,NULL ,NULL,2)
,('John','Smith' ,'Unemployed',45 ,1);
DECLARE @dpth INT=5;
WITH DataAsXml(TheXml) AS
(
SELECT t.*
FROM @tbl t
WHERE t.DEPTH<=@dpth
ORDER BY t.DEPTH DESC
FOR XML PATH('row'),TYPE
)
SELECT TheXml.value('(/row/COL1/text())[1]','varchar(100)') AS COL1
,TheXml.value('(/row/COL2/text())[1]','varchar(100)') AS COL2
,TheXml.value('(/row/COL3/text())[1]','varchar(100)') AS COL3
,TheXml.value('(/row/COL4/text())[1]','int') AS COL4
,TheXml.value('(/row/DEPTH/text())[1]','int') AS DEPTH
FROM DataAsXml;
中级 XML 看起来像这样:
<row>
<COL1>James</COL1>
<COL4>15</COL4>
<DEPTH>5</DEPTH>
</row>
<row>
<COL2>Ericson</COL2>
<DEPTH>4</DEPTH>
</row>
<row>
<COL4>23</COL4>
<DEPTH>3</DEPTH>
</row>
<row>
<COL1>Jack</COL1>
<DEPTH>2</DEPTH>
</row>
<row>
<COL1>John</COL1>
<COL2>Smith</COL2>
<COL3>Unemployed</COL3>
<COL4>45</COL4>
<DEPTH>1</DEPTH>
</row>
如您所见,XML 默认情况下将忽略 NULL 值。该代码将按 降序 顺序对列表进行排序。使用 XQuery 获取第一个值 将return 最顶部的非空值。
我真的很喜欢 Shnugo 的解决方案,但当我看到它时,我想到了另一个,公认的更麻烦的解决方案 - 使用几个常见的 table 表达式和条件聚合 - 所以我'我把它贴在这里只是因为我不想让我花在它上面的时间感觉像一个完整的腰部:
DECLARE @Depth int = 5;
WITH CTE1 AS
(
SELECT COL1, COL2, COL3, COL4, DEPTH,
SIGN(SUM(IIF(COL1 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull1,
SIGN(SUM(IIF(COL2 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull2,
SIGN(SUM(IIF(COL3 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull3,
SIGN(SUM(IIF(COL4 IS NULL, 0, 1)) OVER(ORDER BY Depth DESC)) AS NonNull4
FROM @Table
WHERE DEPTH <= @Depth
), CTE2 AS
(
SELECT COL1, COL2, COL3, COL4, DEPTH,
SUM(NonNull1) OVER(ORDER BY Depth DESC) As S1,
SUM(NonNull2) OVER(ORDER BY Depth DESC) As S2,
SUM(NonNull3) OVER(ORDER BY Depth DESC) As S3,
SUM(NonNull4) OVER(ORDER BY Depth DESC) As S4
FROM CTE1
)
SELECT MAX(IIF(S1 = 1, Col1, NULL)) As Col1,
MAX(IIF(S2 = 1, Col2, NULL)) As Col2,
MAX(IIF(S3 = 1, Col3, NULL)) As Col3,
MAX(IIF(S4 = 1, Col4, NULL)) As Col4,
MAX(DEPTH) As Depth
FROM CTE2
第一个 cte 添加包含 0 的列,直到出现第一个非空值,然后是 1。
第二个 cte 对这些列求和,因此 S1、S2 等'将包含 0、1、2...等'。
final 仅选择 S1、S2 等等于 1 的值 - 这是每列的第一个非空值。
我将把它添加为另一个答案,因为它遵循一个完全不同的想法:
DECLARE @tbl TABLE(COL1 VARCHAR(100),COL2 VARCHAR(100),COL3 VARCHAR(100),COL4 INT,DEPTH INT);
INSERT INTO @tbl VALUES
(NULL,NULL ,'Manager',NULL,9)
,(NULL,NULL ,NULL ,NULL,8)
,('Jack',NULL ,NULL ,36 ,7)
,(NULL,NULL ,'Employed',28 ,6)
,('James',NULL ,NULL ,15 ,5)
,(NULL,'Ericson',NULL ,NULL,4)
,(NULL,NULL ,NULL ,23 ,3)
,('Jack',NULL ,NULL ,NULL,2)
,('John','Smith' ,'Unemployed',45 ,1);
DECLARE @dpth INT=5;
--递归 CTE 将遍历行以获取(并保持)第一个非空值:
WITH cte AS
(
SELECT t.*
,ROW_NUMBER() OVER(ORDER BY t.DEPTH DESC) AS RowIndex
FROM @tbl t
WHERE t.DEPTH<=@dpth
)
,recCTE AS
(
SELECT 1 AS rwInx, cte.* FROM cte WHERE RowIndex=1
UNION ALL
SELECT rc.rwInx+1
,ISNULL(rc.COL1,c.COL1) AS COL1
,ISNULL(rc.COL2,c.COL2) AS COL2
,ISNULL(rc.COL3,c.COL3) AS COL3
,ISNULL(rc.COL4,c.COL4) AS COL4
,ISNULL(rc.DEPTH,c.DEPTH) AS DEPTH
,c.RowIndex
FROM cte c
INNER JOIN recCTE rc ON c.RowIndex=rc.rwInx+1
)
SELECT TOP 1 *
FROM recCTE
ORDER BY RowIndex DESC;
提示:去掉TOP 1
观察列表的递进"filling"。