SQL Server: столбцы для строк

Ищем элегантное (или любое) решение для преобразования столбцов в строки.

Вот пример: у меня есть таблица со следующей схемой:

[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150]

Вот что я хочу получить в результате:

[ID] [EntityId] [IndicatorName] [IndicatorValue]

И значения результата будут:

1 1 'Indicator1' 'Value of Indicator 1 for entity 1'
2 1 'Indicator2' 'Value of Indicator 2 for entity 1'
3 1 'Indicator3' 'Value of Indicator 3 for entity 1'
4 2 'Indicator1' 'Value of Indicator 1 for entity 2'

И так далее..

Это имеет смысл? У вас есть предложения по тому, где искать и как это сделать в T-SQL?

Ответ 1

Вы можете использовать функцию UNPIVOT для преобразования столбцов в строки:

select id, entityId,
  indicatorname,
  indicatorvalue
from yourtable
unpivot
(
  indicatorvalue
  for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;

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

Вы также можете использовать CROSS APPLY с UNION ALL для преобразования столбцов:

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  select 'Indicator1', Indicator1 union all
  select 'Indicator2', Indicator2 union all
  select 'Indicator3', Indicator3 union all
  select 'Indicator4', Indicator4 
) c (indicatorname, indicatorvalue);

В зависимости от вашей версии SQL Server вы даже можете использовать CROSS APPLY с предложением VALUES:

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  values
  ('Indicator1', Indicator1),
  ('Indicator2', Indicator2),
  ('Indicator3', Indicator3),
  ('Indicator4', Indicator4)
) c (indicatorname, indicatorvalue);

Наконец, если у вас есть 150 столбцов для отказа, и вы не хотите жестко закодировать весь запрос, вы можете сгенерировать оператор sql с помощью динамического SQL:

DECLARE @colsUnpivot AS NVARCHAR(MAX),
   @query  AS NVARCHAR(MAX)

select @colsUnpivot 
  = stuff((select ','+quotename(C.column_name)
           from information_schema.columns as C
           where C.table_name = 'yourtable' and
                 C.column_name like 'Indicator%'
           for xml path('')), 1, 1, '')

set @query 
  = 'select id, entityId,
        indicatorname,
        indicatorvalue
     from yourtable
     unpivot
     (
        indicatorvalue
        for indicatorname in ('+ @colsunpivot +')
     ) u'

exec sp_executesql @query;

Ответ 2

well Если у вас 150 столбцов, я думаю, что UNPIVOT не вариант. Таким образом, вы можете использовать трюк xml

;with CTE1 as (
    select ID, EntityID, (select t.* for xml raw('row'), type) as Data
    from temp1 as t
), CTE2 as (
    select
         C.id, C.EntityID,
         F.C.value('local-name(.)', 'nvarchar(128)') as IndicatorName,
         F.C.value('.', 'nvarchar(max)') as IndicatorValue
    from CTE1 as c
        outer apply c.Data.nodes('row/@*') as F(C)
)
select * from CTE2 where IndicatorName like 'Indicator%'

демонстрационная версия sql

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

UPDATE
Как есть большое пламя в комментариях, я думаю, что добавлю некоторые плюсы и минусы xml/dynamic SQL. Я постараюсь быть как можно более объективным и не упомянуть об элегантности и уродстве. Если у вас есть другие плюсы и минусы, отредактируйте ответ или напишите комментарии

против

  • не так быстро, как динамический SQL, грубые тесты дали мне, что xml примерно в 2,5 раза медленнее этой динамической (это был один запрос в таблице из 250000 строк, поэтому эта оценка не является точным), Вы можете сравнить это, если хотите, здесь sqlfiddle пример: на 100000 строках это было 29 с (xml) против 14s (динамическая);
  • может быть, это может быть труднее понять для людей, не знакомых с xpath;

