Convertir des lignes en colonnes à l'aide de «Pivot» dans SQL Server


279

J'ai lu les informations sur les tableaux croisés dynamiques MS et j'ai toujours des problèmes pour obtenir ce correct.

J'ai une table temporaire en cours de création, nous dirons que la colonne 1 est un numéro de magasin, et la colonne 2 est un numéro de semaine et enfin la colonne 3 est un total d'un certain type. De plus, les numéros de semaine sont dynamiques, les numéros de magasin sont statiques.

Store      Week     xCount
-------    ----     ------
102        1        96
101        1        138
105        1        37
109        1        59
101        2        282
102        2        212
105        2        78
109        2        97
105        3        60
102        3        123
101        3        220
109        3        87

J'aimerais qu'il apparaisse sous forme de tableau croisé dynamique, comme ceci:

Store        1          2          3        4        5        6....
----- 
101        138        282        220
102         96        212        123
105         37        
109

Stockez les chiffres sur le côté et les semaines sur le dessus.


Réponses:


356

Si vous utilisez SQL Server 2005+, vous pouvez utiliser la PIVOTfonction pour transformer les données des lignes en colonnes.

Il semble que vous devrez utiliser sql dynamique si les semaines sont inconnues, mais il est plus facile de voir le code correct en utilisant une version codée en dur au départ.

Tout d'abord, voici quelques définitions de tableaux rapides et des données à utiliser:

CREATE TABLE #yt 
(
  [Store] int, 
  [Week] int, 
  [xCount] int
);

INSERT INTO #yt
(
  [Store], 
  [Week], [xCount]
)
VALUES
    (102, 1, 96),
    (101, 1, 138),
    (105, 1, 37),
    (109, 1, 59),
    (101, 2, 282),
    (102, 2, 212),
    (105, 2, 78),
    (109, 2, 97),
    (105, 3, 60),
    (102, 3, 123),
    (101, 3, 220),
    (109, 3, 87);

Si vos valeurs sont connues, vous coderez en dur la requête:

select *
from 
(
  select store, week, xCount
  from yt
) src
pivot
(
  sum(xcount)
  for week in ([1], [2], [3])
) piv;

Voir la démo SQL

Ensuite, si vous devez générer dynamiquement le numéro de semaine, votre code sera:

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

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

set @query = 'SELECT store,' + @cols + ' from 
             (
                select store, week, xCount
                from yt
            ) x
            pivot 
            (
                sum(xCount)
                for week in (' + @cols + ')
            ) p '

execute(@query);

Voir la démo SQL .

La version dynamique, génère la liste des weeknombres qui doivent être convertis en colonnes. Les deux donnent le même résultat:

| STORE |   1 |   2 |   3 |
---------------------------
|   101 | 138 | 282 | 220 |
|   102 |  96 | 212 | 123 |
|   105 |  37 |  78 |  60 |
|   109 |  59 |  97 |  87 |

4
Très agréable! Mais comment éliminer la colonne lorsque toutes les valeurs de cette colonne sont NULL?
ZooZ

1
@ZooZ Voir la réponse ci-dessous . Je ne l'ai pas essayé mot pour mot, mais le concept est solide.
ruffin

1
+1 "Il semble que vous devrez utiliser SQL dynamique si les semaines sont inconnues, mais il est plus facile de voir le code correct en utilisant une version hard-cded au départ." Contrairement à la fonction générique Qlikview ( community.qlik.com/blogs/qlikviewdesignblog/2014/03/31/generic ) qui autorise ne nécessite pas que vous nommiez explicitement les différents "FOR ____ IN (...)"
The Red Pea

1
Si vous créez un tableau croisé dynamique avec un cte plus tôt. cte3 AS (select ... )alors vous avez la logique définie ci-dessus avec le @colset @query... il y a une erreur .` Nom d'objet non valide 'cte3'.` comment résoudre ce problème. -
Elizabeth

3
C'est fantastique - gentil @bluefeet. Je n'avais jamais utilisé STUFF(...)auparavant (ou les XML PATHdeux). Pour le bénéfice des autres lecteurs, tout ce qui se fait, c'est joindre les noms des colonnes et couper la virgule principale. Remarque Je pense que ce qui suit est légèrement plus simple: sélectionnez @cols = (SELECT DISTINCT QUOTENAME (Week) + ',' from yt order by 1 FOR XML PATH ('')) set @cols = SUBSTRING (@cols, 1, LEN ( @cols) - 1) ... remplacer le group bypar distinctet order by 1et couper manuellement une virgule suffixée !
DarthPablo

26

C'est pour un nombre de semaines dynamique.

Exemple complet ici: SQL Dynamic Pivot

DECLARE @DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE @ColumnName AS NVARCHAR(MAX)

