SQL Server 동적 PIVOT 쿼리?


202

나는 다음 데이터를 번역하는 수단을 생각해 냈습니다.

date        category        amount
1/1/2012    ABC             1000.00
2/1/2012    DEF             500.00
2/1/2012    GHI             800.00
2/10/2012   DEF             700.00
3/1/2012    ABC             1100.00

다음과 같이 :

date        ABC             DEF             GHI
1/1/2012    1000.00
2/1/2012                    500.00
2/1/2012                                    800.00
2/10/2012                   700.00
3/1/2012    1100.00

공백 지점은 NULL이거나 공백 일 수 있으며 범주가 동적이어야합니다. 이에 대한 또 다른주의 사항은 제한된 용량으로 쿼리를 실행한다는 것입니다. 이는 임시 테이블이 없음을 의미합니다. 나는 연구를 시도하고 착륙 PIVOT했지만, 그것을 이해하기위한 최선의 노력에도 불구하고 그것을 이해하기 전에 그것을 결코 사용하지 않았으므로. 누구든지 올바른 방향으로 나를 가리킬 수 있습니까?


3
SQL Server의 버전은 무엇입니까?
Aaron Bertrand

답변:


250

동적 SQL PIVOT :

create table temp
(
    date datetime,
    category varchar(3),
    amount money
)

insert into temp values ('1/1/2012', 'ABC', 1000.00)
insert into temp values ('2/1/2012', 'DEF', 500.00)
insert into temp values ('2/1/2012', 'GHI', 800.00)
insert into temp values ('2/10/2012', 'DEF', 700.00)
insert into temp values ('3/1/2012', 'ABC', 1100.00)


DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX);

