SQL Server truy vấn PIVOT động?


202

Tôi đã được giao nhiệm vụ đưa ra một phương tiện dịch các dữ liệu sau:

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

vào sau

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

Các điểm trống có thể là NULL hoặc khoảng trống, hoặc là tốt, và các thể loại sẽ cần phải năng động. Một cảnh báo khác có thể xảy ra là chúng tôi sẽ chạy truy vấn trong một khả năng hạn chế, điều đó có nghĩa là các bảng tạm thời đã hết. Tôi đã cố gắng nghiên cứu và đã tiếp tục PIVOTnhưng vì tôi chưa bao giờ sử dụng nó trước khi tôi thực sự không hiểu nó, mặc dù tôi đã cố gắng hết sức để tìm ra nó. ai đó có thể chỉ cho tôi phương hướng đúng không?


3
Phiên bản SQL Server nào vui lòng?
Aaron Bertrand

1
có thể trùng lặp Viết SQL nâng cao Chọn
RichardTheKiwi

Câu trả lời:


250

PIVOT SQL động:

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

Các kết quả:

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

Vì vậy, \ @cols phải được nối chuỗi, phải không? Chúng ta không thể sử dụng sp_executesql và liên kết tham số để nội suy \ @cols trong đó? Mặc dù chúng tôi tự xây dựng \ @cols, nhưng nếu bằng cách nào đó nó chứa SQL độc hại. Bất kỳ bước giảm nhẹ bổ sung nào tôi có thể thực hiện trước khi nối nó và thực hiện nó?
Hạt đậu đỏ

Làm thế nào bạn sẽ sắp xếp các hàng và cột trên này?
Patrick Schomburg

@PatrickSchomburg Có nhiều cách khác nhau - nếu bạn muốn sắp xếp @colsthì bạn có thể xóa DISTINCTvà sử dụng GROUP BYORDER BYkhi nào bạn nhận được danh sách @cols.
Taryn

Tôi sẽ thử điều đó. Còn các hàng thì sao? Tôi cũng đang sử dụng một ngày, và nó không xuất hiện theo thứ tự.
Patrick Schomburg

1
Không bao giờ tôi đã đặt hàng sai vị trí.
Patrick Schomburg

27

PIVOT SQL động

Cách tiếp cận khác nhau để tạo chuỗi cột

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

Kết quả

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

Tôi biết câu hỏi này đã cũ hơn nhưng tôi đã tìm kiếm câu trả lời và nghĩ rằng tôi có thể mở rộng phần "động" của vấn đề và có thể giúp đỡ ai đó.

Trước hết tôi đã xây dựng giải pháp này để giải quyết vấn đề mà một vài đồng nghiệp đang gặp phải với các bộ dữ liệu lớn và bất tiện cần phải được xoay vòng nhanh chóng.

Giải pháp này yêu cầu tạo ra một thủ tục được lưu trữ, vì vậy nếu đó không phải là câu hỏi cho nhu cầu của bạn, xin vui lòng ngừng đọc ngay bây giờ.

Quy trình này sẽ thực hiện các biến chính của câu lệnh trục để tạo động các câu lệnh trục cho các bảng, tên cột và tổng hợp khác nhau. Cột tĩnh được sử dụng làm cột theo nhóm / nhận dạng cho trục (điều này có thể được loại bỏ khỏi mã nếu không cần thiết nhưng khá phổ biến trong các câu lệnh trục và cần thiết để giải quyết vấn đề ban đầu), cột trụ là nơi tên cột kết quả cuối cùng sẽ được tạo từ và cột giá trị là những gì tổng hợp sẽ được áp dụng cho. Tham số Bảng là tên của bảng bao gồm lược đồ (lược đồ.tablename) phần mã này có thể sử dụng một số tình yêu vì nó không sạch như tôi mong muốn. Nó hiệu quả với tôi vì việc sử dụng của tôi không phải đối mặt công khai và tiêm sql không phải là vấn đề đáng lo ngại.