--Get distinct values of the PIVOT Column 
SELECT @ColumnName= ISNULL(@ColumnName + ',','') + QUOTENAME(Week)
FROM (SELECT DISTINCT Week FROM #StoreSales) AS Weeks

--Prepare the PIVOT query using the dynamic 
SET @DynamicPivotQuery = 
  N'SELECT Store, ' + @ColumnName + ' 
    FROM #StoreSales
    PIVOT(SUM(xCount) 
          FOR Week IN (' + @ColumnName + ')) AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql @DynamicPivotQuery

Hé, j'ai un violon où j'ai besoin de faire pivoter les tableaux dynamiquement, pensez-vous que vous pouvez m'aider avec ça? dbfiddle.uk/…
Silly Volley

@SillyVolley en est un, vous n'avez pas spécifié sur quoi vous vouliez pivoter. De plus, je ne sais pas si vous pouvez le faire dans Postgres, donc je l'ai fait dans SQL Server: dbfiddle.uk/…
Enkode

16

J'ai réalisé la même chose auparavant en utilisant des sous-requêtes. Donc, si votre table d'origine s'appelait StoreCountsByWeek et que vous disposiez d'une table distincte répertoriant les ID de magasin, elle ressemblerait à ceci:

SELECT StoreID, 
    Week1=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=1),
    Week2=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=2),
    Week3=(SELECT ISNULL(SUM(xCount),0) FROM StoreCountsByWeek WHERE StoreCountsByWeek.StoreID=Store.StoreID AND Week=3)
FROM Store
ORDER BY StoreID

Un avantage de cette méthode est que la syntaxe est plus claire et facilite la jointure à d'autres tables pour extraire également d'autres champs dans les résultats.

Mes résultats anecdotiques sont que l'exécution de cette requête sur quelques milliers de lignes s'est terminée en moins d'une seconde, et j'avais en fait 7 sous-requêtes. Mais comme indiqué dans les commentaires, il est plus coûteux de le faire de cette façon, alors faites attention à utiliser cette méthode si vous vous attendez à ce qu'elle s'exécute sur de grandes quantités de données.


8
c'est plus facile, mais c'est une opération très coûteuse, ces sous-requêtes doivent être exécutées une fois pour chaque ligne renvoyée par la table.
Greg

11

Voici ce que vous pouvez faire:

SELECT * 
FROM yourTable
PIVOT (MAX(xCount) 
       FOR Week in ([1],[2],[3],[4],[5],[6],[7])) AS pvt

DEMO


6

J'écris un sp qui pourrait être utile à cet effet, fondamentalement ce sp pivote n'importe quelle table et retourne une nouvelle table pivotée ou retourne juste l'ensemble de données, c'est la façon de l'exécuter:

Exec dbo.rs_pivot_table @schema=dbo,@table=table_name,@column=column_to_pivot,@agg='sum([column_to_agg]),avg([another_column_to_agg]),',
        @sel_cols='column_to_select1,column_to_select2,column_to_select1',@new_table=returned_table_pivoted;

veuillez noter que dans le paramètre @agg, les noms des colonnes doivent être avec '['et le paramètre doit se terminer par une virgule','

SP

Create Procedure [dbo].[rs_pivot_table]
    @schema sysname=dbo,
    @table sysname,
    @column sysname,
    @agg nvarchar(max),
    @sel_cols varchar(max),
    @new_table sysname,
    @add_to_col_name sysname=null
