Уплощение таблицы из 1 строки в таблицу пар ключ-значение - PullRequest
9 голосов
/ 08 сентября 2011

Каков наилучший способ получить набор результатов пары ключ-значение, представляющий значение столбца в строке?

Учитывая следующую таблицу A только с одной строкой


<strong>Column1 Column2 Column3 </strong>...
Value1  Value2  Value3

Iхотите запросить его и вставить в другую таблицу B:


<strong>Key                  Value</strong>
Column1              Value1
Column2              Value2
Column3              Value3

Набор столбцов в таблице A заранее неизвестен.

ПРИМЕЧАНИЕ. Я рассматривал функции FOR XML и PIVOTа также динамический SQL, чтобы сделать что-то вроде этого:


    DECLARE @sql nvarchar(max)
    SET @sql = (SELECT STUFF((SELECT ',' + column_name 
                              FROM INFORMATION_SCHEMA.COLUMNS 
                              WHERE table_name='TableA' 
                              ORDER BY column_name FOR XML PATH('')), 1, 1, ''))
    SET @sql = 'SELECT ' + @sql + ' FROM TableA'
    EXEC(@sql)

Ответы [ 3 ]

18 голосов
/ 08 сентября 2011

Версия, в которой не задействована динамика. Если у вас есть имена столбцов, которые нельзя использовать в качестве имен элементов в XML, это не удастся.

select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
       T2.N.value('text()[1]', 'nvarchar(max)') as Value
from (select *
      from TableA
      for xml path(''), type) as T1(X)
  cross apply T1.X.nodes('/*') as T2(N)

Рабочий образец:

declare @T table
(
  Column1 varchar(10), 
  Column2 varchar(10), 
  Column3 varchar(10)
)

insert into @T values('V1','V2','V3')

select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
       T2.N.value('text()[1]', 'nvarchar(max)') as Value
from (select *
      from @T
      for xml path(''), type) as T1(X)
  cross apply T1.X.nodes('/*') as T2(N)

Результат:

Key                  Value
-------------------- -----
Column1              V1
Column2              V2
Column3              V3

Обновление

Для запроса с несколькими таблицами вы можете использовать for xml auto, чтобы получить имена таблиц в XML. Обратите внимание: если вы используете псевдоним для имен таблиц в запросе, вы получите вместо этого псевдоним.

select X2.N.value('local-name(..)', 'nvarchar(128)') as TableName,
       X2.N.value('local-name(.)', 'nvarchar(128)') as [Key],
       X2.N.value('text()[1]', 'nvarchar(max)') as Value
from (
     -- Your query starts here
     select T1.T1ID,
            T1.T1Col,
            T2.T2ID,
            T2.T2Col
     from T1
       inner join T2
         on T1.T1ID = T2.T1ID
     -- Your query ends here
     for xml auto, elements, type     
     ) as X1(X)
  cross apply X1.X.nodes('//*[text()]') as X2(N)

SQL Fiddle

6 голосов
/ 08 сентября 2011

Я думаю, что вы на полпути.Просто используйте UNPIVOT и dynamic SQL, как рекомендовал Мартин:

CREATE TABLE TableA (
  Code VARCHAR(10),
  Name VARCHAR(10),
  Details VARCHAR(10)
) 

INSERT TableA VALUES ('Foo', 'Bar', 'Baz') 
GO

DECLARE @sql nvarchar(max)
SET @sql = (SELECT STUFF((SELECT ',' + column_name 
                          FROM INFORMATION_SCHEMA.COLUMNS 
                          WHERE table_name='TableA' 
                          ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))

SET @sql = N'SELECT [Key], Val FROM (SELECT ' + @sql + ' FROM TableA) x '
+ 'UNPIVOT ( Val FOR [Key] IN (' + @sql + ')) AS unpiv'
EXEC (@sql)

Результаты :

Key          Val
------------ ------------
Code         Foo
Name         Bar
Details      Baz

Конечно, есть оговорка.Для работы приведенного выше кода все ваши столбцы должны быть одного типа данных.Если это не так, вы получите эту ошибку:

Msg 8167, Level 16, State 1, Line 1
The type of column "Col" conflicts with the type of 
other columns specified in the UNPIVOT list.

Чтобы обойти это, вам нужно создать два оператора строки столбца.Один для получения столбцов и один для приведения их всех в качестве типа данных для вашего столбца Val.

Для нескольких типов столбцов :

CREATE TABLE TableA (
  Code INT,
  Name VARCHAR(10),
  Details VARCHAR(10)
) 

INSERT TableA VALUES (1, 'Foo', 'Baf') 
GO

DECLARE 
  @sql nvarchar(max),
  @cols nvarchar(max),
  @conv nvarchar(max) 

SET @cols = (SELECT STUFF((SELECT ',' + column_name 
                          FROM INFORMATION_SCHEMA.COLUMNS 
                          WHERE table_name='TableA' 
                          ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))

SET @conv = (SELECT STUFF((SELECT ', CONVERT(VARCHAR(50), ' 
                          + column_name + ') AS ' + column_name
                          FROM INFORMATION_SCHEMA.COLUMNS 
                          WHERE table_name='TableA' 
                          ORDER BY ordinal_position FOR XML PATH('')), 1, 1, ''))


SET @sql = N'SELECT [Key], Val FROM (SELECT ' + @conv + ' FROM TableA) x '
+ 'UNPIVOT ( Val FOR [Key] IN (' + @cols + ')) AS unpiv'
EXEC (@sql)
3 голосов
/ 08 сентября 2011

Возможно, вы делаете это более сложным, чем нужно.Частично потому, что я не мог обернуть свой маленький мозг вокруг числа PIVOT / UNPIVOT / любых комбинаций, и динамический SQL "море красного" был бы необходим, чтобы осуществить это.Поскольку вы знаете, что в таблице ровно одна строка, получение значения для каждого столбца может быть просто подзапросом как часть набора UNION ed запросов.

DECLARE @sql NVARCHAR(MAX) = N'INSERT dbo.B([Key], Value) '

SELECT @sql += CHAR(13) + CHAR(10) 
        + ' SELECT [Key] = ''' + REPLACE(name, '''', '''''') + ''', 
        Value = (SELECT ' + QUOTENAME(name) + ' FROM dbo.A) UNION ALL'
FROM sys.columns 
WHERE [object_id] = OBJECT_ID('dbo.A');

SET @sql = LEFT(@sql, LEN(@sql)-9) + ';';

PRINT @sql;
-- EXEC sp_executesql @sql;

Результат (я создал только 4 столбца,но это будет работать для любого числа):

INSERT dbo.B([Key], Value)
 SELECT [Key] = 'Column1', 
        Value = (SELECT [Column1] FROM dbo.A) UNION ALL
 SELECT [Key] = 'Column2', 
        Value = (SELECT [Column2] FROM dbo.A) UNION ALL
 SELECT [Key] = 'Column3', 
        Value = (SELECT [Column3] FROM dbo.A) UNION ALL
 SELECT [Key] = 'Column4', 
        Value = (SELECT [Column4] FROM dbo.A);

Самая эффективная вещь в мире?Скорее всего нет.Но опять же, для однорядной таблицы и, надеюсь, одноразовой задачи, я думаю, что она будет работать нормально.Просто следите за именами столбцов, которые содержат апострофы, если вы разрешите эти вещи в вашем магазине ...

РЕДАКТИРОВАТЬ извините, не мог оставить это так.Теперь он будет обрабатывать апострофы в именах столбцов и других неоптимальных именах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...