J'utilise SQL Server 2008 R2
.
Je dois trier un tableau en fonction de la valeur minimale de deux colonnes.
Le tableau ressemble à ceci:
ID: integer;
Date1: datetime;
Date2: datetime.
Je veux que mes données soient triées par minimum de deux dates.
Quel est le moyen le plus simple de trier cette table de cette façon?
NOT NULL colonnes. Vous devez ajouter l'instruction CASE à la clause ORDER BY comme suit:
SELECT Id, Date1, Date2
FROM YourTable
ORDER BY CASE
WHEN Date1 < Date2 THEN Date1
ELSE Date2
END
Colonnes NULLABLE. Comme Zohar Peled _ écrivait dans les commentaires si les colonnes sont nulles, vous pouvez utiliser ISNULL
(mais il vaut mieux utiliser COALESCE
au lieu de ISNULL
, car c'est ANSI SQL standard
):
SELECT Id, Date1, Date2
FROM YourTable
ORDER BY CASE
WHEN COALESCE(Date1, '1753-01-01') < COALESCE(Date2, '1753-01-01') THEN Date1
ELSE Date2
END
Vous pouvez en savoir plus sur ANSI dateformat standard 1753-01-01
ici .
Utilisez une expression CASE
dans le ORDER BY
:
ORDER BY case when date1 < date2 then date1 else date2 end
Modifier:
Si les valeurs null doivent être prises en compte, ajoutez coalesce()
:
ORDER BY case when date1 < date2 then date1 else coalesce(date2,date1) end
Explication:
Si date1 <date2 puis ordre par date1. (Les deux dates ne sont pas nulles ici.) Fonctionne comme avant.
Sinon, utilisez COALESCE()
pour trier par date2 (lorsque date2 n'est pas nul) ou par date1 (lorsque date2 est nul) ou par null (si les deux dates sont nulles)
Le moyen le plus simple consiste à utiliser le mot clé VALUES
, comme suit:
SELECT ID, Date1, Date2
FROM YourTable
ORDER BY (SELECT MIN(v) FROM (VALUES (Date1), (Date2)) AS value(v))
Ce code fonctionnera pour tous les cas, même avec les colonnes nullable.
Modifier :
La solution avec le mot clé COALESCE
n'est pas universelle. Il a les restrictions importantes:
Date
(si vous utilisez les dates antérieures à 01/01/1753
)NULL
. Il interprète la valeur NULL
comme la valeur minimale datetime
. Mais est-ce réellement Vrai? Ce n'est même pas datetime
, ce n'est rien. IF
sera beaucoup plus compliquée si nous utilisons plus de deux colonnes.Selon la question:
Quel est le moyen le plus simple de trier cette table de cette façon?
La solution la plus courte et la plus simple est celle décrite ci-dessus, car:
Date
et vous n'avez pas besoin de modifier le code._ {Edit 2:} _
Zohar Peled a suggéré la procédure suivante:
Je classerais les lignes selon ces règles: premièrement, lorsque les deux valeurs sont nulles, deuxièmes, lorsque date1 est nul, troisièmement, lorsque la date 2 est nul, quatrième, min (date1, date2)
Donc, dans ce cas, la solution peut être atteinte en utilisant la même approche, comme suit:
SELECT ID, Date1, Date2
FROM YourTable
ORDER BY
CASE WHEN Date1 IS NULL AND Date2 IS NULL THEN 0
WHEN Date1 IS NULL THEN 1
WHEN Date2 IS NULL THEN 2
ELSE 3 END,
(SELECT MIN(v) FROM (VALUES ([Date1]), ([Date2])) AS value(v))
La sortie pour ce code est ci-dessous:
La COALESCE
solutionne sera pas trier le tableau de cette façon. Il gâche les lignes contenant au moins une cellule de la valeur NULL
. Le résultat est le suivant:
J'espère que cela aide et d'attendre les critiques.
Cela peut être une solution alternative qui ne nécessite pas de branchement comme CASE WHEN
. Ceci est basé sur la formule max(a,b)=1/2(a+b+|a−b|)
telle que décrite here . Nous obtenons les valeurs absolues de a et b en utilisant DATEDIFF
avec une date de référence ('1773-01-01'
).
ORDER BY (DATEDIFF(d,'17730101' ,isnull(Startdate,enddate)) + DATEDIFF(d,'17730101' ,isnull(EndDate,Startdate))
- ABS(DATEDIFF(d,isnull(Startdate,enddate),isnull(EndDate,Startdate))))
Données de test
Create Table #DateData(ID int Identity, Name varchar(15),Startdate datetime,EndDate DateTime)
Insert Into #DateData(Name,Startdate,EndDate) values ('myName','2015-04-17 18:48:27','2015-04-18 18:48:27')
Insert Into #DateData(Name,Startdate,EndDate) values ('myName','2015-04-19 18:48:27','2015-04-18 18:48:27')
Insert Into #DateData(Name,Startdate,EndDate) values ('myName','2015-04-20 18:48:27','2015-04-18 18:48:27')
Insert Into #DateData(Name,Startdate,EndDate) values ('myName','2015-04-11 18:48:27','2015-04-22 18:48:27')
Insert Into #DateData(Name,Startdate,EndDate) values ('myName','2015-05-09 18:48:27','2015-04-18 18:48:27')
Insert Into #DateData(Name,Startdate,EndDate) values ('myName','2015-04-17 19:07:38','2015-04-17 18:55:38')
Insert Into #DateData(Name,Startdate,EndDate) values ('myName','2015-04-17 19:07:38','2015-05-12 18:56:29')
Requête complète
select *
from #DateData order by (DATEDIFF(d,'17730101' ,isnull(Startdate,enddate)) + DATEDIFF(d,'17730101' ,isnull(EndDate,Startdate))
- ABS(DATEDIFF(d,isnull(Startdate,enddate),isnull(EndDate,Startdate))))
Si vous ne souhaitez pas utiliser Case statement
dans le Order By
, il s'agit d'une autre approche. Il suffit de déplacer le Case statement
à Select
SELECT Id, Date1, Date2 FROM
(SELECT Id, Date1, Date2
,CASE WHEN Date1 < Date2 THEN Date1 ELSE Date2 END as MinDate
FROM YourTable) as T
ORDER BY MinDate
J'utilise CROSS APPLY
, je ne suis pas sûr de la performance, mais CROSS APPLY
a souvent une meilleure performance de mon expérience.
CREATE TABLE #Test (ID INT, Date1 DATETIME, Date2 DATETIME)
INSERT INTO #Test SELECT 1, NULL, '1/1/1';INSERT INTO #Test SELECT 2, NULL, NULL;INSERT INTO #Test SELECT 3, '2/2/2', '3/3/1';INSERT INTO #Test SELECT 4, '3/3/3', '11/1/1'
SELECT t.ID, Date1, Date2, MinDate
FROM #TEST t
CROSS APPLY (SELECT MIN(d) MinDate FROM (VALUES (Date1), (Date2)) AS a(d)) md
ORDER BY MinDate
DROP TABLE #Test
Je préfère cette façon de gérer les colonnes Nullable:
SELECT Id, Date1, Date2
FROM YourTable
ORDER BY
CASE
WHEN Date1 < Date2 OR Date1 IS NULL THEN Date1
ELSE Date2
END
Je passerais de comment à faire cela à pourquoi vous en avez besoin - et vous proposerais de changer le schéma à la place. La règle de base est la suivante: si vous devez exécuter des cascades pour accéder à vos données, la décision de conception est mauvaise.
Comme vous l'avez vu, cette tâche est très atypique pour SQL. Bien que possible, toutes les méthodes proposées sont extrêmement lentes par rapport à un ORDER BY
ordinaire.
Je pense que lorsque vous voulez trier les deux champs date1
et date2
, vous devriez les avoir tous les deux dans la partie ORDER BY
, comme ceci:
SELECT *
FROM aTable
ORDER BY
CASE WHEN date1 < date2 THEN date1
ELSE date2 END,
CASE WHEN date1 < date2 THEN date2
ELSE date1 END
Le résultat peut être comme ça:
date1 | date2
-----------+------------
2015-04-25 | 2015-04-21
2015-04-26 | 2015-04-21
2015-04-25 | 2015-04-22
2015-04-22 | 2015-04-26
Pour obtenir un résultat de préfet avec des valeurs Null
, utilisez:
SELECT *
FROM aTable
ORDER BY
CASE
WHEN date1 IS NULL THEN NULL
WHEN date1 < date2 THEN date1
ELSE date2 END
,CASE
WHEN date2 IS NULL THEN date1
WHEN date1 IS NULL THEN date2
WHEN date1 < date2 THEN date2
ELSE date1 END
Les résultats seront comme ceci:
date1 | date2
-----------+------------
NULL | NULL
NULL | 2015-04-22
2015-04-26 | NULL
2015-04-25 | 2015-04-21
2015-04-26 | 2015-04-21
2015-04-25 | 2015-04-22
Je voudrais commander les lignes par ces règles:
Pour ce faire, un cas imbriqué sera simple et efficace (sauf si la table est très grande) selon ce post .
SELECT ID, Date1, Date2
FROM YourTable
ORDER BY
CASE
WHEN Date1 IS NULL AND Date2 IS NULL THEN 0
WHEN Date1 IS NULL THEN 1
WHEN Date2 IS NULL THEN 2
ELSE 3 END,
CASE
WHEN Date1 < Date2 THEN Date1
ELSE Date2
END
Il y a une autre option. Vous pouvez calculer la colonne de résultat par la logique nécessaire et couvrir la sélection par une externe avec un classement par colonne. Dans ce cas, le code sera le suivant:
select ID, x.Date1, x.Date2
from
(
select
ID,
Date1,
Date2,
SortColumn = case when Date1 < Date2 then Date1 else Date2 end
from YourTable
) x
order by x.SortColumn
L'avantage de cette solution est que vous pouvez ajouter les requêtes de filtrage nécessaires (dans la sélection interne) et que les index seront néanmoins utiles.
Vous pouvez utiliser la fonction min
dans la clause order by
:
select *
from [table] d
order by ( select min(q.t) from (
select d.date1 t union select d.date2) q
)
Vous pouvez également utiliser l'instruction case
dans la clause order by
, mais comme vous connaissez le résultat de la comparaison (>
et <
), toute valeur (null ou none null) avec null n'est pas true
même si vous avez réglé ansi_nulls
à off
. Ainsi, pour garantir le type souhaité, vous devez gérer null
s, comme vous le savez dans la clause case
, si le résultat d'une when
est true
, les autres déclarations when
ne sont pas évaluées et vous pouvez donc dire:
select * from [table]
order by case
when date1 is null then date2
when date2 is null then date1
when date1<date2 then date1 -- surely date1 and date2 are not null here
else date2
end
Il existe également d'autres solutions si votre scénario est différent. Vous pouvez peut-être évaluer le résultat de la comparaison de plusieurs colonnes (ou d'un calcul) dans un champ séparé et enfin classer par ce champ calculé sans utiliser aucune condition dans votre clause order by.
SELECT ID, Date1, Date2
FROM YourTable
ORDER BY (SELECT TOP(1) v FROM (VALUES (Date1), (Date2)) AS value(v) ORDER BY v)
Très similaire à la réponse @dyatchenko mais sans problème NULL