профи

  • это та же область, что и другие ваши запросы, и это может быть очень удобно. Несколько примеров приходят на ум
    • вы можете запросить таблицы inserted и deleted внутри вашего триггера (вообще не возможно с динамикой);
    • пользователь не должен иметь разрешений при прямом выборе из таблицы. Я имею в виду, что если у вас есть уровень хранимых процедур, и у пользователя есть разрешения для запуска sp, но у них нет прав на запросы таблиц напрямую, вы все равно можете использовать этот запрос внутри хранимой процедуры;
    • вы могли бы отображать таблицу запроса, которую вы заполнили в своей области (чтобы передать ее внутри динамического SQL, вам нужно либо сделать ее временной таблицей, либо создать тип и передать ее как параметр в динамический SQL;
  • вы можете сделать этот запрос внутри функции (скалярный или табличный). Невозможно использовать динамический SQL внутри функций;

Ответ 3

Просто чтобы помочь новым читателям, я создал пример, чтобы лучше понять @bluefeet ответ о UNPIVOT.

 SELECT id
        ,entityId
        ,indicatorname
        ,indicatorvalue
  FROM (VALUES
        (1, 1, 'Value of Indicator 1 for entity 1', 'Value of Indicator 2 for entity 1', 'Value of Indicator 3 for entity 1'),
        (2, 1, 'Value of Indicator 1 for entity 2', 'Value of Indicator 2 for entity 2', 'Value of Indicator 3 for entity 2'),
        (3, 1, 'Value of Indicator 1 for entity 3', 'Value of Indicator 2 for entity 3', 'Value of Indicator 3 for entity 3'),
        (4, 2, 'Value of Indicator 1 for entity 4', 'Value of Indicator 2 for entity 4', 'Value of Indicator 3 for entity 4')
       ) AS Category(ID, EntityId, Indicator1, Indicator2, Indicator3)
UNPIVOT
(
    indicatorvalue
    FOR indicatorname IN (Indicator1, Indicator2, Indicator3)
) UNPIV;

Ответ 4

DECLARE @TableName nvarchar(50)
DECLARE column_to_row CURSOR FOR

--List of tables that we want to unpivot columns as row
SELECT DISTINCT t.name FROM sys.tables t
JOIN sys.schemas s ON t.schema_id=t.schema_id
WHERE t.name like '%_CT%'
AND s.name='cdc'

OPEN  column_to_row
FETCH NEXT FROM column_to_row INTO @TableName
WHILE @@FETCH_STATUS = 0
BEGIN

DECLARE @script nvarchar(max) = null
DECLARE @columns nvarchar(2000) = null

-- keep the table column list
select @columns = COALESCE(@columns + ',','') + c.name from sys.tables  t
join sys.columns c on t.object_id = c.object_id
where t.name = @TableName

set @script = 'SELECT '[email protected]+' FROM [cdc].['[email protected]+'] (nolock)'
--print (@script)
exec (@script)

FETCH NEXT FROM column_to_row INTO @TableName
END
CLOSE column_to_row
DEALLOCATE column_to_row

Вот еще один метод для столбцов для строк, сколько таблиц и сколько столбцов у вас есть, не важно. Просто установите параметры и получите результат. Я написал это, потому что иногда мне нужен результат таблицы A (который представляет собой набор результатов столбца), как поля другой таблицы B (которые должны быть полями строк). В этом случае я не знаю, сколько полей я установил для своей таблицы B.

Ответ 5

Мне понадобилось решение для преобразования столбцов в строки в Microsoft SQL Server, не зная имен колонок (используемых в триггерах) и без динамического sql (динамический sql слишком медленный для использования в триггере).

Наконец-то я нашел это решение, которое отлично работает:

SELECT
    insRowTbl.PK,
    insRowTbl.Username,
    attr.insRow.value('local-name(.)', 'nvarchar(128)') as FieldName,
    attr.insRow.value('.', 'nvarchar(max)') as FieldValue 
FROM ( Select      
          i.ID as PK,
          i.LastModifiedBy as Username,
          convert(xml, (select i.* for xml raw)) as insRowCol
       FROM inserted as i
     ) as insRowTbl
CROSS APPLY insRowTbl.insRowCol.nodes('/row/@*') as attr(insRow)

Как вы можете видеть, я конвертирую строку в XML (Subquery выбирает i, * для xml raw, это преобразует все столбцы в один столбец xml)

Затем я CROSS APPLY функцию для каждого атрибута XML этого столбца, так что я получаю одну строку за атрибут.

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

(Edit: Я только что видел, как Роман Пекар ответил выше, кто делает то же самое. Сначала я использовал динамический SQL-ключ с курсорами, который был в 10-100 раз медленнее, чем это решение, но, возможно, это было вызвано курсором, а не динамическим sql. Во всяком случае, это решение очень просто универсальное, поэтому его окончательный вариант).

Я оставляю этот комментарий в этом месте, потому что я хочу ссылаться на это объяснение в своем сообщении о полном триггере аудита, который вы можете найти здесь: fooobar.com/questions/74653/...

Ответ 6

Просто потому, что я не видел, чтобы это упоминалось.

Если 2016+, здесь есть еще один вариант динамического разворачивания данных без фактического использования Dynamic SQL.

пример

Declare @YourTable Table ([ID] varchar(50),[Col1] varchar(50),[Col2] varchar(50))
Insert Into @YourTable Values 
 (1,'A','B')
,(2,'R','C')
,(3,'X','D')

Select A.[ID]
      ,Item  = B.[Key]
      ,Value = B.[Value]
 From  @YourTable A
 Cross Apply ( Select * 
                From  OpenJson((Select A.* For JSON Path,Without_Array_Wrapper )) 
                Where [Key] not in ('ID','Other','Columns','ToExclude')
             ) B

Возвращает

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D