SET @cols = STUFF((SELECT distinct ',' + QUOTENAME(c.category) 
            FROM temp c
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')

set @query = 'SELECT date, ' + @cols + ' from 
            (
                select date
                    , amount
                    , category
                from temp
           ) x
            pivot 
            (
                 max(amount)
                for category in (' + @cols + ')
            ) p '


execute(@query)

drop table temp

결과 :

Date                        ABC         DEF    GHI
2012-01-01 00:00:00.000     1000.00     NULL    NULL
2012-02-01 00:00:00.000     NULL        500.00  800.00
2012-02-10 00:00:00.000     NULL        700.00  NULL
2012-03-01 00:00:00.000     1100.00     NULL    NULL

따라서 \ @cols는 문자열로 연결되어야합니다. sp_executesql 및 매개 변수 바인딩을 사용하여 \ @cols를 보간 할 수 없습니까? \ @cols 우리 자신을 구성하더라도 어떻게 든 악성 SQL이 포함 된 경우 연결하고 실행하기 전에 취할 수있는 추가 완화 단계가 있습니까?
붉은 완두콩

이것에 대해 행과 열을 어떻게 정렬 하시겠습니까?
Patrick Schomburg

@PatrickSchomburg 다양한 방법이있다 - 당신은 정렬하기를 원한다면 @cols당신은 제거 할 수 있습니다 DISTINCT및 사용을 GROUP BY하고 ORDER BY당신의 목록을 얻을 때 @cols.
Taryn

나는 그것을 시도 할 것이다. 행은 어떻습니까? 날짜도 사용하고 있는데 순서대로 나오지 않습니다.
Patrick Schomburg

1
내가 잘못 주문했을 때
Patrick Schomburg

27

동적 SQL PIVOT

열 문자열을 만들기위한 다른 접근법

create table #temp
(
    date datetime,
    category varchar(3),
    amount money
)

insert into #temp values ('1/1/2012', 'ABC', 1000.00)
insert into #temp values ('2/1/2012', 'DEF', 500.00)
insert into #temp values ('2/1/2012', 'GHI', 800.00)
insert into #temp values ('2/10/2012', 'DEF', 700.00)
insert into #temp values ('3/1/2012', 'ABC', 1100.00)

DECLARE @cols  AS NVARCHAR(MAX)='';
DECLARE @query AS NVARCHAR(MAX)='';

SELECT @cols = @cols + QUOTENAME(category) + ',' FROM (select distinct category from #temp ) as tmp
select @cols = substring(@cols, 0, len(@cols)) --trim "," at end

set @query = 
'SELECT * from 
(
    select date, amount, category from #temp
) src
pivot 
(
    max(amount) for category in (' + @cols + ')
) piv'

execute(@query)
drop table #temp

결과

date                    ABC     DEF     GHI
2012-01-01 00:00:00.000 1000.00 NULL    NULL
2012-02-01 00:00:00.000 NULL    500.00  800.00
2012-02-10 00:00:00.000 NULL    700.00  NULL
2012-03-01 00:00:00.000 1100.00 NULL    NULL

13

나는이 질문이 오래되었다는 것을 알고 있지만 답을 통해 찾고 있었고 문제의 "동적"부분을 확장하여 누군가를 도울 수 있다고 생각했다.

무엇보다 먼저이 솔루션을 구축하여 몇 명의 동료가 일정하지 않고 큰 데이터 세트를 빠르게 피봇해야하는 문제를 해결했습니다.

이 솔루션을 사용하려면 저장 프로 시저를 만들어야하므로 필요에 맞지 않는 경우 지금 읽기를 중단하십시오.

이 절차에서는 다양한 테이블, 열 이름 및 집계에 대한 피벗 문을 동적으로 만들기 위해 피벗 문의 주요 변수를 사용합니다. 정적 열은 피벗에 대한 그룹 별 / 신원 열로 사용됩니다 (필요하지 않은 경우 코드에서 제거 할 수 있지만 피벗 문에서 매우 일반적이며 원래 문제를 해결하는 데 필요했습니다), 피벗 열은 최종 결과 열 이름이 생성되고 값 열이 집계에 적용됩니다. Table 매개 변수는 스키마 (schema.tablename)를 포함하여 테이블의 이름입니다.이 코드 부분은 내가 원하는만큼 깨끗하지 않기 때문에 약간의 사랑을 사용할 수 있습니다. 내 사용법이 공개적으로 직면하지 않았고 SQL 주입이 걱정되지 않았기 때문에 그것은 나를 위해 일했습니다.

코드로 시작하여 저장 프로 시저를 만듭니다. 이 코드는 모든 버전의 SSMS 2005 이상에서 작동해야하지만 2005 년 또는 2016 년에 테스트하지 않았지만 왜 작동하지 않는지 알 수 없습니다.

create PROCEDURE [dbo].[USP_DYNAMIC_PIVOT]
    (
        @STATIC_COLUMN VARCHAR(255),
        @PIVOT_COLUMN VARCHAR(255),
        @VALUE_COLUMN VARCHAR(255),
        @TABLE VARCHAR(255),
        @AGGREGATE VARCHAR(20) = null
    )

AS


BEGIN

SET NOCOUNT ON;
declare @AVAIABLE_TO_PIVOT NVARCHAR(MAX),
        @SQLSTRING NVARCHAR(MAX),
        @PIVOT_SQL_STRING NVARCHAR(MAX),
        @TEMPVARCOLUMNS NVARCHAR(MAX),
        @TABLESQL NVARCHAR(MAX)

if isnull(@AGGREGATE,'') = '' 
    begin
        SET @AGGREGATE = 'MAX'
    end


 SET @PIVOT_SQL_STRING =    'SELECT top 1 STUFF((SELECT distinct '', '' + CAST(''[''+CONVERT(VARCHAR,'+ @PIVOT_COLUMN+')+'']''  AS VARCHAR(50)) [text()]
                            FROM '+@TABLE+'
                            WHERE ISNULL('+@PIVOT_COLUMN+','''') <> ''''
                            FOR XML PATH(''''), TYPE)
                            .value(''.'',''NVARCHAR(MAX)''),1,2,'' '') as PIVOT_VALUES
                            from '+@TABLE+' ma
                            ORDER BY ' + @PIVOT_COLUMN + ''

declare @TAB AS TABLE(COL NVARCHAR(MAX) )

INSERT INTO @TAB EXEC SP_EXECUTESQL  @PIVOT_SQL_STRING, @AVAIABLE_TO_PIVOT 

SET @AVAIABLE_TO_PIVOT = (SELECT * FROM @TAB)


SET @TEMPVARCOLUMNS = (SELECT replace(@AVAIABLE_TO_PIVOT,',',' nvarchar(255) null,') + ' nvarchar(255) null')


SET @SQLSTRING = 'DECLARE @RETURN_TABLE TABLE ('+@STATIC_COLUMN+' NVARCHAR(255) NULL,'+@TEMPVARCOLUMNS+')  
                    INSERT INTO @RETURN_TABLE('+@STATIC_COLUMN+','+@AVAIABLE_TO_PIVOT+')

                    select * from (
                    SELECT ' + @STATIC_COLUMN + ' , ' + @PIVOT_COLUMN + ', ' + @VALUE_COLUMN + ' FROM '+@TABLE+' ) a

                    PIVOT
                    (
                    '+@AGGREGATE+'('+@VALUE_COLUMN+')
                    FOR '+@PIVOT_COLUMN+' IN ('+@AVAIABLE_TO_PIVOT+')
                    ) piv

                    SELECT * FROM @RETURN_TABLE'



EXEC SP_EXECUTESQL @SQLSTRING

END

다음으로 데이터를 준비 할 것입니다. 이 개념 증명에서 집계 변경의 다양한 출력을 표시하기 위해 사용할 몇 가지 데이터 요소를 추가하여 허용 된 답변에서 데이터 예제를 가져 왔습니다.

create table temp
(
    date datetime,
    category varchar(3),
    amount money
)

insert into temp values ('1/1/2012', 'ABC', 1000.00)
insert into temp values ('1/1/2012', 'ABC', 2000.00) -- added
insert into temp values ('2/1/2012', 'DEF', 500.00)
insert into temp values ('2/1/2012', 'DEF', 1500.00) -- added
insert into temp values ('2/1/2012', 'GHI', 800.00)
insert into temp values ('2/10/2012', 'DEF', 700.00)
insert into temp values ('2/10/2012', 'DEF', 800.00) -- addded
insert into temp values ('3/1/2012', 'ABC', 1100.00)

다음 예는 간단한 집합으로 다양한 집합을 보여주는 다양한 실행 문을 보여줍니다. 예제를 간단하게 유지하기 위해 정적, 피벗 및 값 열을 변경하지 않았습니다. 코드를 복사하여 붙여 넣을 수 있습니다.

exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','sum'
exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','max'
exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','avg'
exec [dbo].[USP_DYNAMIC_PIVOT] 'date','category','amount','dbo.temp','min'

이 실행은 다음 데이터 세트를 각각 리턴합니다.

여기에 이미지 설명을 입력하십시오


잘 했어! 저장 프로 시저 대신 TVF 옵션을 사용할 수 있습니까? 그런 TVF에서 선택하는 것이 편리합니다.
Przemyslaw Remin

3
불행히도, 내가 아는 한, TVF의 역동적 인 구조를 가질 수 없기 때문입니다. TVF에 정적 열 세트가 있어야합니다.
SFrejofsky

8

STRING_AGG 함수를 사용하여 피벗 열 목록을 구성하는 SQL Server 2017 버전이 업데이트되었습니다.

create table temp
(
    date datetime,
    category varchar(3),
    amount money
);

insert into temp values ('20120101', 'ABC', 1000.00);
insert into temp values ('20120201', 'DEF', 500.00);
insert into temp values ('20120201', 'GHI', 800.00);
insert into temp values ('20120210', 'DEF', 700.00);
insert into temp values ('20120301', 'ABC', 1100.00);


DECLARE @cols AS NVARCHAR(MAX),
    @query  AS NVARCHAR(MAX);

SET @cols = (SELECT STRING_AGG(category,',') FROM (SELECT DISTINCT category FROM temp WHERE category IS NOT NULL)t);

set @query = 'SELECT date, ' + @cols + ' from 
            (
                select date
                    , amount
                    , category
                from temp
           ) x
            pivot 
            (
                 max(amount)
                for category in (' + @cols + ')
            ) p ';

execute(@query);

drop table temp;

6

동적 TSQL을 사용하여이를 달성 할 수 있습니다 (SQL 주입 공격을 피하려면 QUOTENAME을 사용해야 함).

SQL Server 2005의 동적 열이있는 피벗

SQL Server-동적 PIVOT 테이블-SQL 삽입

동적 SQL의 저주와 축복에 대한 의무적 참조


11
FWIW QUOTENAME는 @tableName을 사용자의 매개 변수로 수락하고 같은 쿼리에 추가하는 경우 SQL 주입 공격에만 도움이됩니다 SET @sql = 'SELECT * FROM ' + @tableName;. 취약한 동적 SQL 문자열을 많이 만들 수 있으며 QUOTENAME도움이되지 않습니다.
Aaron Bertrand

2
@davids 이 메타 토론을 참조하십시오 . 하이퍼 링크를 제거하면 답이 불완전합니다.
커밋

@ Kermit, 코드를 표시하는 것이 더 도움이된다는 데 동의하지만 답변이 되려면 코드가 필요하다고 말하고 있습니까? 링크가 없으면 "동적 TSQL을 사용하여이 작업을 수행 할 수 있습니다"라는 대답이 있습니다. 선택한 답변은 동일한 경로를 제안하며, 방법을 보여주는 경우 추가 혜택을 제공하므로 답변으로 선택되었습니다.
davids

2
예를 들어 새로운 사람을 더 잘 도울 수 있기 때문에 선택한 답변을 선택하기 전에 (투표하기 전에) 투표했습니다. 그러나 새로운 사람도 내가 제공 한 링크를 읽어야한다고 생각하므로 제거하지 않았습니다.
davids

3

필요없는 null 값을 정리하는 솔루션이 있습니다.

DECLARE @cols AS NVARCHAR(MAX),
@maxcols AS NVARCHAR(MAX),
@query  AS NVARCHAR(MAX)

select @cols = STUFF((SELECT ',' + QUOTENAME(CodigoFormaPago) 
                from PO_FormasPago
                order by CodigoFormaPago
        FOR XML PATH(''), TYPE
        ).value('.', 'NVARCHAR(MAX)') 
    ,1,1,'')

select @maxcols = STUFF((SELECT ',MAX(' + QUOTENAME(CodigoFormaPago) + ') as ' + QUOTENAME(CodigoFormaPago)
                from PO_FormasPago
                order by CodigoFormaPago
        FOR XML PATH(''), TYPE
        ).value('.', 'NVARCHAR(MAX)')
    ,1,1,'')

set @query = 'SELECT CodigoProducto, DenominacionProducto, ' + @maxcols + '
            FROM
            (
                SELECT 
                CodigoProducto, DenominacionProducto,
                ' + @cols + ' from 
                 (
                    SELECT 
                        p.CodigoProducto as CodigoProducto,
                        p.DenominacionProducto as DenominacionProducto,
                        fpp.CantidadCuotas as CantidadCuotas,
                        fpp.IdFormaPago as IdFormaPago,
                        fp.CodigoFormaPago as CodigoFormaPago
                    FROM
                        PR_Producto p
                        LEFT JOIN PR_FormasPagoProducto fpp
                            ON fpp.IdProducto = p.IdProducto
                        LEFT JOIN PO_FormasPago fp
                            ON fpp.IdFormaPago = fp.IdFormaPago
                ) xp
                pivot 
                (
                    MAX(CantidadCuotas)
                    for CodigoFormaPago in (' + @cols + ')
                ) p 
            )  xx 
            GROUP BY CodigoProducto, DenominacionProducto'

t @query;

execute(@query);

2

아래 코드는 출력에서 NULL0 으로 대체하는 결과를 제공합니다 .

테이블 생성 및 데이터 삽입 :

create table test_table
 (
 date nvarchar(10),
 category char(3),
 amount money
 )

 insert into test_table values ('1/1/2012','ABC',1000.00)
 insert into test_table values ('2/1/2012','DEF',500.00)
 insert into test_table values ('2/1/2012','GHI',800.00)
 insert into test_table values ('2/10/2012','DEF',700.00)
 insert into test_table values ('3/1/2012','ABC',1100.00)

NULL을 0으로 대체하는 정확한 결과를 생성하기위한 쿼리 :

DECLARE @DynamicPivotQuery AS NVARCHAR(MAX),
@PivotColumnNames AS NVARCHAR(MAX),
@PivotSelectColumnNames AS NVARCHAR(MAX)

--Get distinct values of the PIVOT Column
SELECT @PivotColumnNames= ISNULL(@PivotColumnNames + ',','')
+ QUOTENAME(category)
FROM (SELECT DISTINCT category FROM test_table) AS cat

--Get distinct values of the PIVOT Column with isnull
SELECT @PivotSelectColumnNames 
= ISNULL(@PivotSelectColumnNames + ',','')
+ 'ISNULL(' + QUOTENAME(category) + ', 0) AS '
+ QUOTENAME(category)
FROM (SELECT DISTINCT category FROM test_table) AS cat

--Prepare the PIVOT query using the dynamic 
SET @DynamicPivotQuery = 
N'SELECT date, ' + @PivotSelectColumnNames + '
FROM test_table
pivot(sum(amount) for category in (' + @PivotColumnNames + ')) as pvt';

--Execute the Dynamic Pivot Query
EXEC sp_executesql @DynamicPivotQuery

출력 :

여기에 이미지 설명을 입력하십시오

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.