Перебирать столбцы и перечислять все столбцы, в которых значение имеет запись - PullRequest
0 голосов
/ 19 марта 2020

У меня есть следующая таблица:

ID    Group   Col1    Col2    Col3    Col4    ... ColN
------------------------------------------------------
1     AAA     foo     bar
2     AAA     123     far             baz
3     BBB
4     CCC     345     123
5     AAA                     caz

Для каждого Group мне нужно выяснить, в каком Col умнсе оно имеет значение. Меня не волнуют сами значения.

Пример:

  • Group AAA содержит 3 ID s: 1, 2, 5. ID 1 имеет значение в Col1, Col2. ID 2 имеет значение в Col1, Col2. ID 5 имеет значение в Col3, итого, Group AAA имеет значения в Col1, Col2, Col3

Выходные данные должны быть такими (также перечислены Group s, которые имеют нет значения для любого Col umn как null:

Group    Cols
------------------------------
AAA      Col1; Col2; Col3;Col4
BBB      null
CCC      Col3

У меня есть сотни столбцов и сотни тысяч записей.

Может кто-нибудь помочь мне начать? не знаю, как я могу динамически перебирать все имена столбцов и перечислять их.

Ответы [ 3 ]

3 голосов
/ 19 марта 2020

Из-за комментария ОП о том, что у них 100 столбцов, это говорит о том, что им нужно динамическое c решение. Я закончил это решение так же, как OP прокомментировал, что они используют 2016, так что это будет не работать в 2016 году. Они OP должны будут преобразовать это в более старый метод FOR XML PATH и STUFF вместо использования STRING_AGG.

Кроме этого, это работает:

USE Sandbox;
GO


CREATE TABLE dbo.YourTable (ID int,
                            [Group] char(3),
                            Col1 char(3),
                            Col2 char(3),
                            Col3 char(3),
                            Col4 char(3));
GO

INSERT INTO dbo.YourTable
VALUES(1,'AAA','foo','bar',NULL,NULL),
      (2,'AAA','123','far',NULL,'baz'),
      (3,'BBB',NULL,NULL,NULL,NULL),
      (4,'CCC','345','123',NULL,NULL),
      (5,'AAA',NULL,NULL,'czx',NULL);
GO

--Hard coded example, to get the idea correct first
WITH UnPvt AS(
    SELECT DISTINCT
           YT.[Group],
           V.ColumnName
    FROM dbo.YourTable YT
         CROSS APPLY (VALUES(N'Col1',Col1),
                            (N'Col2',Col2),
                            (N'Col3',Col3),
                            (N'Col4',Col4))V(ColumnName,ColumnValue)
    WHERE V.ColumnValue IS NOT NULL)
SELECT YT.[Group],
       STRING_AGG(U.ColumnName,'; ') WITHIN GROUP (ORDER BY U.ColumnName) AS Cols
FROM (SELECT DISTINCT [Group] FROM dbo.YourTable) YT
      LEFT JOIN UnPvt U ON YT.[Group] = U.[Group]
GROUP BY YT.[group]

GO

--Dynamic Solution
DECLARE @SchemaName sysname = N'dbo',
        @TableName sysname = N'YourTable';

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE @Delimiter nvarchar(50) = N',' + @CRLF + N'                           ';

SET @SQL = N'WITH UnPvt AS(' + @CRLF +
           N'   SELECT DISTINCT' + @CRLF +
           N'          YT.[Group],' + @CRLF +
           N'          V.ColumnName' + @CRLF +
           N'   FROM dbo.YourTable YT' + @CRLF +
           N'        CROSS APPLY (VALUES' + 
           (SELECT STRING_AGG(N'(N' + QUOTENAME(c.[name],'''') + N',' + QUOTENAME(c.[name]) + N')',@Delimiter) WITHIN GROUP (ORDER BY C.[name])
            FROM sys.schemas s
                 JOIN sys.tables t ON s.schema_id = t.schema_id
                 JOIN sys.columns c ON t.object_id = c.OBJECT_ID
            WHERE s.[name] = @SchemaName
              AND t.[name] = @TableName
              AND C.[name] NOT IN (N'ID',N'Group')) + N')V(ColumnName,ColumnValue)' + @CRLF +
           N'    WHERE V.ColumnValue IS NOT NULL)' + @CRLF +
           N'SELECT YT.[Group],' + @CRLF +
           N'       STRING_AGG(U.ColumnName,''; '') WITHIN GROUP (ORDER BY U.ColumnName) AS Cols' + @CRLF +
           N'FROM (SELECT DISTINCT [Group] FROM dbo.YourTable) YT' + @CRLF +
           N'      LEFT JOIN UnPvt U ON YT.[Group] = U.[Group]' + @CRLF +
           N'GROUP BY YT.[group];';

PRINT @SQL;

EXEC sp_executesql @SQL;

GO

DROP TABLE dbo.YourTable;

DB <> Fiddle

Обратите внимание, что это предполагает что все столбцы (кроме ID и Group) также имеют одинаковый тип данных.

Редактировать: Вздох ... FOR XML PATH решение:

DECLARE @SchemaName sysname = N'dbo',
        @TableName sysname = N'YourTable';

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE @Delimiter nvarchar(50) = N',' + @CRLF + N'                           ';

SET @SQL = N'WITH UnPvt AS(' + @CRLF +
           N'   SELECT DISTINCT' + @CRLF +
           N'          YT.[Group],' + @CRLF +
           N'          V.ColumnName' + @CRLF +
           N'   FROM dbo.YourTable YT' + @CRLF +
           N'        CROSS APPLY (VALUES' + 
           STUFF((SELECT @Delimiter + N'(N' + QUOTENAME(c.[name],'''') + N',' + QUOTENAME(c.[name]) + N')'
                  FROM sys.schemas s
                       JOIN sys.tables t ON s.schema_id = t.schema_id
                       JOIN sys.columns c ON t.object_id = c.OBJECT_ID
                  WHERE s.[name] = @SchemaName
                    AND t.[name] = @TableName
                    AND C.[name] NOT IN (N'ID',N'Group')
                  ORDER BY c.[name]
                  FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,DATALENGTH(@Delimiter)/2,N'') + N')V(ColumnName,ColumnValue)' + @CRLF +
           N'    WHERE V.ColumnValue IS NOT NULL)' + @CRLF +
           N'SELECT YT.[Group],' + @CRLF +
           N'       STUFF((SELECT N''; '' + ColumnName' + @CRLF +
           N'              FROM UnPvt U' + @CRLF +
           N'              WHERE U.[Group] = YT.[Group]' + @CRLF +
           N'              ORDER BY U.ColumnName' + @CRLF +
           N'              FOR XML PATH(''''),TYPE).value(''.'',''nvarchar(MAX)''),1,2,N'''') AS Cols' + @CRLF +
           N'FROM (SELECT DISTINCT [Group] FROM dbo.YourTable) YT' + @CRLF +
           N'GROUP BY YT.[group];';

PRINT @SQL;

EXEC sp_executesql @SQL;
1 голос
/ 19 марта 2020

Производительность будет плохой, но это работает в общем:

DECLARE @mockupTable TABLE (ID INT,[Group] VARCHAR(10),Col1 VARCHAR(10),Col2 VARCHAR(10),Col3 VARCHAR(10),Col4 VARCHAR(10));
INSERT INTO @mockupTable VALUES
 (1,'AAA','foo','bar',NULL,NULL)
,(2,'AAA','123','far',NULL,'baz')
,(3,'BBB',NULL,NULL,NULL,NULL)
,(4,'CCC','345','123',NULL,NULL)
,(5,'AAA',NULL,NULL,'caz',NULL);

- запрос

SELECT rw.query('for $n in * return concat(",",local-name($n))').value('.','nvarchar(max)')
FROM
(
SELECT * 
FROM @mockupTable t
FOR XML PATH('row'),TYPE
) A(x)
CROSS APPLY A.x.nodes('/row') B(rw);

Идея вкратце:

Преобразуем таблицу до XML. Использование SELECT * приведет к XML с именованными элементами. Это хук для общего c сцепления с именами столбцов.

Второе, что нужно знать: по умолчанию значения NULL будут по умолчанию опущены в XML.

Результат отражает «заполненные» столбцы.

Промежуточное значение XML выглядит следующим образом (представлены только ненулевые значения):

<row>
  <ID>1</ID>
  <Group>AAA</Group>
  <Col1>foo</Col1>
  <Col2>bar</Col2>
</row>
<row>
  <ID>2</ID>
  <Group>AAA</Group>
  <Col1>123</Col1>
  <Col2>far</Col2>
  <Col4>baz</Col4>
</row>
<row>
  <ID>3</ID>
  <Group>BBB</Group>
</row>
<row>
  <ID>4</ID>
  <Group>CCC</Group>
  <Col1>345</Col1>
  <Col2>123</Col2>
</row>
<row>
  <ID>5</ID>
  <Group>AAA</Group>
  <Col3>caz</Col3>
</row>

ОБНОВЛЕНИЕ Ближе к вашему ожидаемый результат

Попробуйте получить идентификатор и сгруппировать ваш результат

SELECT  rw.value('(ID/text())[1]','int') ID
       ,rw.value('(Group/text())[1]','varchar(10)') [Group]
       ,rw.query('for $n in *[local-name() ne "ID" and local-name() ne "Group"] return concat(",",local-name($n))').value('.','nvarchar(max)')
FROM
(
SELECT * 
FROM @mockupTable t
FOR XML PATH('row'),TYPE
) A(x)
CROSS APPLY A.x.nodes('/row') B(rw);

Результат

ID  Group   usedColumns
1   AAA     ,Col1 ,Col2
2   AAA     ,Col1 ,Col2 ,Col4
3   BBB 
4   CCC     ,Col1 ,Col2
5   AAA     ,Col3

ОБНОВЛЕНИЕ 2 Ваш сгруппированный результат

Вы можно попробовать это, чтобы получить сгруппированный результат полностью

SELECT C.gr.value('text()[1]','varchar(100)')
      ,C.gr.query('for $n in row/*[local-name() ne "ID" and local-name() ne "Group"] 
                   return <n>{local-name($n)}</n>')
           .query('for $n in distinct-values(n/text()) 
                   return concat(",",$n)')
           .value('.','nvarchar(max)')
FROM
(
SELECT * 
FROM @mockupTable t
FOR XML PATH('row'),TYPE
) A(x)
CROSS APPLY (SELECT A.x.query('for $gr in distinct-values(/row/Group/text())
                               return <gr>{$gr}{/row[Group=$gr]}</gr>
                               ')) B(gr)
CROSS APPLY B.gr.nodes('/gr') C(gr);

Результат

AAA     ,Col1 ,Col2 ,Col3 ,Col4
BBB     
CCC     ,Col1 ,Col2
1 голос
/ 19 марта 2020

Один метод использует concat_ws():

select t.grp,
       concat_ws(',',
                 (case when max(col1) is not null then 'col1' end),
                 (case when max(col2) is not null then 'col2' end),
                 . . .  -- fill in the logic for the rest of the columns
                ) as columns
from t
group by grp;

Примечание: concat_ws() был представлен в SQL Server 2017. Вы можете сделать что-то подобное в более старых версиях.

...