É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 celle qui donne la dernière colonne - 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 correctement normalisée. Ces colonnes "tableau" ne figurent pas dans une table réelle 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 changer le jeu 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 cela fonctionne, même si c'est un peu lourd. C'est également plus compliqué que ce qui est indiqué dans les réponses, car vous devez tenir compte du fait qu'il y a deux valeurs min dans la même ligne.
En tout cas, je pensais publier ma solution actuelle qui, compte tenu de mes contraintes, fonctionne plutôt bien. Il 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 la meilleure performance, mais vu les circonstances (je ne peux pas repenser toutes les requêtes uniquement pour la nouvelle exigence de la colonne MinValue), il s'agit d'un assez élégant "sortir de prison" carte".
Il y a probablement plusieurs façons de le faire. Ma suggestion est d'utiliser Cas/Quand le faire. Avec 3 colonnes, c'est pas trop grave.
Select Id,
Case When Col1 < Col2 And Col1 < Col3 Then Col1
When Col2 < Col1 And Col2 < Col3 Then Col2
Else Col3
End As TheMin
From YourTableNameHere
En utilisant CROSS APPLY
:
SELECT ID, Col1, Col2, Col3, MinValue
FROM YourTable
CROSS APPLY (SELECT MIN(d) MinValue FROM (VALUES (Col1), (Col2), (Col3)) AS a(d)) A
SELECT ID, Col1, Col2, Col3,
(SELECT MIN(Col) FROM (VALUES (Col1), (Col2), (Col3)) AS X(Col)) AS TheMin
FROM Table
La meilleure façon de le faire est probablement de ne pas le faire - il est étrange que des personnes insistent pour que leurs données soient stockées de manière à requérir une "gymnastique" SQL extraire des informations significatives, quand il existe des moyens beaucoup plus faciles pour obtenir le résultat souhaité si vous structurez un peu mieux votre schéma :-)
Le moyen correct de le faire, à mon avis, consiste à disposer du 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
avec 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' individuelles séparément en utilisant where col = 2
Dans vos autres requêtes. Cela permet également une expansion facile si le nombre de "vieilles colonnes" augmente.
Cela rend vos requêtes donc beaucoup plus faciles. La directive générale que j’ai tendance à utiliser est que si vous avez déjà quelque chose qui ressemble à un tableau dans une ligne de la base de données, vous faites probablement quelque chose de mal et devrait penser à restructurer les données.
Cependant, si pour une raison quelconque, vous ne pouvez pas modifier ces colonnes, nous vous suggérons d'utiliser des déclencheurs d'insertion et de mise à jour et d'ajouter une autre colonne que ces déclencheurs ont réglée au minimum sur Col1/2/3
. Cela déplace le "coût" de l'opération de la sélection vers la mise à jour/insertion où elle appartient - la plupart des tables de base de données de mon expérience sont lues beaucoup plus souvent qu'écrites, de sorte que les coûts d'écriture ont tendance à être plus efficaces au fil du temps.
En d’autres termes, le minimum pour une ligne ne change que lorsque l’une des autres colonnes change, donc c’est le moment où vous devriez le calculer, pas à chaque fois que vous sélectionnez (ce qui est gaspillé si les données ne changent pas). Vous vous retrouveriez alors avec une table comme celle-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 devant prendre des décisions à l'heure select
est généralement une mauvaise idée en termes de performances, car les données ne changent que lors de l'insertion/de la mise à jour - l'ajout d'une autre colonne occupe plus d'espace dans la base de données et sera légèrement plus lent pour les insertions et les mises à jour mais peut être beaucoup plus rapide pour les sélections - l'approche recommandée devrait dépendre de vos priorités, mais, comme indiqué, la plupart des tableaux sont lire loin plus souvent qu'ils ne sont écrits.
Vous pouvez utiliser l'approche "force brute" avec une torsion:
SELECT CASE
WHEN Col1 <= Col2 AND Col1 <= Col3 THEN Col1
WHEN Col2 <= Col3 THEN Col2
ELSE Col3
END AS [Min Value] FROM [Your Table]
Lorsque la première condition lorsque la condition échoue, cela garantit que Col1 n'est pas la valeur la plus petite. Vous pouvez donc l'éliminer du reste des conditions. De même pour les conditions ultérieures. Votre requête devient la suivante pour cinq colonnes:
SELECT CASE
WHEN Col1 <= Col2 AND Col1 <= Col3 AND Col1 <= Col4 AND Col1 <= Col5 THEN Col1
WHEN Col2 <= Col3 AND Col2 <= Col4 AND Col2 <= Col5 THEN Col2
WHEN Col3 <= Col4 AND Col3 <= Col5 THEN Col3
WHEN Col4 <= Col5 THEN Col4
ELSE Col5
END AS [Min Value] FROM [Your Table]
Notez que s'il y a une égalité entre deux colonnes ou plus, alors <=
s'assure que nous quittons l'instruction CASE
le plus tôt possible.
Utilisez ceci:
select least(col1, col2, col3) FROM yourtable
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
alors quand j'ai besoin de l'utiliser je ferais:
select col1, col2, col3, dbo.f_min_int(dbo.f_min_int(col1,col2),col3)
si vous avez 5 colonnes alors ce qui précède 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)
Vous pouvez également le faire avec une requête d'union. Au fur et à mesure que le nombre de colonnes augmente, vous devrez modifier la requête, mais il s'agira au moins d'une modification simple.
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
C'est la force brute mais ça marche
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
... parce que min () ne fonctionne que sur une colonne et pas sur plusieurs colonnes.
Les deux cette question Et cette question essayez d'y répondre.
En résumé, Oracle possède une fonction intégrée à cet effet. Sql Server vous oblige à définir une fonction définie par l'utilisateur ou à utiliser des instructions de casse.
Si vous êtes capable de créer une procédure stockée, cela pourrait prendre un tableau de valeurs, et vous pourriez simplement appeler cela.
select *,
case when column1 < columnl2 And column1 < column3 then column1
when columnl2 < column1 And columnl2 < column3 then columnl2
else column3
end As minValue
from tbl_example
SELECT [ID],
(
SELECT MIN([value].[MinValue])
FROM
(
VALUES
([Col1]),
([Col1]),
([Col2]),
([Col3])
) AS [value] ([MinValue])
) AS [MinValue]
FROM Table;
Si vous utilisez SQL 2005, vous pouvez faire quelque chose de bien comme ça:
;WITH res
AS ( SELECT t.YourID ,
CAST(( SELECT Col1 AS c01 ,
Col2 AS c02 ,
Col3 AS c03 ,
Col4 AS c04 ,
Col5 AS c05
FROM YourTable AS cols
WHERE YourID = t.YourID
FOR
XML AUTO ,
ELEMENTS
) AS XML) AS colslist
FROM YourTable AS t
)
SELECT YourID ,
colslist.query('for $c in //cols return min(data($c/*))').value('.',
'real') AS YourMin ,
colslist.query('for $c in //cols return avg(data($c/*))').value('.',
'real') AS YourAvg ,
colslist.query('for $c in //cols return max(data($c/*))').value('.',
'real') AS YourMax
FROM res
De cette façon, vous ne vous perdez pas dans autant d'opérateurs :)
Cependant, cela pourrait être plus lent que l'autre choix.
C'est ton choix...
Pour plusieurs colonnes, il est préférable d’utiliser une instruction CASE. Toutefois, pour deux colonnes numériques i et j, vous pouvez utiliser un calcul simple:
min (i, j) = (i + j)/2 - abs (i-j)/2
Cette formule peut être utilisée pour obtenir la valeur minimale de plusieurs colonnes, mais sa valeur réellement désordonnée après 2, min (i, j, k) serait min (i, min (j, k)).
Ci-dessous, j'utilise une table temporaire pour obtenir le minimum de plusieurs dates. La première table temporaire interroge plusieurs tables jointes pour obtenir différentes dates (ainsi que d'autres valeurs pour la requête). La seconde table temporaire récupère ensuite les différentes colonnes et la date minimale en utilisant autant de passes que de colonnes de date.
Cela ressemble essentiellement à la requête d'union, le même nombre de passes est requis, mais peut être plus efficace (en fonction de l'expérience, mais aurait besoin de tests). L'efficacité n'était pas un problème dans ce cas (8 000 enregistrements). On pourrait indexer etc.
--==================== this gets minimums and global min
if object_id('tempdb..#temp1') is not null
drop table #temp1
if object_id('tempdb..#temp2') is not null
drop table #temp2
select r.recordid , r.ReferenceNumber, i.InventionTitle, RecordDate, i.ReceivedDate
, min(fi.uploaddate) [Min File Upload], min(fi.CorrespondenceDate) [Min File Correspondence]
into #temp1
from record r
join Invention i on i.inventionid = r.recordid
left join LnkRecordFile lrf on lrf.recordid = r.recordid
left join fileinformation fi on fi.fileid = lrf.fileid
where r.recorddate > '2015-05-26'
group by r.recordid, recorddate, i.ReceivedDate,
r.ReferenceNumber, i.InventionTitle
select recordid, recorddate [min date]
into #temp2
from #temp1
update #temp2
set [min date] = ReceivedDate
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.ReceivedDate < [min date] and t1.ReceivedDate > '2001-01-01'
update #temp2
set [min date] = t1.[Min File Upload]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Upload] < [min date] and t1.[Min File Upload] > '2001-01-01'
update #temp2
set [min date] = t1.[Min File Correspondence]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
where t1.[Min File Correspondence] < [min date] and t1.[Min File Correspondence] > '2001-01-01'
select t1.*, t2.[min date] [LOWEST DATE]
from #temp1 t1 join #temp2 t2 on t1.recordid = t2.recordid
order by t1.recordid
Un peu de torsion sur la requête de l'union:
DECLARE @Foo TABLE (ID INT, Col1 INT, Col2 INT, Col3 INT)
INSERT @Foo (ID, Col1, Col2, Col3)
VALUES
(1, 3, 34, 76),
(2, 32, 976, 24),
(3, 7, 235, 3),
(4, 245, 1, 792)
SELECT
ID,
Col1,
Col2,
Col3,
(
SELECT MIN(T.Col)
FROM
(
SELECT Foo.Col1 AS Col UNION ALL
SELECT Foo.Col2 AS Col UNION ALL
SELECT Foo.Col3 AS Col
) AS T
) AS TheMin
FROM
@Foo AS Foo
Si vous connaissez les valeurs que vous recherchez, généralement un code d'état, les informations suivantes peuvent être utiles:
select case when 0 in (PAGE1STATUS ,PAGE2STATUS ,PAGE3STATUS,
PAGE4STATUS,PAGE5STATUS ,PAGE6STATUS) then 0 else 1 end
FROM CUSTOMERS_FORMS
Je sais que cette question est ancienne, mais j’avais toujours besoin de la réponse et n’étais pas satisfaite des autres réponses; j’ai donc dû concevoir la mienne, ce qui est un peu différent de @ paxdiablo´s answer .
Je venais du pays de SAP ASE 16.0 et je n'avais besoin que d'un aperçu des statistiques de certaines données enregistrées à mon humble avis dans différentes colonnes d'une même ligne (elles représentent des moments différents - quand l'arrivée de quelque chose était planifiée, ce à quoi on s'attendait quand l'action a commencé et finalement quelle était l'heure réelle). Ainsi, j'avais transposé des colonnes dans les lignes de la table temporaire et préformé ma requête sur cette requête comme d'habitude.
N.B. Ce n'est pas la solution universelle qui vous convient!
CREATE TABLE #tempTable (ID int, columnName varchar(20), dataValue int)
INSERT INTO #tempTable
SELECT ID, 'Col1', Col1
FROM sourceTable
WHERE Col1 IS NOT NULL
INSERT INTO #tempTable
SELECT ID, 'Col2', Col2
FROM sourceTable
WHERE Col2 IS NOT NULL
INSERT INTO #tempTable
SELECT ID, 'Col3', Col3
FROM sourceTable
WHERE Col3 IS NOT NULL
SELECT ID
, min(dataValue) AS 'Min'
, max(dataValue) AS 'Max'
, max(dataValue) - min(dataValue) AS 'Diff'
FROM #tempTable
GROUP BY ID
Cela prenait environ 30 secondes sur un ensemble source de 630000 lignes et utilisait uniquement des données d'index. Il n'était donc pas utile de l'exécuter dans un processus critique mais pour des tâches telles que l'inspection ponctuelle de données ou le rapport de fin de journée. bien (mais vérifiez ceci avec vos pairs ou vos supérieurs, s'il vous plaît!). Le principal avantage de ce style pour moi était que je pouvais facilement utiliser plus/moins de colonnes et modifier le groupement, le filtrage, etc., en particulier une fois les données copiées.
Les données supplémentaires (columnName
, max
es, ...) devaient m'aider dans ma recherche, vous pourriez donc ne pas en avoir besoin; Je les ai laissés ici peut-être à spark quelques idées :-).