Простий спосіб переміщення стовпців і рядків у SQL?


110

Як я просто перемикаю стовпці з рядками в SQL? Чи є якась проста команда для перенесення?

тобто поверніть цей результат:

        Paul  | John  | Tim  |  Eric
Red     1       5       1       3
Green   8       4       3       5
Blue    2       2       9       1

в це:

        Red  | Green | Blue
Paul    1       8       2
John    5       4       2
Tim     1       3       9
Eric    3       5       1

PIVOT видається занадто складним для цього сценарію.


1
stackoverflow.com/questions/10699997/… є дещо схожим питанням.
Тім Дірборборн

Відповіді:


145

Існує кілька способів перетворення цих даних. У своєму початковому дописі ви заявили, що PIVOTздається занадто складним для цього сценарію, але його можна застосувати дуже легко, використовуючи UNPIVOTіPIVOT функції, і функції SQL Server.

Однак, якщо у вас немає доступу до цих функцій , це може бути відтворено з допомогою UNION ALLдо UNPIVOTа потім агрегатної функції з CASEзаявою до PIVOT:

Створити таблицю:

CREATE TABLE yourTable([color] varchar(5), [Paul] int, [John] int, [Tim] int, [Eric] int);

INSERT INTO yourTable
    ([color], [Paul], [John], [Tim], [Eric])
VALUES
    ('Red', 1, 5, 1, 3),
    ('Green', 8, 4, 3, 5),
    ('Blue', 2, 2, 9, 1);

Версія Union All, агрегат та CASE:

select name,
  sum(case when color = 'Red' then value else 0 end) Red,
  sum(case when color = 'Green' then value else 0 end) Green,
  sum(case when color = 'Blue' then value else 0 end) Blue
from
(
  select color, Paul value, 'Paul' name
  from yourTable
  union all
  select color, John value, 'John' name
  from yourTable
  union all
  select color, Tim value, 'Tim' name
  from yourTable
  union all
  select color, Eric value, 'Eric' name
  from yourTable
) src
group by name

Див. SQL Fiddle with Demo

У UNION ALLвикон ет UNPIVOTз даних шляхом перетворення стовпців Paul, John, Tim, Ericв окремі рядки. Потім ви застосовуєте функцію сукупності sum()з caseоператором, щоб отримати нові стовпці для кожного color.

Статична версія Unpivot та Pivot:

І функції, UNPIVOTі PIVOTфункції на SQL-сервері значно полегшують цю трансформацію. Якщо ви знаєте всі значення, які ви хочете перетворити, ви можете жорстко кодувати їх у статичну версію, щоб отримати результат:

select name, [Red], [Green], [Blue]
from
(
  select color, name, value
  from yourtable
  unpivot
  (
    value for name in (Paul, John, Tim, Eric)
  ) unpiv
) src
pivot
(
  sum(value)
  for color in ([Red], [Green], [Blue])
) piv

Див. SQL Fiddle with Demo

Внутрішній запит UNPIVOTвиконує ту саму функцію, що і UNION ALL. Він бере список стовпців і перетворює їх у рядки, PIVOTпотім виконує остаточне перетворення у стовпці.

Динамічна версія зведення:

Якщо у вас є невідома кількість стовпців ( Paul, John, Tim, Ericу вашому прикладі), а потім невідома кількість кольорів для перетворення, ви можете використовувати динамічний sql для створення списку до, UNPIVOTа потім PIVOT:

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

select @colsUnpivot = stuff((select ','+quotename(C.name)
         from sys.columns as C
         where C.object_id = object_id('yourtable') and
               C.name <> 'color'
         for xml path('')), 1, 1, '')

