Sql server 将记录展平为一行,其中的值不为null

Sql server 将记录展平为一行,其中的值不为null,sql-server,tsql,aggregate,sql-server-2016,flatten,Sql Server,Tsql,Aggregate,Sql Server 2016,Flatten,我知道有很多扁平化的问题,但它们似乎不符合这个要求 给定一个包含4列的数据表,其中所有列都可以有null,我需要能够指定一个特定的深度值,并返回一条记录,该记录将在该深度处搜索,并向下搜索1以填补空白 COL1 COL2 COL3 COL4 DEPTH --------- ------------- ------------ ------ ----------- NULL NULL Manager NULL

我知道有很多扁平化的问题,但它们似乎不符合这个要求

给定一个包含4列的数据表,其中所有列都可以有null,我需要能够指定一个特定的深度值,并返回一条记录,该记录将在该深度处搜索,并向下搜索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时,应返回:

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将忽略空值。代码将按降序对列表进行排序。使用XQuery获取第一个值将返回最顶层的非空值。

我非常喜欢Shnugo的解决方案,但当我看到它时,我想出了另一个解决方案,无可否认,更麻烦的解决方案——使用了几个常见的表表达式和条件聚合——所以我在这里发布它,只是因为我不想让我花在它上面的时间感觉像是一个累赘:

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。。。等等。
最后一个选项只选择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;

提示:去掉前1项,观察列表的逐步填充

非常感谢您提出的清晰的问题,包括完整的、您自己的尝试和预期的结果。应该是这样的+1从我的角度来看,我试着,有时候很难描述所有的事情,有时候你也不总是能找到一个最简单的例子。我从来都不明白为什么人们仍然对我投反对票,我希望他们能花点时间让我知道,这样我才能进步。尽管如此,谢谢你的评论:这就是我要找的那个,我把我的尝试和你的相匹配,并且惊讶地看到我离你有多近。是XQuery让我有点绊倒了,我知道XML默认情况下会忽略null,我本应该利用它。谢谢你!哦,我相信你在WITH中的第一次选择是多余的。@Storm关于多余的选择,你是对的。实际上,这有点反直觉,但最好不要读。我改变了这一点…我总是喜欢得到你的见解佐哈尔,感谢你抽出时间!很高兴我能帮上忙:-哈哈,我也是从CTE开始的,这感觉是很自然的第一选择,但有一点我不得不退一步说****太复杂了,必须有一个更简单的方法:P