110 votes

Quelle est la meilleure façon de sélectionner la valeur minimale dans plusieurs colonnes ?

Étant donné le tableau suivant dans SQL Server 2005 :

ID   Col1   Col2   Col3
--   ----   ----   ----
1       3     34     76  
2      32    976     24
3       7    235      3
4     245      1    792

Quelle est la meilleure façon d'écrire la requête qui donne le résultat suivant (c'est-à-dire une requête qui donne la colonne finale - une colonne contenant les valeurs minimales de Col1, Col2 et Col 3) ? pour chaque ligne ) ?

ID   Col1   Col2   Col3  TheMin
--   ----   ----   ----  ------
1       3     34     76       3
2      32    976     24      24
3       7    235      3       3
4     245      1    792       1

UPDATE :

Pour plus de clarté (comme je l'ai dit dans les commentaires), dans le scénario réel, la base de données est la suivante correctement normalisé . Ces colonnes "tableau" ne se trouvent pas dans un tableau réel mais dans un ensemble de résultats requis dans un rapport. Et la nouvelle exigence est que le rapport a également besoin de cette colonne MinValue. Je ne peux pas modifier l'ensemble de résultats sous-jacent et je me suis donc tourné vers T-SQL pour obtenir une carte de sortie de prison pratique.

J'ai essayé l'approche CASE mentionnée ci-dessous et elle fonctionne, bien qu'elle soit un peu lourde. Elle est également plus compliquée que ce qui est indiqué dans les réponses, car il faut tenir compte du fait qu'il y a deux valeurs minimales dans la même ligne.

Quoi qu'il en soit, j'ai pensé publier ma solution actuelle qui, compte tenu de mes contraintes, fonctionne assez bien. Elle utilise l'opérateur UNPIVOT :

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

Je dirai d'emblée que je ne m'attends pas à ce que cela offre les meilleures performances, mais compte tenu des circonstances (je ne peux pas revoir toutes les requêtes uniquement pour la nouvelle exigence de la colonne MinValue), il s'agit d'une "carte de sortie de prison" assez élégante.

6voto

paxdiablo Points 341644

La meilleure façon de procéder est probablement no pour le faire - il est étrange que les gens insistent pour stocker leurs données d'une manière qui nécessite une "gymnastique" SQL pour extraire des informations significatives, alors qu'il y a des moyens beaucoup plus faciles d'obtenir le résultat souhaité si vous structurez simplement un peu mieux votre schéma :-)

Les droit La meilleure façon de procéder, à mon avis, est d'utiliser le tableau suivant :

ID    Col    Val
--    ---    ---
 1      1      3
 1      2     34
 1      3     76

 2      1     32
 2      2    976
 2      3     24

 3      1      7
 3      2    235
 3      3      3

 4      1    245
 4      2      1
 4      3    792

con ID/Col comme clé primaire (et éventuellement Col comme clé supplémentaire, selon vos besoins). Votre requête devient alors une simple select min(val) from tbl et vous pouvez toujours traiter les "anciennes colonnes" séparément en utilisant where col = 2 dans vos autres requêtes. Cela permet également une extension facile si le nombre d'"anciennes colonnes" augmente.

Cela permet à vos requêtes donc beaucoup plus facile. La ligne de conduite générale que j'ai tendance à suivre est la suivante : si vous toujours Si vous avez quelque chose qui ressemble à un tableau dans une ligne de la base de données, vous faites probablement quelque chose de mal et vous devriez envisager de restructurer les données.


Toutefois, si pour une raison quelconque vous ne peut modifier ces colonnes, je suggère d'utiliser des déclencheurs d'insertion et de mise à jour et d'ajouter des autre colonne que ces déclencheurs fixent au minimum sur Col1/2/3 . Le "coût" de l'opération sera ainsi déplacé de la sélection vers la mise à jour/l'insertion, où il doit être. D'après mon expérience, la plupart des tables de base de données sont lues beaucoup plus souvent qu'elles ne sont écrites, de sorte que le coût de l'écriture tend à être plus efficace au fil du temps.

En d'autres termes, le minimum d'une ligne ne change que lorsque l'une des autres colonnes change. c'est lorsque vous devez la calculer, et non à chaque fois que vous sélectionnez (ce qui est inutile si les données ne changent pas). Vous obtiendriez alors un tableau comme celui-ci :

ID   Col1   Col2   Col3   MinVal
--   ----   ----   ----   ------
 1      3     34     76        3
 2     32    976     24       24
 3      7    235      3        3
 4    245      1    792        1

Toute autre option qui doit prendre des décisions à select L'ajout d'une colonne supplémentaire prend plus de place dans la base de données et sera légèrement plus lent pour les insertions et les mises à jour, mais il peut être plus facile de l'utiliser. beaucoup plus rapide pour les sélections - l'approche préférée doit dépendre de vos priorités, mais, comme indiqué, la plupart des tableaux sont lus loin plus souvent qu'ils ne sont écrits.

6voto

Georgios Points 41

Si les colonnes étaient des entiers, comme dans votre exemple, je créerais une fonction :

create function f_min_int(@a as int, @b as int) 
returns int
as
begin
    return case when @a < @b then @a else coalesce(@b,@a) end
end

Ensuite, lorsque j'ai besoin de l'utiliser, je fais :

select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)

Si vous avez 5 colonnes, la formule ci-dessus devient

select col1, col2, col3, col4, col5,
dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(dbo.f_min_int(col1,col2),col3),col4),col5)

5voto

G Mastros Points 12241

Vous pouvez également utiliser une requête d'union. Au fur et à mesure que le nombre de colonnes augmente, vous devrez modifier la requête, mais au moins, il s'agira d'une modification directe.

Select T.Id, T.Col1, T.Col2, T.Col3, A.TheMin
From   YourTable T
       Inner Join (
         Select A.Id, Min(A.Col1) As TheMin
         From   (
                Select Id, Col1
                From   YourTable

                Union All

                Select Id, Col2
                From   YourTable

                Union All

                Select Id, Col3
                From   YourTable
                ) As A
         Group By A.Id
       ) As A
       On T.Id = A.Id

4voto

Learning Points 5386

C'est de la force brute, mais cela fonctionne

 select case when col1 <= col2 and col1 <= col3 then col1
           case when col2 <= col1 and col2 <= col3 then col2
           case when col3 <= col1 and col3 <= col2 then col3
    as 'TheMin'
           end

from Table T

... car min() ne fonctionne que sur une colonne et non sur plusieurs colonnes.

2voto

Sam Saffron Points 56236

Les deux cette question Y cette question essayez de répondre à cette question.

Oracle dispose d'une fonction intégrée pour cela, tandis que Sql Server vous oblige à définir une fonction définie par l'utilisateur ou à utiliser des instructions de cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X