SQL Server: стовпці до рядків


129

Шукаєте елегантне (або будь-яке) рішення для перетворення стовпців у рядки.

Ось приклад: у мене є таблиця із такою схемою:

[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?


2
Ви ще заглянули в Pivot / Unpivot ?
Джош Джей

Наприкінці це пішло з рішенням блакитного фута. Елегантний та функціональний. Велике спасибі всім.
Сергій

Відповіді:


248

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

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

Зауважте, типи даних стовпців, які ви не відводите, повинні бути однаковими, щоб вам довелося перетворити типи даних перед тим, як застосувати unpivot.

Ви також можете використовувати 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;

4
Для тих, хто хоче більше гайок і болтів щодо UNPIVOTта / проти. APPLY, ця публікація в блозі 2010 року від Бреда Шульцапродовження ) є (є) прекрасною.
ruffin

2
Msg 8167, рівень 16, стан 1, рядок 147 Тип стовпця "блахбла" суперечить типу інших стовпців, визначеним у списку UNPIVOT.
JDPeckham

@JDPeckham Якщо у вас різні типи даних, то вам потрібно перетворити їх в один і той же тип і довжину, перш ніж виконувати unpivot. Ось більше інформації про це .
Taryn

у методу xml є недолік, оскільки він не може скасувати xml-коди, такі як & gt ;, & lt; і &. Крім того, продуктивність може бути значно покращена шляхом перезапису наступним чином: виберіть @colsUnpivot = речі ((виберіть ',' + ім'я цитати (C.колона_ ім'я) як [текст ()] з інформаційних стовпців information_schema.collum як C, де C.table_name = 'yourtable' і C.column_name типу "показник%" для шляху xml (''), введіть) .value ('text () [1]', 'nvarchar (max)'), 1, 1, '')
rrozema

24

добре Якщо у вас 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 fiddle demo

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

ОНОВЛЕННЯ
Оскільки в коментарях є велике полум'я, я думаю, я додам деякі плюси і мінуси xml / динамичного SQL. Я постараюся бути настільки об'єктивним, як міг, і не кажу про елегантність та потворність. Якщо у вас є інші плюси і мінуси, відредагуйте відповідь або напишіть у коментарях

мінуси

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

плюси

  • це та сама сфера, як і ваші інші запити, і це може бути дуже зручно. Кілька прикладів приходять на думку
    • ви можете запитувати insertedі deletedтаблиці всередині вашого тригера (взагалі неможливо з динамікою);
    • користувач не повинен мати дозволів на прямий вибір з таблиці. Що я маю на увазі, якщо у вас зберігається рівень процедур і користувач має дозволи на запуск sp, але у вас немає дозволів безпосередньо запитувати таблиці, ви все одно можете використовувати цей запит всередині збереженої процедури;
    • ви можете запитувати змінну таблиці, яку ви заповнили у вашому діапазоні (щоб передати її всередині динамічного SQL, вам слід зробити замість неї тимчасову таблицю або створити тип та передати її як параметр у динамічний SQL;
  • Ви можете виконати цей запит у межах функції (скалярної чи табличної). Використовувати динамічний SQL всередині функцій неможливо;

2
Які дані ви вибираєте за допомогою XML, який не потребує вибору даних із таблиці?
Аарон Бертран

1
Наприклад, ви можете вирішити не надавати користувачам дозволу на вибір даних із таблиць, а лише на збережених процедурах, що працюють з таблицями, тому я міг вибрати для xml всередині процедури, але мені доведеться використовувати деякі вирішення, якщо я хочу використовувати динамічний SQL
Роман Пекар

3
Якщо ви хочете, щоб ваші користувачі мали змогу виконувати код, вам належить надати їм будь-який доступ, необхідний для виконання коду. Не складайте вимог, які не існують, щоб ваша відповідь звучала краще (вам також не потрібно коментувати конкуруючі відповіді, щоб переглянути вашу відповідь - якщо вони знайшли цю відповідь, вони можуть знайти і вашу).
Аарон Бертран

2
Крім того, якщо ваше обгрунтування використання XML полягає в тому, що ви можете помістити його в збережену процедуру, щоб уникнути прямого доступу до таблиці, можливо, ваш приклад повинен показати, як ввести її в збережену процедуру і як надати права користувачеві, щоб вони може виконати його, не читаючи доступу до базової таблиці. Для мене це повзання сфери, тому що більшість людей, які пишуть запити проти таблиці, мають доступ до таблиці.
Аарон Бертран

2
Я б сказав, що різниця в тривалості в 10 разів має значення, так. І ~ 8000 рядків не є "великими обсягами даних" - чи слід бачити, що відбувається проти 800000 рядків?
Аарон Бертран

7

Щоб допомогти новим читачам, я створив приклад, щоб краще зрозуміти відповідь @ 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;

3

Мені знадобилося рішення для перетворення стовпців у рядки в 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 (Підзапит вибору i, * для сировини xml, це перетворює всі стовпці в один XML-колонку)

Тоді я КРОСУВАТИ ЗАСТОСУВАТИ функцію до кожного атрибута XML цього стовпця, щоб отримати один рядок за атрибутом.

Загалом, це перетворює стовпці в рядки, не знаючи назви стовпців і не використовуючи динамічний sql. Це досить швидко для моєї мети.

(Редагувати: Я щойно побачив відповідь Романа Пекара вище, хто робить те саме. Я спершу застосував динамічний триггер sql з курсорами, який був у 10 до 100 разів повільніше, ніж це рішення, але, можливо, його спричинив курсор, а не динамічний пл. У будь-якому випадку це рішення дуже просте універсальне, тому його остаточний варіант).

Я залишаю цей коментар у цьому місці, тому що я хочу посилатися на це пояснення у своєму дописі про повний тригер аудиту, який ви можете знайти тут: https://stackoverflow.com/a/43800286/4160788


3
DECLARE @TableName varchar(max)=NULL
SELECT @TableName=COALESCE(@TableName+',','')+t.TABLE_CATALOG+'.'+ t.TABLE_SCHEMA+'.'+o.Name
  FROM sysindexes AS i
  INNER JOIN sysobjects AS o ON i.id = o.id
  INNER JOIN INFORMATION_SCHEMA.TABLES T ON T.TABLE_NAME=o.name
 WHERE i.indid < 2
  AND OBJECTPROPERTY(o.id,'IsMSShipped') = 0
  AND i.rowcnt >350
  AND o.xtype !='TF'
 ORDER BY o.name ASC

 print @tablename

Ви можете отримати список таблиць, у яких кількість рядків> 350. Ви можете побачити у списку рішення таблиці як рядок.


2

Просто тому, що я не бачив його згадуваного.

Якщо 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.