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에서이 시나리오에는 너무 복잡해 보이지만 SQL Server 의 UNPIVOTPIVOT 함수 를 모두 사용하여 매우 쉽게 적용 할 수 있다고 언급했습니다 .

그러나이 사용하고 복제 할 수있는 그 기능에 액세스 할 수없는 경우 UNION ALLUNPIVOTA를 집계 함수 다음과 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 정적 버전 :

SQL 서버 의 UNPIVOTPIVOT함수는이 변환을 훨씬 쉽게 만듭니다. 변환하려는 모든 값을 알고있는 경우 해당 값을 정적 버전으로 하드 코딩하여 결과를 얻을 수 있습니다.

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 보기

이있는 내부 쿼리 UNPIVOTUNION 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 보기

동적 버전 모두를 쿼리 yourtable하고 sys.columns테이블에 항목의 목록 생성 UNPIVOTPIVOT. 그런 다음 실행될 쿼리 문자열에 추가됩니다. 동적 버전의 장점은 변경 목록 이 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

결과를 ac # DataTable에 전달하고 있습니다. 간단한 메서드 'Transpose (Datatable dt)'를 만들어서 할 수 있지만 SQL에는이를 수행 할 수있는 것이 있습니다.
edezzie

@edezzie SQL에는이를 수행하는 간단한 방법이 없습니다. 동적 버전을 변경하여 테이블 이름을 전달하고 시스템 테이블에서 정보를 가져올 수 있습니다.
Taryn

이 훌륭한 답변을 답변으로 표시하지 않은 이유가 있습니까? @bluefeet에게 메달을 줘!
Fandango68

이것은 잘 작동하고 있습니다 select * from Table unpivot (([Paul], [John], [Tim], [Eric])의 이름에 대한 값) 위로 피벗 ([Red], [Green]의 색상에 대한 최대 (값) , [Blue])) p 그러나이 select * from Table unpivot (([Paul], [John], [Tim], [Eric])의 이름 값) up pivot (max (value) for (SELECT COLOR FROM TABLENAME))에서 따옴표로 묶인 값을 입력하는 대신 p는 선택 쿼리를 사용하여 값을 직접 검색 할 수 없습니다.
Mogli 2016 년

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. 테이블 이름이 주어지면 local-name ()을 사용하여 sys.columns 또는 FOR XML 속임수 에서 모든 열 이름을 확인할 수 있습니다 .
  2. FOR XML을 사용하여 고유 한 색상 (또는 한 열의 값) 목록을 작성할 수도 있습니다.
  3. 위의 내용을 동적 SQL 일괄 처리로 결합하여 모든 테이블을 처리 할 수 ​​있습니다.

11

SQL에서 열과 행을 전치하는 몇 가지 솔루션을 더 지적하고 싶습니다.

첫 번째는 CURSOR를 사용하는 것입니다. 전문 커뮤니티의 일반적인 합의는 SQL Server 커서를 멀리하는 것이지만 여전히 커서 사용이 권장되는 경우가 있습니다. 어쨌든 커서는 행을 열로 바꾸는 또 다른 옵션을 제공합니다.

  • 수직 확장

    PIVOT과 마찬가지로 커서에는 더 많은 정책 번호를 포함하도록 데이터 세트가 확장 될 때 더 많은 행을 추가 할 수있는 동적 기능이 있습니다.

  • 수평 확장

    PIVOT과 달리 커서는 스크립트를 변경하지 않고 새로 추가 된 문서를 포함하도록 확장 할 수있어이 영역에서 탁월합니다.

  • 성능 분석

    CURSOR를 사용하여 행을 열로 전치하는 주요 제한은 일반적으로 커서를 사용하는 것과 관련된 단점입니다. 성능이 크게 저하됩니다. 이는 Cursor가 각 FETCH NEXT 작업에 대해 별도의 쿼리를 생성하기 때문입니다.

행을 열로 전치하는 또 다른 솔루션은 XML을 사용하는 것입니다.

행을 열로 전치하는 XML 솔루션은 기본적으로 동적 열 제한을 해결한다는 점에서 PIVOT의 최적 버전입니다.

스크립트의 XML 버전은 XML 경로, 동적 T-SQL 및 일부 내장 함수 (예 : STUFF, QUOTENAME)의 조합을 사용하여 이러한 제한을 해결합니다.

  • 수직 확장

    PIVOT 및 Cursor와 유사하게 새로 추가 된 정책은 원래 스크립트를 변경하지 않고도 스크립트의 XML 버전에서 검색 할 수 있습니다.

  • 수평 확장

    PIVOT과 달리 새로 추가 된 문서는 스크립트 변경없이 표시 할 수 있습니다.

  • 성능 분석

    IO 측면에서 스크립트의 XML 버전 통계는 PIVOT과 거의 유사합니다. 유일한 차이점은 XML에 dtTranspose 테이블의 두 번째 스캔이 있지만 이번에는 논리적 읽기 데이터 캐시에서 가져온 것입니다.

