Простой способ транспонировать столбцы и строки в SQL?


111

Как мне просто переключать столбцы со строками в 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 ALLto, 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, Aggregate и 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 с демонстрацией

В 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 с демонстрацией

Внутренний запрос с UNPIVOTклассом выполняет ту же функцию, что и UNION ALL. Он берет список столбцов и превращает его в строки,PIVOT затем выполняет окончательное преобразование в столбцы.

Версия Dynamic 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 с демонстрацией

Динамическая версия запрашивает обе, 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 ......)) В моем примере, приведенном выше, представьте, что первая таблица - это набор результатов, сгенерированный из очень сложного запроса, включающего несколько таблиц, объединений, сводных таблиц и т. Д. Тем не менее, результатом является простой стол. Я просто хочу перевернуть столбцы со строками в этом результате.
edezzie

Я передаю результат в c # DataTable. Я могу создать простой метод Transpose (Datatable dt), чтобы сделать это там, но есть ли что-нибудь в SQL для этого.
edezzie

@edezzie, в SQL нет простого способа сделать это. Вероятно, вы могли бы изменить динамическую версию, чтобы передать имя таблицы и извлечь информацию из системных таблиц.
Тарин

Есть ли причина, по которой вы не отметили этот блестящий ответ как ОТВЕТ? Подари медаль @bluefeet!
Fandango68

Это работает нормально, выберите * из таблицы unpivot (значение для имени в ([Paul], [John], [Tim], [Eric])) вверх pivot (max (значение) для цвета в ([Red], [Green] , [Синий])) p, но можно ли записать это select * from Table unpivot (значение для имени в ([Paul], [John], [Tim], [Eric])) вверх pivot (max (value) для color in (SELECT COLOR FROM TABLENAME)) p вместо ввода значения в кавычках не может получить значения напрямую с помощью запроса выбора.
Mogli

20

Обычно это требует, чтобы вы знали ВСЕ метки столбцов И строк заранее. Как вы можете видеть в запросе ниже, все метки перечислены полностью как в операциях UNPIVOT, так и в операциях (re) 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.columns или FOR XML с помощью local-name () .
  2. Вы также можете создать список различных цветов (или значений для одного столбца) с помощью FOR XML.
  3. Вышеупомянутое можно объединить в динамический пакет sql для обработки любой таблицы.

11

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

Первый - с помощью КУРСОРА. Хотя общее мнение в профессиональном сообществе состоит в том, чтобы держаться подальше от курсоров SQL Server, все же есть случаи, когда использование курсоров рекомендуется. В любом случае курсоры предоставляют нам еще одну возможность транспонировать строки в столбцы.

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

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

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

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

  • Анализ производительности

    Основным ограничением транспонирования строк в столбцы с помощью CURSOR является недостаток, связанный с использованием курсоров в целом - они требуют значительных затрат производительности. Это связано с тем, что Cursor генерирует отдельный запрос для каждой операции FETCH NEXT.

Еще одно решение перестановки строк в столбцы - использование XML.

Решение XML для транспонирования строк в столбцы в основном является оптимальной версией PIVOT, поскольку оно устраняет ограничение динамического столбца.

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

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

    Подобно PIVOT и Cursor, новые добавленные политики можно получить в XML-версии сценария без изменения исходного сценария.

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

    В отличие от PIVOT, недавно добавленные документы могут отображаться без изменения сценария.

  • Анализ производительности

    Что касается ввода-вывода, статистика XML-версии сценария почти аналогична PIVOT - с той лишь разницей, что XML имеет второе сканирование таблицы dtTranspose, но на этот раз из кэша логических данных для чтения.

Вы можете найти больше об этих решениях (включая некоторые актуальные примеры T-SQL) в этой статье: https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/


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 (max)
hamish

1
Для всех полей nvarchar (max) просто измените сумму (значение) на max (значение) в 6-й строке с конца
Пако Зарате

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 )

Когда вы выполняете prodecure

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, которую нельзя предоставить.

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

РЕДАКТИРОВАТЬ: Я также заменил sum (value) на max (value) в 6-й строке с конца, как предложил Пако.

РЕДАКТИРОВАТЬ:

Я понял кое-что, что мне подходит. Не знаю, лучший это ответ или нет.

У меня есть учетная запись пользователя, доступная только для чтения, которая используется для выполнения хранимых процедур и, следовательно, для создания отчетов из базы данных. Поскольку созданная мною функция SQLTranspose будет работать только с «законной» таблицей (не с объявленной таблицей и не с временной таблицей), мне пришлось найти способ для учетной записи пользователя, доступной только для чтения, для создания (а затем последующего удаления) таблицы. .

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

Сначала я ввел эту команду из учетной записи 'sa' или 'sysadmin': CREATE SCHEMA ro AUTHORIZATION

Каждый раз, когда я обращаюсь к своей таблице "tmpoutput", я указываю ее следующим образом:

сбросить таблицу ro.tmpoutput


Я понял кое-что, что мне подходит. Не знаю, лучший это ответ или нет.
CraigD
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.