web-dev-qa-db-fra.com

Quelle est la façon la plus efficace d'obtenir le minimum de plusieurs colonnes sur SQL Server 2005?

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 
29
Rachel

J'ai testé les performances des 3 méthodes, et voici ce que j'ai trouvé:

  • 1 enregistrement: Pas de différence notable
  • 10 enregistrements: pas de différence notable
  • 1000 enregistrements: pas de différence notable
  • 10 000 enregistrements: la sous-requête UNION était un peu plus lente. Le CASE WHEN la requête est un peu plus rapide que celle de UNPIVOT.
  • 100 000 enregistrements: la sous-requête UNION est beaucoup plus lente, mais la requête UNPIVOT devient un peu plus rapide que la requête CASE WHEN requete
  • 500 000 enregistrements: UNION la sous-requête est encore beaucoup plus lente, mais UNPIVOT devient beaucoup plus rapide que le CASE WHEN requete

Donc, 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.

22
Rachel

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
6
Mikael Eriksson

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.

6
Jon Seigel

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.

1
Gulli Meel

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)
)
1
Jesse Adam