web-dev-qa-db-fra.com

Trier par valeur minimale de deux colonnes

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?

50
Vasiliy Volkov

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-01ici

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) 

33
jarlh

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:

  • Cela ne fonctionnera pas si les colonnes sont du type Date (si vous utilisez les dates antérieures à 01/01/1753)
  • Cela ne fonctionnera pas si l'une des colonnes est 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. 
  • L'expression 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:

  • Il ne faut pas beaucoup de code pour le mettre en œuvre - ajoutez simplement une ligne de plus.
  • Vous vous n'avez pas besoin de vous en soucier pour savoir si les colonnes sont nulles ou non. Vous utilisez simplement le code et cela fonctionne.
  • Vous pouvez étendre _ le nombre de colonnes de votre requête en ajoutant simplement celle après une virgule.
  • Il fonctionne avec les colonnes 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:

The output result for *Zohar's* way of order

La COALESCEsolutionne 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:

Weird ORDER BY of <code>COALESCE</code> solution

J'espère que cela aide et d'attendre les critiques.

8
dyatchenko

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))))
5
Arun Gairola

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
5
pjobs

Code pour max

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
4
EarlOfEnnui

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 
4
Jesús López

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.

  • Si vous devez le faire souvent, le minimum des deux dates doit avoir une signification physique indépendante pour votre application .
    • Ce qui justifie une colonne distincte (ou peut-être une colonne remplaçant l'une des deux) - maintenue par un déclencheur ou même manuellement si la signification est suffisamment indépendante pour que la colonne ne le soit éventuellement pas.
2
ivan_pozdeev

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
2
shA.t

Je voudrais commander les lignes par ces règles: 

  1. quand les deux sont nuls 
  2. quand date1 est null 
  3. quand la date 2 est nulle 
  4. min (date1, date2)

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
1
Zohar Peled

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.

1
Sandr

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

0
Null
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

0
AlexK