SQL Server dinamik PIVOT sorgusu?


204

Aşağıdaki verileri çevirmenin bir yolunu bulmakla görevlendirildim:

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

aşağıdaki gibi:

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

Boş noktalar NULL veya boşluk olabilir, ya iyidir ve kategorilerin dinamik olması gerekir. Bunun bir başka olası uyarısı, sorguyu sınırlı bir kapasitede çalıştıracağımızdır, bu da geçici tabloların tükendiği anlamına gelir. Araştırmaya çalıştım ve indim, PIVOTama bunu anlamadım, daha önce anlamadığım gibi, anlamaya çalıştığım en iyi çabama rağmen. Birisi beni doğru yönde gösterebilir mi?


3
SQL Server'ın hangi sürümü lütfen?
Aaron Bertrand

Yanıtlar:


251

Dinamik 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

Sonuçlar:

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

Yani \ @cols dizgiyle birleştirilmelidir, değil mi? \ @Cols değerini enterpolat etmek için sp_executesql ve parametre bağlamayı kullanamıyoruz? Kendimizi \ @cols oluştursak da, ya bir şekilde zararlı SQL içeriyorsa. Birleştirmeden ve yürütmeden önce uygulayabileceğim ek hafifletici adımlar var mı?
Kırmızı Bezelye

Bunun üzerindeki satırları ve sütunları nasıl sıralarsınız?
Patrick Schomburg

@PatrickSchomburg Çeşitli yollar vardır - eğer sıralamak @colsisterseniz , o zaman kaldırabilir DISTINCTve kullanabilirsiniz GROUP BYve ORDER BYlistesini aldığınızda @cols.
Taryn

Bunu deneyeceğim. Sıralar ne olacak? Ben de bir tarih kullanıyorum ve sıra ile çıkmıyor.
Patrick Schomburg

1
Boş ver, siparişi yanlış yere koydum.
Patrick Schomburg

27

Dinamik SQL PIVOT

Sütun dizesi oluşturmak için farklı yaklaşım

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

Sonuç

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

Bu sorunun daha eski olduğunu biliyorum ama cevapları arıyordum ve sorunun "dinamik" kısmını genişletebileceğimi ve muhtemelen birine yardım edebileceğimi düşündüm.

Her şeyden önce, bu çözümü birkaç iş arkadaşının hızlı bir şekilde döndürülmesi gereken tutarsız ve büyük veri kümeleriyle yaşadıkları bir sorunu çözmek için inşa ettim.

Bu çözüm, saklı bir yordamın oluşturulmasını gerektirir, bu yüzden ihtiyaçlarınız için söz konusu değilse, lütfen şimdi okumayı bırakın.

Bu yordam, değişken tablolar, sütun adları ve toplamalar için dinamik olarak pivot ifadeleri oluşturmak üzere bir pivot ifadesinin temel değişkenlerini alacaktır. Statik sütun, pivot için grup kimliği / kimlik sütunu olarak kullanılır (bu gerekli değilse koddan çıkarılabilir, ancak pivot ifadelerinde oldukça yaygındır ve orijinal sorunu çözmek için gereklidir), pivot sütunu sonuçta elde edilen sütun adlarından üretilecek ve değer sütunu toplamın uygulanacağı değerdir. Table parametresi, şema (schema.tablename) içeren tablonun adıdır, kodun bu kısmı, olmasını istediğim kadar temiz olmadığı için biraz sevgi kullanabilir. Benim için çalıştı çünkü benim kullanım kamuya açık değildi ve sql enjeksiyon bir endişe değildi.

Saklı yordamı oluşturmak için kod ile başlayalım. Bu kod SSMS 2005 ve sonraki tüm sürümlerinde çalışmalıdır, ancak 2005 veya 2016'da test etmedim, ancak neden işe yaramadığını göremiyorum.

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

Sonra verilerimizi örnek için hazırlayacağız. Toplam değişikliğin çeşitli çıktılarını göstermek için bu kavram kanıtında kullanılacak birkaç veri öğesinin eklenmesiyle veri yanıtını kabul edilen cevaptan aldım.

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)

Aşağıdaki örnekler, çeşitli toplamaları basit bir örnek olarak gösteren çeşitli yürütme ifadelerini göstermektedir. Örneği basit tutmak için statik, pivot ve değer sütunlarını değiştirmeyi tercih etmedim. Kendinizle uğraşmaya başlamak için kodu kopyalayıp yapıştırabilmeniz gerekir

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'

Bu yürütme, sırasıyla aşağıdaki veri kümelerini döndürür.

resim açıklamasını buraya girin


Aferin! Saklı yordam yerine bir TVF seçeneği yapabilir misiniz? Böyle bir TVF arasından seçim yapmak uygun olacaktır.
Przemyslaw Remin

3
Ne yazık ki, bilgim dahilinde değil, çünkü bir TVF için dinamik bir yapıya sahip olamazsınız. Bir TVF'de statik bir sütun kümeniz olması gerekir.
SFrejofsky

8

Pivot sütun listesini oluşturmak için STRING_AGG işlevini kullanan SQL Server 2017 için güncellenmiş sürüm:

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

Bunu dinamik TSQL kullanarak gerçekleştirebilirsiniz (SQL enjeksiyon saldırılarını önlemek için QUOTENAME kullanmayı unutmayın):

SQL Server 2005'te Dinamik Sütunlara Sahip Pivotlar

SQL Server - Dinamik PIVOT Tablosu - SQL Enjeksiyonu

Dinamik SQL'in Laneti ve Bereketlerine zorunlu referans


11
FWIW, QUOTENAMESQL enjeksiyon saldırılarına yalnızca @tableName öğesini bir kullanıcıdan parametre olarak kabul edip benzer bir sorguya eklerseniz yardımcı olur SET @sql = 'SELECT * FROM ' + @tableName;. Çok sayıda savunmasız dinamik SQL dizesi oluşturabilirsiniz ve QUOTENAMEsize yardımcı olmak için yalamayacaksınız.
Aaron Bertrand

2
@davids Lütfen bu meta tartışmaya bakın . Köprüleri kaldırırsanız, cevabınız eksik demektir.
Kermit

@Kermit, kodun gösterilmesinin daha yararlı olduğunu kabul ediyorum, ancak bir cevap olması için gerekli olduğunu mu söylüyorsunuz? Bağlantılar olmadan, cevabım "Bunu dinamik TSQL kullanarak başarabilirsiniz". Seçilen cevap aynı rotayı önerir, ayrıca nasıl yapılacağını da gösterirse ek faydası vardır, bu yüzden cevap olarak seçilmiştir.
davids

2
Seçilen cevabı (seçilmeden önce) oyladım çünkü bir örneği vardı ve yeni birine daha iyi yardımcı olacak. Ancak, yeni birisinin sağladığım bağlantıları da okuması gerektiğini düşünüyorum, bu yüzden onları kaldırmadım.
davids

3

Gereksiz null değerleri temizleyen çözümüm var

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

Aşağıdaki kod , çıktıda NULL değerini sıfıra değiştiren sonuçlar sağlar .

Tablo oluşturma ve veri ekleme:

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 yerine sıfırlarla da aynı sonuçları elde etmek için sorgu:

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

ÇIKTI :

resim açıklamasını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.