As
--Exec dbo.rs_pivot_table dbo,##TEMPORAL1,tip_liq,'sum([val_liq]),sum([can_liq]),','cod_emp,cod_con,tip_liq',##TEMPORAL1PVT,'hola';
Begin

    Declare @query varchar(max)='';
    Declare @aggDet varchar(100);
    Declare @opp_agg varchar(5);
    Declare @col_agg varchar(100);
    Declare @pivot_col sysname;
    Declare @query_col_pvt varchar(max)='';
    Declare @full_query_pivot varchar(max)='';
    Declare @ind_tmpTbl int; --Indicador de tabla temporal 1=tabla temporal global 0=Tabla fisica

    Create Table #pvt_column(
        pivot_col varchar(100)
    );

    Declare @column_agg table(
        opp_agg varchar(5),
        col_agg varchar(100)
    );

    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(@table) AND type in (N'U'))
        Set @ind_tmpTbl=0;
    ELSE IF OBJECT_ID('tempdb..'+ltrim(rtrim(@table))) IS NOT NULL
        Set @ind_tmpTbl=1;

    IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(@new_table) AND type in (N'U')) OR 
        OBJECT_ID('tempdb..'+ltrim(rtrim(@new_table))) IS NOT NULL
    Begin
        Set @query='DROP TABLE '+@new_table+'';
        Exec (@query);
    End;

    Select @query='Select distinct '+@column+' From '+(case when @ind_tmpTbl=1 then 'tempdb.' else '' end)+@schema+'.'+@table+' where '+@column+' is not null;';
    Print @query;

    Insert into #pvt_column(pivot_col)
    Exec (@query)

    While charindex(',',@agg,1)>0
    Begin
        Select @aggDet=Substring(@agg,1,charindex(',',@agg,1)-1);

        Insert Into @column_agg(opp_agg,col_agg)
        Values(substring(@aggDet,1,charindex('(',@aggDet,1)-1),ltrim(rtrim(replace(substring(@aggDet,charindex('[',@aggDet,1),charindex(']',@aggDet,1)-4),')',''))));

        Set @agg=Substring(@agg,charindex(',',@agg,1)+1,len(@agg))

    End

    Declare cur_agg cursor read_only forward_only local static for
    Select 
        opp_agg,col_agg
    from @column_agg;

    Open cur_agg;

    Fetch Next From cur_agg
    Into @opp_agg,@col_agg;

    While @@fetch_status=0
    Begin

        Declare cur_col cursor read_only forward_only local static for
        Select 
            pivot_col 
        From #pvt_column;

        Open cur_col;

        Fetch Next From cur_col
        Into @pivot_col;

        While @@fetch_status=0
        Begin

            Select @query_col_pvt='isnull('+@opp_agg+'(case when '+@column+'='+quotename(@pivot_col,char(39))+' then '+@col_agg+
            ' else null end),0) as ['+lower(Replace(Replace(@opp_agg+'_'+convert(varchar(100),@pivot_col)+'_'+replace(replace(@col_agg,'[',''),']',''),' ',''),'&',''))+
                (case when @add_to_col_name is null then space(0) else '_'+isnull(ltrim(rtrim(@add_to_col_name)),'') end)+']'
            print @query_col_pvt
            Select @full_query_pivot=@full_query_pivot+@query_col_pvt+', '

            --print @full_query_pivot

            Fetch Next From cur_col
            Into @pivot_col;        

        End     

        Close cur_col;
        Deallocate cur_col;

        Fetch Next From cur_agg
        Into @opp_agg,@col_agg; 
    End

    Close cur_agg;
    Deallocate cur_agg;

    Select @full_query_pivot=substring(@full_query_pivot,1,len(@full_query_pivot)-1);

    Select @query='Select '+@sel_cols+','+@full_query_pivot+' into '+@new_table+' From '+(case when @ind_tmpTbl=1 then 'tempdb.' else '' end)+
    @schema+'.'+@table+' Group by '+@sel_cols+';';

    print @query;
    Exec (@query);

End;
GO

Voici un exemple d'exécution:

Exec dbo.rs_pivot_table @schema=dbo,@table=##TEMPORAL1,@column=tip_liq,@agg='sum([val_liq]),avg([can_liq]),',@sel_cols='cod_emp,cod_con,tip_liq',@new_table=##TEMPORAL1PVT;

puis Select * From ##TEMPORAL1PVTrenverrait:

entrez la description de l'image ici


2
select * from (select name, ID from Empoyee) Visits
    pivot(sum(ID) for name
    in ([Emp1],
    [Emp2],
    [Emp3]
    ) ) as pivottable;

2

Voici une révision de la réponse @Tayrn ci-dessus qui pourrait vous aider à comprendre le pivotement un peu plus facilement:

Ce n'est peut-être pas la meilleure façon de le faire, mais c'est ce qui m'a aidé à comprendre comment faire pivoter les tableaux.

ID = lignes que vous souhaitez faire pivoter

MY_KEY = la colonne que vous sélectionnez dans votre tableau d'origine qui contient les noms de colonnes que vous souhaitez faire pivoter.

VAL = la valeur que vous souhaitez renvoyer sous chaque colonne.

MAX (VAL) => Peut être remplacé par d'autres fonctions d'agrégation. SUM (VAL), MIN (VAL), ETC ...

DECLARE @cols AS NVARCHAR(MAX),
@query  AS NVARCHAR(MAX)
select @cols = STUFF((SELECT ',' + QUOTENAME(MY_KEY) 
                from yt
                group by MY_KEY
                order by MY_KEY ASC
        FOR XML PATH(''), TYPE
        ).value('.', 'NVARCHAR(MAX)') 
    ,1,1,'')
set @query = 'SELECT ID,' + @cols + ' from 
         (
            select ID, MY_KEY, VAL 
            from yt
        ) x
        pivot 
        (
            sum(VAL)
            for MY_KEY in (' + @cols + ')
        ) p '

        execute(@query);

2

Donnez-vous simplement une idée de la façon dont d'autres bases de données résolvent ce problème. DolphinDBa également un support intégré pour le pivotement et le sql semble beaucoup plus intuitif et soigné. C'est aussi simple que de spécifier la colonne clé ( Store), la colonne pivotante ( Week) et la métrique calculée ( sum(xCount)).

//prepare a 10-million-row table
n=10000000
t=table(rand(100, n) + 1 as Store, rand(54, n) + 1 as Week, rand(100, n) + 1 as xCount)

//use pivot clause to generate a pivoted table pivot_t
pivot_t = select sum(xCount) from t pivot by Store, Week

DolphinDB est une base de données hautes performances en colonnes. Le calcul dans la démo coûte aussi peu que 546 ms sur un ordinateur portable dell xps (i7 cpu). Pour obtenir plus de détails, veuillez consulter le manuel DolphinDB en ligne https://www.dolphindb.com/help/index.html?pivotby.html

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.