Comment changer simplement de colonne avec des lignes en SQL?
c'est à dire tourner ce résultat:
Paul | John | Tim | Eric
Red 1 5 1 3
Green 8 4 3 5
Blue 2 2 9 1
dans ceci:
Red | Green | Blue
Paul 1 8 2
John 5 4 2
Tim 1 3 9
Eric 3 5 1
PIVOT
semble trop complexe pour ce scénario.
Vous pouvez transformer ces données de plusieurs manières. Dans votre message d'origine, vous déclariez que PIVOT
semblait trop complexe pour ce scénario, mais il peut être appliqué très facilement à l'aide des fonctions UNPIVOT
ET PIVOT
dans SQL Server.
Toutefois, si vous n'avez pas accès à ces fonctions, vous pouvez le répliquer à l'aide de UNION ALL
en UNPIVOT
, puis d'une fonction d'agrégat avec une instruction CASE
en PIVOT
:
Créer un tableau:
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, version agrégée et 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
Voir SQL Fiddle avec Demo
Le UNION ALL
exécute la UNPIVOT
des données en transformant les colonnes Paul, John, Tim, Eric
en lignes séparées. Ensuite, vous appliquez la fonction d'agrégation sum()
à l'instruction case
pour obtenir les nouvelles colonnes pour chaque color
.
Version statique et pivots croisés:
Les fonctions UNPIVOT
et PIVOT
de SQL Server facilitent beaucoup cette transformation. Si vous connaissez toutes les valeurs que vous souhaitez transformer, vous pouvez les coder en dur dans une version statique pour obtenir le résultat:
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
Voir SQL Fiddle avec Demo
La requête interne avec UNPIVOT
remplit la même fonction que UNION ALL
. Il prend la liste des colonnes et la transforme en lignes, la PIVOT
effectue ensuite la transformation finale en colonnes.
Version de pivot dynamique:
Si vous avez un nombre inconnu de colonnes (Paul, John, Tim, Eric
dans votre exemple), puis un nombre inconnu de couleurs à transformer, vous pouvez utiliser SQL dynamique pour générer la liste à UNPIVOT
, puis 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)
Voir SQL Fiddle avec Demo
La version dynamique interroge à la fois yourtable
et ensuite la table sys.columns
pour générer la liste des éléments à UNPIVOT
et PIVOT
. Ceci est ensuite ajouté à une chaîne de requête à exécuter. Le plus de la version dynamique est que si vous avez une liste changeante de colors
et/ou names
, cela générera la liste au moment de l'exécution.
Les trois requêtes donneront le même résultat:
| NAME | RED | GREEN | BLUE |
-----------------------------
| Eric | 3 | 5 | 1 |
| John | 5 | 4 | 2 |
| Paul | 1 | 8 | 2 |
| Tim | 1 | 3 | 9 |
Cela nécessite normalement de connaître TOUTES les étiquettes de colonne ET de ligne au préalable. Comme vous pouvez le voir dans la requête ci-dessous, les étiquettes sont toutes répertoriées dans leur intégralité à la fois dans les opérations UNPIVOT et (re) PIVOT.
Configuration du schéma 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;
Requête 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 |
J'aimerais souligner quelques solutions supplémentaires pour la transposition de colonnes et de lignes en SQL.
Le premier est - en utilisant CURSOR. Bien que le consensus général dans la communauté professionnelle soit de rester à l’écart des curseurs SQL Server, il est toujours recommandé d’utiliser des curseurs. Quoi qu'il en soit, les curseurs nous proposent une autre option pour transposer des lignes en colonnes.
Expansion verticale
Semblable au PIVOT, le curseur a la capacité dynamique d’ajouter plus de lignes à mesure que votre jeu de données se développe pour inclure plus de numéros de politique.
Expansion horizontale
Contrairement au PIVOT, le curseur excelle dans cette zone car il peut être étendu pour inclure le document ajouté récemment, sans modifier le script.
Ventilation des performances
La principale limitation de la transposition de lignes en colonnes à l'aide de CURSOR est un inconvénient lié à l'utilisation de curseurs en général: leur coût de performance est considérable. En effet, le curseur génère une requête distincte pour chaque opération FETCH NEXT.
Une autre solution de transposition de lignes en colonnes consiste à utiliser XML.
La solution XML pour la transposition de lignes en colonnes est fondamentalement une version optimale du PIVOT dans la mesure où elle aborde la limitation dynamique des colonnes.
La version XML du script corrige cette limitation en combinant chemin XML, T-SQL dynamique et certaines fonctions intégrées (c'est-à-dire STUFF, QUOTENAME).
Expansion verticale
Semblables au PIVOT et au Curseur, les nouvelles politiques ajoutées peuvent être récupérées dans la version XML du script sans modifier le script d'origine.
Expansion horizontale
Contrairement au PIVOT, les nouveaux documents ajoutés peuvent être affichés sans modifier le script.
Ventilation des performances
En termes d'E/S, les statistiques de la version XML du script sont presque similaires à celles du PIVOT - la seule différence est que le XML a une seconde analyse de la table dtTranspose, mais cette fois à partir d'un cache logique de lecture de données.
Vous trouverez plus d'informations sur ces solutions (y compris quelques exemples réels de T-SQL) dans cet article: https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/
Basé sur cette solution from bluefeet , voici une procédure stockée qui utilise SQL dynamique pour générer la table transposée. Il nécessite que tous les champs soient numériques sauf la colonne transposée (la colonne qui sera l'en-tête dans la table résultante):
/****** 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
Vous pouvez le tester avec la table fournie avec cette commande:
exec SQLTranspose 'yourTable', 'color'
Je fais d'abord UnPivot
et stocke les résultats dans CTE
et j'utilise l'opération CTE
dans 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;
En ajoutant à la réponse formidable de @Paco Zarate ci-dessus, si vous souhaitez transposer une table comportant plusieurs types de colonnes, ajoutez-la ensuite à la fin de la ligne 39, de sorte qu'elle ne transpose que les colonnes int
:
and C.system_type_id = 56 --56 = type int
Voici la requête complète en cours de modification:
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, '')
Pour trouver d'autres system_type_id
, lancez ceci:
select name, system_type_id from sys.types order by name
J'aime partager le code que j'utilise pour transposer un texte fractionné basé sur la réponse + bluefeet. Dans cette approche, je suis implémenté comme une procédure dans 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
Je mélange cette solution avec les informations sur la procédure à suivre pour ordonner les lignes sans ordre par ( SQLAuthority.com ) et la fonction de fractionnement sur MSDN ( social.msdn.Microsoft.com )
Quand vous exécutez le 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
tu obtiens le résultat suivant
name rowid 1 2 3
col_value 1 hello beautiful world
De cette façon, convertissez toutes les données des champs (colonnes) de la table en enregistrement (ligne).
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
J'ai pu utiliser la solution de Paco Zarate et cela fonctionne à merveille. J'ai dû ajouter une ligne ("SET ANSI_WARNINGS ON"), mais il se peut que cela corresponde à la façon dont je l'ai utilisée ou appelée. Il y a un problème d'utilisation et j'espère que quelqu'un pourra m'aider à résoudre ce problème:
La solution ne fonctionne qu'avec une table SQL réelle. Je l'ai essayé avec une table temporaire et aussi une table (déclarée) en mémoire, mais cela ne fonctionne pas avec ceux-ci. Donc, dans mon code d'appel, je crée une table sur ma base de données SQL puis appelle SQLTranspose. Encore une fois, cela fonctionne très bien. C'est juste ce que je veux. Voici mon problème:
Pour que la solution globale soit réellement dynamique, je dois créer cette table dans laquelle je stocke temporairement "à la volée" les informations préparées que j'envoie à SQLTranspose, puis je supprime cette table une fois que SQLTranspose est appelé. La suppression de la table pose un problème avec mon plan de mise en œuvre ultime. Le code doit être exécuté à partir d'une application d'utilisateur final (un bouton d'un formulaire/menu Microsoft Access). Lorsque j'utilise ce processus SQL (créer une table SQL, appeler SQLTranspose, supprimer une table SQL), l'application de l'utilisateur final génère une erreur car le compte SQL utilisé ne dispose pas des droits nécessaires pour supprimer une table.
Je suppose donc qu'il existe plusieurs solutions possibles:
Trouvez un moyen de faire fonctionner SQLTranspose avec une table temporaire ou une variable de table déclarée.
Découvrez une autre méthode pour la transposition de lignes et de colonnes ne nécessitant pas de table SQL réelle.
Trouvez une méthode appropriée permettant au compte SQL utilisé par mes utilisateurs finaux de supprimer une table. C'est un compte SQL partagé unique codé dans mon application Access. Il semble que l'autorisation soit un privilège de type dbo qui ne peut pas être accordé.
Je reconnais que certaines de ces choses peuvent justifier un autre fil séparé et une question. Cependant, comme il est possible qu'une solution soit simplement une façon différente de transposer des lignes et des colonnes, je ferai mon premier article ici dans ce fil de discussion.
EDIT: J'ai également remplacé sum (value) par max (value) dans la 6ème ligne à partir de la fin, comme suggéré par Paco.
MODIFIER:
J'ai compris quelque chose qui fonctionne pour moi. Je ne sais pas si c'est le le meilleur réponds ou non.
J'ai un compte utilisateur en lecture seule qui est utilisé pour exécuter des procédures strored et donc pour générer des rapports de sortie à partir d'une base de données. Comme la fonction SQLTranspose que j'ai créée ne fonctionnera qu'avec une table "légitime" (pas une table déclarée ni une table temporaire), je devais trouver un moyen pour un compte utilisateur en lecture seule de créer (puis de supprimer) une table. .
Je pensais que pour mes besoins, il était normal que le compte d'utilisateur soit autorisé à créer une table. L'utilisateur n'a toujours pas pu supprimer la table. Ma solution consistait à créer un schéma dans lequel le compte d'utilisateur est autorisé. Ensuite, chaque fois que je crée, utilise ou supprime cette table, la référence avec le schéma spécifié.
J'ai d'abord émis cette commande à partir d'un compte 'sa' ou 'sysadmin': CREATE SCHEMA ro AUTHORIZATION
Chaque fois que je fais référence à ma table "tmpoutput", je le spécifie comme suit:
déposer la table ro.tmpoutput