select @colsPivot = STUFF((SELECT  ',' 
                      + quotename(color)
                    from yourtable t
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')


set @query 
  = 'select name, '+@colsPivot+'
      from
      (
        select color, name, value
        from yourtable
        unpivot
        (
          value for name in ('+@colsUnpivot+')
        ) unpiv
      ) src
      pivot
      (
        sum(value)
        for color in ('+@colsPivot+')
      ) piv'

exec(@query)

Див. SQL Fiddle with Demo

Динамічна версія запитує і те, yourtableі потім sys.columnsтаблицю для створення списку елементів до UNPIVOTта PIVOT. Потім додається до рядка запиту, який потрібно виконати. Плюс динамічної версії полягає в тому, що у вас змінюється список colorsта / або namesце генерує список під час виконання.

Усі три запити дадуть однаковий результат:

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |

Я повинен підкреслити, що я хочу, щоб метод перенесення результатів запиту в базу даних. Проста команда типу транспонування, яка може бути виконана на наборі результатів із запиту, не знаючи нічого про сам запит або стовпці, таблиці тощо, з яких витягує інформацію. Щось на кшталт: (TRANSPOSE (SELECT ......)) У моєму прикладі, наведеному вище, уявіть, що перша таблиця - це набір результатів, сформований із дуже складного запиту, що включає декілька таблиць, об'єднань, поворотів тощо. Однак результат є проста таблиця. Я просто хочу перегорнути стовпці рядками в цьому результаті.
едезі

Я передаю результат в AC # DataTable. Я можу створити простий метод 'Transpose (Datatable dt)', щоб зробити це там, але чи є щось у SQL для цього.
edezzie

@edezzie в SQL не є простим способом зробити це. Можливо, ви могли б змінити динамічну версію, щоб передати назву таблиці та витягнути інформацію з системних таблиць.
Тарін

Чи є якась причина, чому ви не позначили цю блискучу відповідь як ВІДПОВІДЬ? Подаруйте @bluefeet медаль!
Fandango68

Це працює добре виберіть * з таблиці unpivot (значення для імені в ([Пол], [Джон], [Тім], [Ерік])) вгору повороту (макс (значення) для кольору в ([Червоний], [Зелений] , [Синій])) p, але чи можна записати цей вибір * з таблиці unpivot (значення для імені в ([Пол], [Джон], [Тім], [Ерік])) вгору поворот (макс (значення) для колір у (SELECT COLOR OF TABLENAME)) p замість введення котируваного значення не може отримати значення безпосередньо за допомогою вибору запиту.
Моглі

20

Зазвичай, заздалегідь потрібно знати ВСІ ярлики стовпців та рядків. Як ви бачите в запиті нижче, всі мітки повністю перераховані як в операціях UNPIVOT, так і в операціях PIVOT.

Налаштування схеми MS SQL Server 2012 :

create table tbl (
    color varchar(10), Paul int, John int, Tim int, Eric int);
insert tbl select 
    'Red' ,1 ,5 ,1 ,3 union all select
    'Green' ,8 ,4 ,3 ,5 union all select
    'Blue' ,2 ,2 ,9 ,1;

Запит 1 :

select *
from tbl
unpivot (value for name in ([Paul],[John],[Tim],[Eric])) up
pivot (max(value) for color in ([Red],[Green],[Blue])) p

Результати :

| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric |   3 |     5 |    1 |
| John |   5 |     4 |    2 |
| Paul |   1 |     8 |    2 |
|  Tim |   1 |     3 |    9 |

Додаткові нотатки:

  1. Давши назву таблиці, ви можете визначити всі назви стовпців із sys.column або хитрості для XML за допомогою local-name () .
  2. Ви також можете створити список різних кольорів (або значень для одного стовпця), використовуючи FOR XML.
  3. Описане вище може бути об'єднано в динамічну партію sql для обробки будь-якої таблиці.

11

Я хотів би зазначити ще кілька рішень щодо транспонування стовпців та рядків у SQL.

Перший - використання CURSOR. Хоча загальним консенсусом у професійному співтоваристві є триматися подалі від курсорів SQL Server, все ж є випадки, коли рекомендується використовувати курсори. У будь-якому випадку курсори пропонують нам ще один варіант переміщення рядків у стовпці.

  • Вертикальне розширення

    Подібно до PIVOT, курсор має динамічну можливість додавати більше рядків, оскільки ваш набір даних розширюється, щоб включати більше номерів політик.

  • Горизонтальне розширення

    На відміну від PIVOT, курсор перевершує цю область, оскільки він здатний розширитись, щоб включити нещодавно доданий документ, не змінюючи сценарій.

  • Поломка продуктивності

    Основним обмеженням переміщення рядків у стовпці за допомогою CURSOR є недолік, пов’язаний із використанням курсорів загалом - вони мають значну вартість продуктивності. Це тому, що Курсор генерує окремий запит для кожної операції FETCH NEXT.

Ще одне рішення переміщення рядків у стовпці - це використання XML.

Рішення XML для переміщення рядків у стовпці - це в основному оптимальна версія PIVOT, оскільки вона стосується обмеження динамічного стовпця.

Версія сценарію XML вирішує це обмеження за допомогою комбінації шляху XML, динамічного T-SQL та деяких вбудованих функцій (наприклад, STUFF, QUOTENAME).

  • Вертикальне розширення

    Подібно до PIVOT та курсору, щойно додані політики можна отримати у XML-версії сценарію, не змінюючи початковий сценарій.

  • Горизонтальне розширення

    На відміну від PIVOT, щойно додані документи можуть відображатися без зміни сценарію.

  • Поломка продуктивності

    Що стосується IO, статистика XML-версії сценарію майже схожа на PIVOT - різниця лише в тому, що XML має друге сканування таблиці dtTranspose, але цього разу з логічного кешу даних кешу.

Ви можете дізнатися більше про ці рішення (включаючи деякі фактичні приклади T-SQL) у цій статті: https://www.sqlshack.com/multiple-options-to-transposed-rows-into-column/


8

На основі цього рішення від bluefeet тут зберігається процедура, яка використовує динамічний sql для генерації транспонованої таблиці. Потрібно, щоб усі поля були числовими, за винятком транспонованого стовпця (стовпця, який буде заголовком в отриманій таблиці):

/****** Object:  StoredProcedure [dbo].[SQLTranspose]    Script Date: 11/10/2015 7:08:02 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      Paco Zarate
-- Create date: 2015-11-10
-- Description: SQLTranspose dynamically changes a table to show rows as headers. It needs that all the values are numeric except for the field using for     transposing.
-- Parameters: @TableName - Table to transpose
--             @FieldNameTranspose - Column that will be the new headers
-- Usage: exec SQLTranspose <table>, <FieldToTranspose>
-- =============================================
ALTER PROCEDURE [dbo].[SQLTranspose] 
  -- Add the parameters for the stored procedure here
  @TableName NVarchar(MAX) = '', 
  @FieldNameTranspose NVarchar(MAX) = ''
AS
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

  DECLARE @colsUnpivot AS NVARCHAR(MAX),
  @query  AS NVARCHAR(MAX),
  @queryPivot  AS NVARCHAR(MAX),
  @colsPivot as  NVARCHAR(MAX),
  @columnToPivot as NVARCHAR(MAX),
  @tableToPivot as NVARCHAR(MAX), 
  @colsResult as xml

  select @tableToPivot = @TableName;
  select @columnToPivot = @FieldNameTranspose


  select @colsUnpivot = stuff((select ','+quotename(C.name)
       from sys.columns as C
       where C.object_id = object_id(@tableToPivot) and
             C.name <> @columnToPivot 
       for xml path('')), 1, 1, '')

  set @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename('+@columnToPivot+')
                  from '+@tableToPivot+' t
                  where '+@columnToPivot+' <> ''''
          FOR XML PATH(''''), TYPE)'

  exec sp_executesql @queryPivot, N'@colsResult xml out', @colsResult out

  select @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'),1,1,'')

  set @query 
    = 'select name, rowid, '+@colsPivot+'
        from
        (
          select '+@columnToPivot+' , name, value, ROW_NUMBER() over (partition by '+@columnToPivot+' order by '+@columnToPivot+') as rowid
          from '+@tableToPivot+'
          unpivot
          (
            value for name in ('+@colsUnpivot+')
          ) unpiv
        ) src
        pivot
        (
          sum(value)
          for '+@columnToPivot+' in ('+@colsPivot+')
        ) piv
        order by rowid'
  exec(@query)
END

Ви можете перевірити його за допомогою таблиці, наданої цією командою:

exec SQLTranspose 'yourTable', 'color'

аргг. але я хочу один , де все поля є NVARCHAR (макс)
Хеміш

1
Для всіх полів nvarchar (max) просто змініть суму (значення) на max (значення) в 6-му рядку з кінця
Paco Zarate

2

Я роблю UnPivotперше і зберігаю результати в CTEі використовую CTEв Pivotоперації.

Демо

with cte as
(
select 'Paul' as Name, color, Paul as Value 
 from yourTable
 union all
 select 'John' as Name, color, John as Value 
 from yourTable
 union all
 select 'Tim' as Name, color, Tim as Value 
 from yourTable
 union all
 select 'Eric' as Name, color, Eric as Value 
 from yourTable
 )

select Name, [Red], [Green], [Blue]
from
(
select *
from cte
) as src
pivot 
(
  max(Value)
  for color IN ([Red], [Green], [Blue])
) as Dtpivot;

2

Додаючи до приголомшливої ​​відповіді @Paco Zarate вище, якщо ви хочете перенести таблицю з кількома типами стовпців, то додайте це в кінець рядка 39, щоб він переміщав лише intстовпці:

and C.system_type_id = 56   --56 = type int

Ось повний запит, який змінюється:

select @colsUnpivot = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id(@tableToPivot) and
      C.name <> @columnToPivot and C.system_type_id = 56    --56 = type int
for xml path('')), 1, 1, '')

Щоб знайти інших system_type_id, запустіть це:

select name, system_type_id from sys.types order by name

1

Мені подобається ділитися кодом, який я використовую, щоб перенести розбитий текст на основі відповіді bluefeet. У цьому підході я реалізується як процедура в MS SQL 2005

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      ELD.
-- Create date: May, 5 2016.
-- Description: Transpose from rows to columns the user split function.
-- =============================================
CREATE PROCEDURE TransposeSplit @InputToSplit VARCHAR(8000)
    ,@Delimeter VARCHAR(8000) = ','
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @colsUnpivot AS NVARCHAR(MAX)
        ,@query AS NVARCHAR(MAX)
        ,@queryPivot AS NVARCHAR(MAX)
        ,@colsPivot AS NVARCHAR(MAX)
        ,@columnToPivot AS NVARCHAR(MAX)
        ,@tableToPivot AS NVARCHAR(MAX)
        ,@colsResult AS XML

    SELECT @tableToPivot = '#tempSplitedTable'

    SELECT @columnToPivot = 'col_number'

    CREATE TABLE #tempSplitedTable (
        col_number INT
        ,col_value VARCHAR(8000)
        )

    INSERT INTO #tempSplitedTable (
        col_number
        ,col_value
        )
    SELECT ROW_NUMBER() OVER (
            ORDER BY (
                    SELECT 100
                    )
            ) AS RowNumber
        ,item
    FROM [DB].[ESCHEME].[fnSplit](@InputToSplit, @Delimeter)

    SELECT @colsUnpivot = STUFF((
                SELECT ',' + quotename(C.NAME)
                FROM [tempdb].sys.columns AS C
                WHERE C.object_id = object_id('tempdb..' + @tableToPivot)
                    AND C.NAME <> @columnToPivot
                FOR XML path('')
                ), 1, 1, '')

    SET @queryPivot = 'SELECT @colsResult = (SELECT  '','' 
                    + quotename(' + @columnToPivot + ')
                  from ' + @tableToPivot + ' t
                  where ' + @columnToPivot + ' <> ''''
          FOR XML PATH(''''), TYPE)'

    EXEC sp_executesql @queryPivot
        ,N'@colsResult xml out'
        ,@colsResult OUT

    SELECT @colsPivot = STUFF(@colsResult.value('.', 'NVARCHAR(MAX)'), 1, 1, '')

    SET @query = 'select name, rowid, ' + @colsPivot + '
        from
        (
          select ' + @columnToPivot + ' , name, value, ROW_NUMBER() over (partition by ' + @columnToPivot + ' order by ' + @columnToPivot + ') as rowid
          from ' + @tableToPivot + '
          unpivot
          (
            value for name in (' + @colsUnpivot + ')
          ) unpiv
        ) src
        pivot
        (
          MAX(value)
          for ' + @columnToPivot + ' in (' + @colsPivot + ')
        ) piv
        order by rowid'

    EXEC (@query)

    DROP TABLE #tempSplitedTable
END
GO

Я змішую це рішення з інформацією про те, як замовити рядки без замовлення ( SQLAuthority.com ) та функцію розділення на MSDN ( social.msdn.microsoft.com )

Коли ви виконуєте продескур

DECLARE @RC int
DECLARE @InputToSplit varchar(MAX)
DECLARE @Delimeter varchar(1)

set @InputToSplit = 'hello|beautiful|world'
set @Delimeter = '|'

EXECUTE @RC = [TransposeSplit] 
   @InputToSplit
  ,@Delimeter
GO

Ви отримуєте наступний результат

  name       rowid  1      2          3
  col_value  1      hello  beautiful  world

1

Таким чином конвертувати всі дані з файлів (стовпців) у таблицю для запису (рядок).

Declare @TableName  [nvarchar](128)
Declare @ExecStr    nvarchar(max)
Declare @Where      nvarchar(max)
Set @TableName = 'myTableName'
--Enter Filtering If Exists
Set @Where = ''

--Set @ExecStr = N'Select * From '+quotename(@TableName)+@Where
--Exec(@ExecStr)

Drop Table If Exists #tmp_Col2Row

Create Table #tmp_Col2Row
(Field_Name nvarchar(128) Not Null
,Field_Value nvarchar(max) Null
)

Set @ExecStr = N' Insert Into #tmp_Col2Row (Field_Name , Field_Value) '
Select @ExecStr += (Select N'Select '''+C.name+''' ,Convert(nvarchar(max),'+quotename(C.name) + ') From ' + quotename(@TableName)+@Where+Char(10)+' Union All '
         from sys.columns as C
         where (C.object_id = object_id(@TableName)) 
         for xml path(''))
Select @ExecStr = Left(@ExecStr,Len(@ExecStr)-Len(' Union All '))
--Print @ExecStr
Exec (@ExecStr)

Select * From #tmp_Col2Row
Go

0

Мені вдалося застосувати рішення Пако Зарате, і воно прекрасно працює. Мені довелося додати один рядок ("SET ANSI_WARNINGS ON"), але це може бути щось унікальне для того, як я його використовував або називав. Виникла проблема з моїм використанням, і я сподіваюся, що хтось може мені допомогти з цим:

Рішення працює лише з фактичною таблицею SQL. Я спробував це з тимчасовою таблицею, а також із таблицею в пам'яті (оголошеною), але вона не працює з ними. Отже, у своєму викликовому коді я створюю таблицю в моїй базі даних SQL, а потім викликаю SQLTranspose. Знову ж таки, це чудово працює. Це просто те, що я хочу. Ось моя проблема:

Для того, щоб загальне рішення було по-справжньому динамічним, мені потрібно створити таку таблицю, де я тимчасово зберігаю підготовлену інформацію, яку я надсилаю на SQLTranspose "на льоту", а потім видаляю цю таблицю після виклику SQLTranspose. Видалення таблиці представляє проблему з моїм кінцевим планом реалізації. Код потрібно запускати з програми кінцевого користувача (кнопка у формі / меню Microsoft Access). Коли я використовую цей процес SQL (створити таблицю SQL, зателефонуйте в SQLTranspose, видаліть таблицю SQL), програма кінцевого користувача виявить помилку, оскільки використовуваний обліковий запис SQL не має прав на скидання таблиці.

Тому я думаю, що існує кілька можливих рішень:

  1. Знайдіть спосіб змусити SQLTranspose працювати з тимчасовою таблицею або оголошеною змінною таблиці.

  2. Визначте інший метод переміщення рядків і стовпців, який не потребує фактичної таблиці SQL.

  3. Зробіть відповідний метод дозволу обліковому запису SQL, який використовуються моїми кінцевими користувачами, скидати таблицю. Це єдиний спільний обліковий запис SQL, закодований у моїй програмі Access. Здається, що дозвіл є привілеєм типу dbo, який неможливо надати.

Я усвідомлюю, що щось із цього може вимагати іншого, окремої теми та питання. Однак, оскільки є ймовірність, що одне рішення може бути просто іншим способом зробити перенесення рядків і стовпців, я зроблю своє перше повідомлення тут у цій темі.

EDIT: Я також замінив суму (значення) на max (значення) в 6-му рядку з кінця, як запропонував Пако.

Редагувати:

Я зрозумів щось, що для мене працює. Я не знаю, це найкраща відповідь чи ні.

У мене є обліковий запис користувача, доступний лише для читання, який використовується для виконання процедур, призначених для редагування, і, таким чином, генерує висновки звітів з бази даних. Оскільки створена мною функція SQLTranspose працюватиме лише з "законною" таблицею (не декларованою та не тимчасовою таблицею), я повинен був знайти спосіб створення облікового запису користувача для читання (а потім видалення) таблиці .

Я аргументував, що для моїх цілей нормально, щоб обліковий запис користувача дозволяв створювати таблицю. Користувач все ще не міг видалити таблицю. Моє рішення полягало у створенні схеми, де авторизований обліковий запис користувача. Тоді, коли я створюю, використовую або видаляю цю таблицю, пересилайте її за вказаною схемою.

Я вперше видав цю команду з облікового запису 'sa' або 'sysadmin': СТВОРИТИ СХЕМУ ro AUTHORIZATION

Коли я будь-коли звертаюся до своєї таблиці "tmpoutput", я вказую її так, як цей приклад:

ro.tmpoutput випадаючої таблиці


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