J'ai une table qui ressemble à ceci:
RECIPE VERSION_ID INGREDIENT PERCENTAGE
4000 100 Ing_1 23,0
4000 100 Ing_100 0,1
4000 200 Ing_1 20,0
4000 200 Ing_100 0,7
4000 300 Ing_1 22,3
4000 300 Ing_100 0,9
4001 900 Ing_1 8,3
4001 900 Ing_100 72,4
4001 901 Ing_1 9,3
4001 901 Ing_100 70,5
5012 871 Ing_1 45,1
5012 871 Ing_100 0,9
5012 877 Ing_1 47,2
5012 877 Ing_100 0,8
5012 879 Ing_1 46,6
5012 879 Ing_100 0,9
5012 880 Ing_1 43,6
5012 880 Ing_100 1,2
Il y a 100 ingrédients par recette/version. Je voudrais afficher les données de ce tableau comme ceci:
RECIPE INGREDIENT_Vxxx PERCENTAGE_Vxxx INGREDIENT_Vyyy INGREDIENT_Vyyy (ETC)
4000 Ing_1 23,0 Ing_1 20,0
4000 Ing_100 0,1 Ing_100 0,7
Parce que dans différentes versions de recettes, des ingrédients peuvent être supprimés ou ajoutés, je voudrais afficher à la fois l'ingrédient et le pourcentage par version par recette. Il y a aussi la difficulté que différentes recettes ont un nombre différent de versions.
Je ne sais même pas si c'est possible du tout ou par où commencer. Peut-être avec la fonction PIVOT
?
Quelqu'un pourrait-il m'orienter dans la bonne direction?
Le problème ici me semble être davantage un problème de portée - vous avez probablement de la difficulté à résoudre ce problème car les exigences ne sont pas suffisamment définies. Avec la description et les exemples de données fournis, il existe au moins trois solutions partielles, dont aucune ne peut être applicable à vos cas d'utilisation particuliers. Avec les données de test configurées comme suit,
IF NOT EXISTS ( SELECT 1
FROM sys.objects
WHERE name = 'Recipe'
AND type = 'U' )
BEGIN
--DROP TABLE dbo.Recipe;
CREATE TABLE dbo.Recipe
(
Recipe INTEGER NOT NULL,
VersionID INTEGER NOT NULL,
Ingredient VARCHAR( 8 ) NOT NULL,
Percentage DECIMAL( 5, 2 )
);
INSERT INTO dbo.Recipe ( Recipe, VersionID, Ingredient, Percentage )
SELECT 4000, 100, 'Ing_1', 23.0
UNION ALL SELECT 4000, 100, 'Ing_100', 0.1
UNION ALL SELECT 4000, 200, 'Ing_1', 20.0
UNION ALL SELECT 4000, 200, 'Ing_100', 0.7
UNION ALL SELECT 4000, 300, 'Ing_1', 22.3
UNION ALL SELECT 4000, 300, 'Ing_100', 0.9
UNION ALL SELECT 4001, 900, 'Ing_1', 8.3
UNION ALL SELECT 4001, 900, 'Ing_100', 72.4
UNION ALL SELECT 4001, 901, 'Ing_1', 9.3
UNION ALL SELECT 4001, 901, 'Ing_100', 70.5
UNION ALL SELECT 5012, 871, 'Ing_1', 45.1
UNION ALL SELECT 5012, 871, 'Ing_100', 0.9
UNION ALL SELECT 5012, 877, 'Ing_1', 47.2
UNION ALL SELECT 5012, 877, 'Ing_100', 0.8
UNION ALL SELECT 5012, 879, 'Ing_1', 46.6
UNION ALL SELECT 5012, 879, 'Ing_100', 0.9
UNION ALL SELECT 5012, 880, 'Ing_1', 43.6
UNION ALL SELECT 5012, 880, 'Ing_100', 1.2;
ALTER TABLE dbo.Recipe
ADD CONSTRAINT PK__Recipe
PRIMARY KEY CLUSTERED ( Recipe, VersionID, Ingredient );
CREATE NONCLUSTERED INDEX IX__Recipe__Recipe__VersionID
ON dbo.Recipe ( Recipe, VersionID )
INCLUDE ( Percentage );
END;
GO
nous pouvons utiliser notre nouveau tableau pour explorer quelques solutions possibles. En développant l'exemple de sortie, nous ajoutons la recette suivante dans l'ensemble de résultats pour illustrer la difficulté de la question.
RECIPE --- INGREDIENT_V100 --- PERCENTAGE_V100 --- INGREDIENT_V200 --- INGREDIENT_V200
4000 Ing_1 23,0 Ing_1 20,0
4000 Ing_100 0,1 Ing_100 0,7
4001 Ing_1 8,3 Ing_1 9,3
4001 Ing_100 72,4 Ing_100 70,5
Colonnes %_V100
et %_V200
est parfaitement logique dans le cas de 4000
recette, mais perdent rapidement leur signification à mesure que des recettes supplémentaires sont ajoutées. Le 4001
la recette aurait besoin de nouvelles colonnes séparées pour étiqueter correctement les données par version, mais comme les numéros de version diffèrent pour chaque recette, ce chemin nous mène à un ensemble de résultats très clairsemé qui serait carrément ennuyeux à utiliser, ou nous devons alias les colonnes, perdant les données du numéro de version.
En commençant par ce que je pense être la pire façon de procéder, regardons l'ensemble de résultats clairsemé. Pour les exemples de données, nous tenterions de générer une requête qui ressemblerait à quelque chose dans les lignes suivantes:
SELECT p.Recipe,
[Ingredient_v100] = CASE WHEN p.[100] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v100] = p.[100],
[Ingredient_v200] = CASE WHEN p.[200] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v200] = p.[200],
[Ingredient_v300] = CASE WHEN p.[300] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v300] = p.[300],
[Ingredient_v871] = CASE WHEN p.[871] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v871] = p.[871],
[Ingredient_v877] = CASE WHEN p.[877] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v877] = p.[877],
[Ingredient_v879] = CASE WHEN p.[879] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v879] = p.[879],
[Ingredient_v880] = CASE WHEN p.[880] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v880] = p.[880],
[Ingredient_v900] = CASE WHEN p.[900] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v900] = p.[900],
[Ingredient_v901] = CASE WHEN p.[901] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v901] = p.[901]
FROM ( SELECT r.Recipe,
r.VersionID,
r.Ingredient,
r.Percentage
FROM dbo.Recipe r ) s
PIVOT ( MAX( s.Percentage )
FOR s.VersionID IN ( [100], [200], [300], [871], [877], [879], [880], [900], [901] ) ) p
ORDER BY p.Recipe;
En raison du nombre variable de versions, nous pouvons utiliser du SQL dynamique pour générer et exécuter la requête.
DECLARE @Piv NVARCHAR( MAX ),
@Col NVARCHAR( MAX ),
@SQL NVARCHAR( MAX );
SELECT @Piv = LEFT( b.Piv, LEN( b.Piv ) - 1 )
FROM ( SELECT N'[' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '], '
FROM ( SELECT DISTINCT r.VersionID
FROM dbo.Recipe r ) a
ORDER BY a.VersionID
FOR XML PATH ( '' ) ) b ( Piv );
SELECT @Col = LEFT( b.Piv, LEN( b.Piv ) - 1 )
FROM ( SELECT N'[Ingredient_v' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '] = CASE'
+ ' WHEN p.[' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '] IS NULL THEN NULL'
+ ' ELSE p.[Ingredient] END, '
+ '[Percentage_v' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '] = p.['
+ CONVERT( VARCHAR( 8 ), a.VersionID ) + '], '
FROM ( SELECT DISTINCT r.VersionID
FROM dbo.Recipe r ) a
ORDER BY a.VersionID
FOR XML PATH ( '' ) ) b ( Piv );
SET @SQL = N'
SELECT p.Recipe, ' + @Col + '
FROM ( SELECT r.Recipe,
r.VersionID,
r.Ingredient,
r.Percentage
FROM dbo.Recipe r ) s
PIVOT ( MAX( s.Percentage )
FOR s.VersionID IN ( ' + @Piv + ' ) ) p
ORDER BY p.Recipe;';
EXECUTE dbo.sp_executesql @statement = @SQL;
GO
Ce résultat est tout simplement nul. Voici un SQL Fiddle qui affiche les résultats, jetez un œil et passons à autre chose.
Le jeu de résultats clairsemé se révélant presque inutile, nous pouvons accepter de perdre les numéros de version des recettes et simplement les commander en numéro de version croissant. Pour les besoins de l'exemple, nous allons alias par ordre alphabétique, de sorte que les versions 100
, 200
et 300
de la recette 4000
recevra A
, B
et C
désignations, tandis que les versions 900
et 901
ne recevra que A
et B
. La requête que nous aimerions générer pour cela devrait ressembler à ceci:
SELECT p.Recipe,
[Ingredient_vA] = p.[Ingredient], [Percentage_vA] = ISNULL( p.[Percentage_vA], 0 ),
[Ingredient_vB] = p.[Ingredient], [Percentage_vB] = ISNULL( p.[Percentage_vB], 0 ),
[Ingredient_vC] = p.[Ingredient], [Percentage_vC] = ISNULL( p.[Percentage_vC], 0 ),
[Ingredient_vD] = p.[Ingredient], [Percentage_vD] = ISNULL( p.[Percentage_vD], 0 )
FROM ( SELECT Lvl = 'Percentage_v' + CHAR( 64 +
DENSE_RANK() OVER (
PARTITION BY r.Recipe
ORDER BY r.VersionID ) ),
r.Recipe,
r.Ingredient,
r.Percentage
FROM dbo.Recipe r ) s
PIVOT ( MAX( s.Percentage )
FOR s.Lvl IN ( [Percentage_vA], [Percentage_vB], [Percentage_vC], [Percentage_vD] ) ) p
ORDER BY p.Recipe;
De manière similaire à la première solution, le SQL dynamique peut être exploité pour y parvenir.
DECLARE @Piv NVARCHAR( MAX ),
@Col NVARCHAR( MAX ),
@SQL NVARCHAR( MAX );
SELECT @Piv = LEFT( b.Piv, LEN( b.Piv ) - 1 )
FROM ( SELECT N'[Percentage_v' + CHAR( 64 + a.Lvl ) + '], '
FROM ( SELECT DISTINCT Lvl = DENSE_RANK()
OVER ( PARTITION BY r.Recipe
ORDER BY r.VersionID )
FROM dbo.Recipe r ) a
ORDER BY a.Lvl
FOR XML PATH ( '' ) ) b ( Piv );
SELECT @Col = LEFT( b.Col, LEN( b.Col ) - 1 )
FROM ( SELECT N'[Ingredient_v' + CHAR( 64 + a.Lvl ) + '] = p.[Ingredient], '
+ '[Percentage_v' + CHAR( 64 + a.Lvl ) + '] = ISNULL( p.[Percentage_v'
+ CHAR( 64 + a.Lvl ) + '], 0 ),'
FROM ( SELECT DISTINCT Lvl = DENSE_RANK()
OVER ( PARTITION BY r.Recipe
ORDER BY r.VersionID )
FROM dbo.Recipe r ) a
ORDER BY a.Lvl
FOR XML PATH ( '' ) ) b ( Col );
SET @SQL = N'
SELECT p.Recipe, ' + @Col + '
FROM ( SELECT Lvl = ''Percentage_v'' + CHAR( 64 +
DENSE_RANK() OVER (
PARTITION BY r.Recipe
ORDER BY r.VersionID ) ),
r.Recipe,
r.Ingredient,
r.Percentage
FROM dbo.Recipe r ) s
PIVOT ( MAX( s.Percentage )
FOR s.Lvl IN ( ' + @Piv + ' ) ) p
ORDER BY p.Recipe;';
EXECUTE dbo.sp_executesql @statement = @SQL;
GO
Cela se termine par un ensemble de résultats beaucoup plus joli, comme on peut le voir dans ce SQL Fiddle , malgré la perte des numéros de version spécifiques de chaque recette.
Si la perte des numéros de version ne peut être tolérée, une approche hybride peut être mise en œuvre, bien que les résultats de chaque appel soient limités à une seule recette. En effet, notre objectif SQL serait similaire à la première solution, mais avec un nombre Recipe
expressément défini.
SELECT p.Recipe,
[Ingredient_v100] = CASE WHEN p.[100] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v100] = p.[100],
[Ingredient_v200] = CASE WHEN p.[200] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v200] = p.[200],
[Ingredient_v300] = CASE WHEN p.[300] IS NULL THEN NULL ELSE p.[Ingredient] END, [Percentage_v300] = p.[300]
FROM ( SELECT r.Recipe,
r.VersionID,
r.Ingredient,
r.Percentage
FROM dbo.Recipe r
WHERE r.Recipe = @Recipe ) s
PIVOT ( MAX( s.Percentage )
FOR s.VersionID IN ( [100], [200], [300] ) ) p
ORDER BY p.Recipe;
La génération pourrait être gérée comme suit:
DECLARE @Piv NVARCHAR( MAX ),
@Col NVARCHAR( MAX ),
@Param NVARCHAR( MAX ),
@SQL NVARCHAR( MAX ),
@Recipe INTEGER = 4000;
SELECT @Piv = LEFT( b.Piv, LEN( b.Piv ) - 1 )
FROM ( SELECT N'[' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '], '
FROM ( SELECT DISTINCT r.VersionID
FROM dbo.Recipe r
WHERE Recipe = @Recipe ) a
ORDER BY a.VersionID
FOR XML PATH ( '' ) ) b ( Piv );
SELECT @Col = LEFT( b.Piv, LEN( b.Piv ) - 1 )
FROM ( SELECT N'[Ingredient_v' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '] = CASE'
+ ' WHEN p.[' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '] IS NULL THEN NULL'
+ ' ELSE p.[Ingredient] END, '
+ '[Percentage_v' + CONVERT( VARCHAR( 8 ), a.VersionID ) + '] = p.['
+ CONVERT( VARCHAR( 8 ), a.VersionID ) + '], '
FROM ( SELECT DISTINCT r.VersionID
FROM dbo.Recipe r
WHERE Recipe = @Recipe ) a
ORDER BY a.VersionID
FOR XML PATH ( '' ) ) b ( Piv );
SET @Param = N'@Recipe INTEGER';
SET @SQL = N'
SELECT p.Recipe, ' + @Col + '
FROM ( SELECT r.Recipe,
r.VersionID,
r.Ingredient,
r.Percentage
FROM dbo.Recipe r
WHERE r.Recipe = @Recipe ) s
PIVOT ( MAX( s.Percentage )
FOR s.VersionID IN ( ' + @Piv + ' ) ) p
ORDER BY p.Recipe;';
EXECUTE dbo.sp_executesql @statement = @SQL, @param = @Param, @Recipe = @Recipe;
GO
Les résultats peuvent ensuite être consultés par recette, comme indiqué dans ce SQL Fiddle ou celui-ci .