Свести записи в одну строку, где значения не равны нулю - PullRequest
2 голосов
/ 05 марта 2020

Я знаю, что есть куча "сплющенных" вопросов, но они, кажется, не соответствуют этому требованию.

Учитывая таблицу данных с 4 столбцами, все из которых могут иметь нулевые, мне нужно быть в состоянии указать заданное значение глубины c и получить обратно одну запись, которая будет искать на этой глубине и ниже (в направлении 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 );

Есть ли лучший способ получить данные? Я попробовал несколько вещей, но больше ничего не получалось, не говоря уже о том, чтобы лучше.

Ответы [ 3 ]

2 голосов
/ 05 марта 2020

Вы можете использовать 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 для извлечения самого первого значения вернет самое верхнее ненулевое значение.

1 голос
/ 05 марта 2020

Я добавлю это в качестве другого ответа, поскольку оно следует совершенно другой идее:

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, чтобы наблюдать постепенное «заполнение» списка.

1 голос
/ 05 марта 2020

Мне действительно нравится решение Shnu go, но к тому времени, когда я его увидел, я нашел другое, по общему признанию, более громоздкое решение - использование пары общих табличных выражений и условное агрегирование - так что я разместив его здесь просто потому, что я не хочу, чтобы время, которое я потратил на него, выглядело как общая талия:

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 et c 'будет содержать 0, 1, 2 ... et c'.
Финал выбирает только те значения, где S1, S2 et c 'равно 1 - это первое ненулевое значение каждого столбца.

...