이 문서 ( https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/ )에서 이러한 솔루션 (실제 T-SQL 예제 포함)에 대해 자세히 알아볼 수 있습니다.


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'

argg. 하지만 모든 필드가 nvarchar (max) 인 하나를 원합니다
hamish

1
nvarchar (max) 모든 필드에 대해 끝부터 6 번째 줄의 max (value)로 sum (value)을 변경합니다.
Paco Zarate 2015

2

나는 UnPivot먼저 수행하고 결과를 저장 CTE하고CTE in 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 답변을 기반으로 분할 된 텍스트를 전치하는 데 사용하는 코드를 공유하고 싶습니다. 이 aproach에서 나는 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

이런 식으로 테이블의 Filelds (Columns)에서 모든 데이터를 기록 (행)으로 변환합니다.

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

Paco Zarate의 솔루션을 사용할 수 있었고 아름답게 작동합니다. 한 줄을 추가해야했지만 ( "SET ANSI_WARNINGS ON"), 내가 사용하거나 호출 한 방식에 고유 한 것일 수 있습니다. 내 사용에 문제가 있으며 누군가가 나를 도울 수 있기를 바랍니다.

이 솔루션은 실제 SQL 테이블에서만 작동합니다. 임시 테이블과 인 메모리 (선언 된) 테이블로 시도했지만 작동하지 않습니다. 그래서 내 호출 코드에서 SQL 데이터베이스에 테이블을 만든 다음 SQLTranspose를 호출합니다. 다시 말하지만 잘 작동합니다. 내가 원하는 것입니다. 내 문제는 다음과 같습니다.

전체 솔루션이 진정으로 동적이 되려면 SQLTranspose에 "즉시"로 보낼 준비된 정보를 임시로 저장하는 테이블을 만든 다음 SQLTranspose가 호출되면 해당 테이블을 삭제해야합니다. 테이블 삭제로 인해 궁극적 인 구현 계획에 문제가 있습니다. 코드는 최종 사용자 응용 프로그램 (Microsoft Access 양식 / 메뉴의 단추)에서 실행해야합니다. 이 SQL 프로세스를 사용하면 (SQL 테이블 생성, SQLTranspose 호출, SQL 테이블 삭제) 사용 된 SQL 계정에 테이블을 삭제할 권한이 없기 때문에 최종 사용자 응용 프로그램에서 오류가 발생합니다.

그래서 몇 가지 가능한 해결책이 있다고 생각합니다.

  1. SQLTranspose가 임시 테이블 또는 선언 된 테이블 변수와 함께 작동하도록하는 방법을 찾으십시오.

  2. 실제 SQL 테이블이 필요하지 않은 행과 열을 전치하는 또 다른 방법을 알아 봅니다.

  3. 최종 사용자가 사용하는 SQL 계정이 테이블을 삭제하도록 허용하는 적절한 방법을 알아 내십시오. 내 Access 애플리케이션에 코딩 된 단일 공유 SQL 계정입니다. 권한은 부여 할 수없는 dbo 유형 권한 인 것 같습니다.

나는 이것 중 일부가 다른 별도의 스레드와 질문을 보증 할 수 있음을 알고 있습니다. 그러나 하나의 솔루션이 단순히 행과 열의 전치를 수행하는 다른 방법 일 가능성이 있기 때문에이 스레드에서 첫 번째 게시물을 작성하겠습니다.

편집 : Paco가 제안한 것처럼 끝에서 여섯 번째 줄에서 sum (value)을 max (value)로 바꿨습니다.

편집하다:

나는 나를 위해 일하는 것을 알아 냈다. 최고 인지 모르겠어 대답 .

스트로드 프로 시저를 실행하여 데이터베이스에서보고 출력을 생성하는 데 사용되는 읽기 전용 사용자 계정이 있습니다. 내가 만든 SQLTranspose 함수는 "합법적 인"테이블 (선언 된 테이블이 아니라 임시 테이블이 아님)에서만 작동하므로 읽기 전용 사용자 계정이 테이블을 생성하고 나중에 삭제하는 방법을 찾아야했습니다. .

내 목적을 위해 사용자 계정이 테이블을 생성 할 수있는 것이 괜찮다고 생각했습니다. 사용자는 여전히 테이블을 삭제할 수 없습니다. 내 솔루션은 사용자 계정이 승인 된 스키마를 만드는 것이 었습니다. 그런 다음 해당 테이블을 생성, 사용 또는 삭제할 때마다 지정된 스키마로 참조합니다.

먼저 'sa'또는 'sysadmin'계정에서이 명령을 실행했습니다. CREATE SCHEMA ro AUTHORIZATION

내 "tmpoutput"테이블을 참조 할 때마다 다음 예제와 같이 지정합니다.

드롭 테이블 ro.tmpoutput


나는 나를 위해 일하는 것을 알아 냈다. 그것이 최선의 대답 인지 아닌지 모르겠습니다 .
CraigD
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.