Hãy bắt đầu với mã để tạo thủ tục được lưu trữ. Mã này sẽ hoạt động trong tất cả các phiên bản SSMS 2005 trở lên nhưng tôi chưa thử nghiệm nó vào năm 2005 hoặc 2016 nhưng tôi không thể hiểu tại sao nó không hoạt động.

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

Tiếp theo chúng tôi sẽ lấy dữ liệu của chúng tôi sẵn sàng cho ví dụ. Tôi đã lấy ví dụ dữ liệu từ câu trả lời được chấp nhận với việc thêm một vài yếu tố dữ liệu để sử dụng trong bằng chứng khái niệm này để hiển thị các kết quả đầu ra khác nhau của thay đổi tổng hợp.

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)

Các ví dụ sau đây cho thấy các câu lệnh thực thi khác nhau hiển thị các tập hợp khác nhau như một ví dụ đơn giản. Tôi đã không chọn thay đổi các cột tĩnh, trục và giá trị để giữ cho ví dụ đơn giản. Bạn có thể chỉ cần sao chép và dán mã để bắt đầu tự làm phiền nó

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'

Việc thực hiện này trả về các tập dữ liệu sau tương ứng.

nhập mô tả hình ảnh ở đây


Làm tốt lắm! Bạn có thể vui lòng thực hiện một tùy chọn của TVF thay vì thủ tục được lưu trữ. Sẽ thuận tiện để chọn từ TVF như vậy.
Przemyslaw Remin

3
Thật không may, theo hiểu biết tốt nhất của tôi, bởi vì bạn không thể có cấu trúc động cho TVF. Bạn phải có một tập hợp các cột tĩnh trong TVF.
SFrejofsky

8

Phiên bản cập nhật cho SQL Server 2017 bằng cách sử dụng chức năng STRING_AGG để xây dựng danh sách cột trụ:

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

Bạn có thể đạt được điều này bằng cách sử dụng TSQL động (nhớ sử dụng QUOTENAME để tránh các cuộc tấn công tiêm nhiễm SQL):

Pivots với các cột động trong SQL Server 2005

Máy chủ SQL - Bảng PIVOT động - SQL Injection

Tham chiếu bắt buộc đối với Lời nguyền và Phước lành của SQL động


11
FWIW QUOTENAMEchỉ giúp các cuộc tấn công SQL SQL nếu bạn chấp nhận @tableName làm tham số từ người dùng và thêm nó vào một truy vấn như thế nào SET @sql = 'SELECT * FROM ' + @tableName;. Bạn có thể xây dựng nhiều chuỗi SQL động dễ bị tổn thương và QUOTENAMEsẽ không làm gì để giúp bạn.
Aaron Bertrand

2
@davids Vui lòng tham khảo thảo luận meta này . Nếu bạn loại bỏ các siêu liên kết, câu trả lời của bạn là không đầy đủ.
Kermit

@Kermit, tôi đồng ý rằng việc hiển thị mã sẽ hữu ích hơn, nhưng bạn có nói rằng nó là bắt buộc để nó là một câu trả lời không? Không có các liên kết, câu trả lời của tôi là "Bạn có thể đạt được điều này bằng cách sử dụng TSQL động". Câu trả lời được chọn gợi ý cùng một lộ trình, với lợi ích bổ sung nếu cũng chỉ ra cách thực hiện, đó là lý do tại sao nó được chọn làm câu trả lời.
davids

2
Tôi đã bình chọn câu trả lời được chọn (trước khi nó được chọn) bởi vì nó có một ví dụ và sẽ giúp người mới tốt hơn. Tuy nhiên, tôi nghĩ ai đó mới cũng nên đọc các liên kết tôi cung cấp, đó là lý do tại sao tôi không xóa chúng.
davids

3

Có giải pháp của tôi làm sạch các giá trị null không cần thiết

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

Đoạn mã dưới đây cung cấp kết quả thay thế NULL thành 0 trong đầu ra.

Tạo bảng và chèn dữ liệu:

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)

Truy vấn để tạo kết quả chính xác cũng thay thế NULL bằng số không:

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

ĐẦU RA:

nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.