Je suis dans une situation où je veux obtenir la valeur minimale de 6 colonnes.
J'ai trouvé jusqu'à présent trois façons d'accomplir cela, mais je suis préoccupé par les performances de ces méthodes et j'aimerais savoir laquelle serait la meilleure pour les performances.
La première méthode consiste à utiliser ne grande déclaration de cas . Voici un exemple avec 3 colonnes, basé sur l'exemple du lien ci-dessus. Ma déclaration de cas serait beaucoup plus longue car je regarderai 6 colonnes.
Select Id,
Case When Col1 <= Col2 And Col1 <= Col3 Then Col1
When Col2 <= Col3 Then Col2
Else Col3
End As TheMin
From MyTable
La deuxième option consiste à utiliser l'opérateur UNION
avec plusieurs instructions select . Je mettrais cela dans un UDF qui accepte un paramètre Id.
select Id, dbo.GetMinimumFromMyTable(Id)
from MyTable
et
select min(col)
from
(
select col1 [col] from MyTable where Id = @id
union all
select col2 from MyTable where Id = @id
union all
select col3 from MyTable where Id = @id
) as t
Et la troisième option que j'ai trouvée était de tiliser l'opérateur UNPIVOT , dont je ne savais même pas qu'il existait jusqu'à présent
with cte (ID, Col1, Col2, Col3)
as
(
select ID, Col1, Col2, Col3
from TestTable
)
select cte.ID, Col1, Col2, Col3, TheMin from cte
join
(
select
ID, min(Amount) as TheMin
from
cte
UNPIVOT (Amount for AmountCol in (Col1, Col2, Col3)) as unpvt
group by ID
) as minValues
on cte.ID = minValues.ID
En raison de la taille de la table et de la fréquence à laquelle cette table est interrogée et mise à jour, je suis préoccupé par l'impact sur les performances que ces requêtes auraient sur la base de données.
Cette requête sera en fait utilisée dans une jointure à une table avec quelques millions d'enregistrements, mais les enregistrements retournés seront réduits à une centaine d'enregistrements à la fois. Il sera exécuté plusieurs fois au cours de la journée et les 6 colonnes que j'interroge sont fréquemment mises à jour (elles contiennent des statistiques quotidiennes). Je ne pense pas qu'il y ait d'index sur les 6 colonnes que j'interroge.
Laquelle de ces méthodes est meilleure pour les performances lorsque vous essayez d'obtenir le minimum de plusieurs colonnes? Ou existe-t-il une autre meilleure méthode que je ne connais pas?
J'utilise SQL Server 2005
Exemples de données et résultats
Si mes données contenaient des enregistrements comme celui-ci:
Id Col1 Col2 Col3 Col4 Col5 Col6 1 3 4 0 2 1 5 2 2 6 10 5 7 9 3 1 1 2 3 4 5 4 9 5 4 6 8 9
Le résultat final devrait être
Id Valeur 1 0 2 2 3 1 4 4
J'ai testé les performances des 3 méthodes, et voici ce que j'ai trouvé:
UNION
était un peu plus lente. Le CASE WHEN
la requête est un peu plus rapide que celle de UNPIVOT
.UNION
est beaucoup plus lente, mais la requête UNPIVOT
devient un peu plus rapide que la requête CASE WHEN
requeteUNION
la sous-requête est encore beaucoup plus lente, mais UNPIVOT
devient beaucoup plus rapide que le CASE WHEN
requeteDonc, les résultats finaux semblent être
Avec des jeux d'enregistrements plus petits, il ne semble pas y avoir suffisamment de différence. Utilisez ce qui est le plus facile à lire et à entretenir.
Une fois que vous commencez à entrer dans des jeux d'enregistrements plus volumineux, le UNION ALL
la sous-requête commence à mal fonctionner par rapport aux deux autres méthodes.
L'instruction CASE
donne les meilleurs résultats jusqu'à un certain point (dans mon cas, environ 100k lignes), et quel point la requête UNPIVOT
devient la requête la plus performante
Le nombre réel auquel une requête devient meilleure qu'une autre changera probablement en raison de votre matériel, du schéma de base de données, des données et de la charge actuelle du serveur, alors assurez-vous de tester avec votre propre système si vous êtes préoccupé par les performances.
J'ai également effectué des tests en utilisant la réponse de Mikael ; cependant, il était plus lent que les 3 autres méthodes essayées ici pour la plupart des tailles de jeux d'enregistrements. La seule exception était qu'il faisait mieux qu'un UNION ALL
requête pour les très grandes tailles de jeux d'enregistrements. J'aime le fait qu'il montre le nom de la colonne en plus de la plus petite valeur.
Je ne suis pas un dba, donc je n'ai peut-être pas optimisé mes tests et manqué quelque chose. Je testais avec les données réelles réelles, donc cela peut avoir affecté les résultats. J'ai essayé de tenir compte de cela en exécutant chaque requête à plusieurs reprises, mais on ne sait jamais. Je serais certainement intéressé si quelqu'un écrivait un test clair de cela et partageait ses résultats.
Je ne sais pas ce qui est le plus rapide mais vous pouvez essayer quelque chose comme ça.
declare @T table
(
Col1 int,
Col2 int,
Col3 int,
Col4 int,
Col5 int,
Col6 int
)
insert into @T values(1, 2, 3, 4, 5, 6)
insert into @T values(2, 3, 1, 4, 5, 6)
select T4.ColName, T4.ColValue
from @T as T1
cross apply (
select T3.ColValue, T3.ColName
from (
select row_number() over(order by T2.ColValue) as rn,
T2.ColValue,
T2.ColName
from (
select T1.Col1, 'Col1' union all
select T1.Col2, 'Col2' union all
select T1.Col3, 'Col3' union all
select T1.Col4, 'Col4' union all
select T1.Col5, 'Col5' union all
select T1.Col6, 'Col6'
) as T2(ColValue, ColName)
) as T3
where T3.rn = 1
) as T4
Résultat:
ColName ColValue
------- -----------
Col1 1
Col3 1
Si vous n'êtes pas intéressé par la colonne qui a la valeur min, vous pouvez l'utiliser à la place.
declare @T table
(
Id int,
Col1 int,
Col2 int,
Col3 int,
Col4 int,
Col5 int,
Col6 int
)
insert into @T
select 1, 3, 4, 0, 2, 1, 5 union all
select 2, 2, 6, 10, 5, 7, 9 union all
select 3, 1, 1, 2, 3, 4, 5 union all
select 4, 9, 5, 4, 6, 8, 9
select T.Id, (select min(T1.ColValue)
from (
select T.Col1 union all
select T.Col2 union all
select T.Col3 union all
select T.Col4 union all
select T.Col5 union all
select T.Col6
) as T1(ColValue)
) as ColValue
from @T as T
Une requête de pivot simplifiée.
select Id, min(ColValue) as ColValue
from @T
unpivot (ColValue for Col in (Col1, Col2, Col3, Col4, Col5, Col6)) as U
group by Id
Ajoutez une colonne calculée persistante qui utilise une instruction CASE
pour effectuer la logique dont vous avez besoin.
La valeur minimale sera alors toujours disponible efficacement lorsque vous aurez besoin de faire une jointure (ou quoi que ce soit d'autre) sur la base de cette valeur.
La valeur sera recalculée chaque fois que l'une des valeurs source change (INSERT
/UPDATE
/MERGE
). Je ne dis pas que c'est nécessairement la meilleure solution pour la charge de travail, je la propose simplement comme solution a, tout comme les autres réponses. Seul l'OP peut déterminer celui qui convient le mieux à la charge de travail.
Votre instruction case
n'est pas efficace. Vous faites 5 comparaisons dans le pire des cas et 2 dans le meilleur des cas; alors que trouver le minimum de n
devrait faire tout au plus n-1
comparaisons.
Pour chaque ligne, vous effectuez en moyenne 3,5 comparaisons au lieu de 2. Ainsi, cela prend plus de temps processeur et est lent. Recommencez vos tests en utilisant l'instruction case
ci-dessous. Il utilise simplement 2 comparaisons par ligne et devrait être plus efficace que unpivot
et union all
.
Select Id,
Case
When Col1 <= Col2 then case when Col1 <= Col3 Then Col1 else col3 end
When Col2 <= Col3 Then Col2
Else Col3
End As TheMin
From YourTableNameHere
Le union all
la méthode est incorrecte dans votre cas car vous obtenez la valeur minimale non pas par ligne mais pour la table entière. De plus, il ne sera pas efficace car vous allez scanner le même tableau 3 fois. Lorsque la table est petite, les E/S ne feront pas beaucoup de différence, mais pour les grandes tables, ce sera le cas. N'utilisez pas cette méthode.
Unpivot
est bon et essayez également de débloquer manuellement en utilisant cross join votre table avec (select 1 union all select 2 union all select 3)
. Il devrait être aussi efficace que le unpivot
.
La meilleure solution serait d'avoir une colonne persistante calculée, si vous n'avez pas de problèmes d'espace. Cela augmentera la taille de la ligne de 4 octets (je suppose que vous aurez le type int
), ce qui augmentera à son tour la taille de la table.
Cependant, l'espace et la mémoire sont des problèmes dans votre système et le CPU n'est pas alors ne le faites pas persister mais utilisez une colonne calculée simple en utilisant l'instruction case. Cela rendra le code plus simple.
Déclaration de cas pour 6 dates. Pour faire moins, copiez la vraie branche de la première instruction case. Le pire des cas est lorsque Date1 est la valeur la plus basse, le meilleur des cas est lorsque Date6 est la valeur la plus basse, alors mettez la date la plus probable dans Date6. J'ai écrit cela en raison des limites des colonnes calculées.
CASE WHEN Date1 IS NULL OR Date1 > Date2 THEN
CASE WHEN Date2 IS NULL OR Date2 > Date3 THEN
CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
Date6
ELSE
Date3
END
END
END
ELSE
CASE WHEN Date2 IS NULL OR Date2 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
END
ELSE
CASE WHEN Date2 IS NULL OR Date2 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date2 IS NULL OR Date2 > Date6 THEN
Date6
ELSE
Date2
END
END
END
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date3 THEN
CASE WHEN Date3 IS NULL OR Date3 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date3 IS NULL OR Date3 > Date6 THEN
Date6
ELSE
Date3
END
END
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date4 THEN
CASE WHEN Date4 IS NULL OR Date4 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date4 IS NULL OR Date4 > Date6 THEN
Date6
ELSE
Date4
END
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date5 THEN
CASE WHEN Date5 IS NULL OR Date5 > Date6 THEN
Date6
ELSE
Date5
END
ELSE
CASE WHEN Date1 IS NULL OR Date1 > Date6 THEN
Date6
ELSE
Date1
END
END
END
END
END
Si vous êtes tombé sur cette page en cherchant simplement à comparer les dates et que vous n'êtes pas aussi préoccupé par les performances ou la compatibilité, vous pouvez utiliser un constructeur de valeur de table, qui peut être utilisé partout où les sous-sélections sont autorisées (SQL Server 2008 et versions ultérieures):
Lowest =
(
SELECT MIN(TVC.d)
FROM
(
VALUES
(Date1),
(Date2),
(Date3),
(Date4),
(Date5),
(Date6)
)
AS TVC(d)
)