【问题标题】:Sort by minimum value of two columns按两列的最小值排序
【发布时间】:2015-06-18 11:49:27
【问题描述】:

我使用SQL Server 2008 R2

我需要按两列的最小值对表格进行排序。

表格如下所示:

ID: integer; 
Date1: datetime; 
Date2: datetime.

我希望我的数据至少按两个日期排序。

以这种方式对该表进行排序的最简单方法是什么?

【问题讨论】:

  • 尴尬的是SQL Server没有标量MIN函数。请投票:connect.microsoft.com/SQLServer/feedback/details/767183/…
  • 您可能对相关问题的许多 q/a 感兴趣,即如何获得最少的列。这里有两个例子:stackoverflow.com/questions/1947753/…dba.stackexchange.com/questions/21542/…
  • @usr:可能,但它可能不适用于这种情况。对我来说,期望如果存在这样的函数,它的工作方式类似于聚合 MIN 似乎是合理的,即它将忽略 NULL,除非所有参数都是 NULL(或没有参数)。另一方面,ORDER BY 将 NULL 视为任何数据类型的最小值。由于 OP 想要使用结果进行排序,他们可能有兴趣以 ORDER BY 处理 NULL 的方式而不是 MIN 处理它的方式处理 NULL。
  • 我不太确定,“按最少两个日期排序”是什么意思。你能给我们一个示例数据吗,超过 3 行。
  • @Infinity - 两个赏金?你觉得这个有点世俗的问题需要多少“关注”?

标签: sql sql-server tsql sorting min


【解决方案1】:

NOT NULL 列。您需要在 ORDER BY 子句中添加 CASE 表达式:

SELECT Id, Date1, Date2
FROM YourTable
ORDER BY CASE 
           WHEN Date1 < Date2 THEN Date1 
           ELSE Date2 
         END 

NULLABLE 列。正如 Zohar Peled 在 cmets 中所写,如果列可以为空,您可以使用ISNULL(但最好使用COALESCE 而不是ISNULL,因为它是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

您可以阅读ANSI标准日期格式1753-01-01here

【讨论】:

  • 假设日期列不可为空。如果是,那么正确的 order by 子句将是 CASE WHEN ISNULL(Date1, '01/01/1753') &lt; ISNULL(Date2, '01/01/1753') THEN Date1 ELSE Date2 END
  • 我同意,但 OP 可能不知道这个问题,所以值得一提。无论如何,您已经获得了我的投票,因为您的答案包含指向 MSDN 相关部分的链接,因此比其他答案更好。
  • 您确实应该使用 ANSI 标准日期格式。 1753-01-01
  • 你为什么要编辑你的答案 100 次?添加空间删除空间添加空间删除空间?
  • 你能停止回滚吗?它反复“碰撞”您的答案,可能被视为滥用编辑功能。它也在生成标志。如果这种情况继续下去,我将不得不锁定答案。
【解决方案2】:

ORDER BY 中使用CASE 表达式:

 ORDER BY case when date1 < date2 then date1 else date2 end

编辑:

如果需要考虑空值,添加coalesce():

 ORDER BY case when date1 < date2 then date1 else coalesce(date2,date1) end

解释:

如果 date1

否则,使用COALESCE() 按 date2(当 date2 不为 null 时)或 date1(当 date2 为 null 时)或按 null(如果两个日期均为 null)排序。

【讨论】:

  • 使用 case 没有影响,因为 try simple order by date1,date2 会得到相同的结果。
  • Select * from Table order by date1, date2 (100% 相同的结果)
  • @qazifarhan,恐怕你错了。简单测试:3行表:2015-01-10, 2015-01-202015-01-20, 2015-01-11; 2015-01-15, 2015-01-15。这是您按两个日期的MIN 排序时得到的顺序。如果你只是按date1、date2排序,你会得到不同的结果。
【解决方案3】:

最简单的方法是使用VALUES 关键字,如下所示:

SELECT ID, Date1, Date2
FROM YourTable
ORDER BY (SELECT MIN(v) FROM (VALUES (Date1), (Date2)) AS value(v))

此代码适用于所有情况,即使是 可为空 列。

编辑:

The solutionCOALESCE 关键字不是通用的。它有重要的限制:

  • 如果列是 Date 类型的,它将不起作用(如果您使用 01/01/1753 之前的日期)
  • 如果其中一列是NULL,它将不起作用。它解释了 NULL 值作为最小 datetime 值。但实际上是 真的?它甚至不是datetime,什么都不是。
  • 如果我们使用两个以上列,IF 表达式将会复杂得多。

根据问题:

以这种方式对该表进行排序的最简单方法是什么?

最短和最简单的解决方案是上面描述的解决方案,因为:

  • 实现它不需要大量编码 - 只需再添加一行。
  • 不需要关心列是否可以为空。您只需使用代码即可。
  • 可以扩展查询中的列数,只需在逗号后添加列数即可。
  • 它与Date工作,您无需修改​​代码。

编辑 2:

Zohar Peled 建议了以下顺序:

我会按照以下规则对行进行排序:第一,当两者都为空时,第二,当 date1 为空时,第三,当日期 2 为空时,第四,min(date1, date2)

因此,对于这种情况,可以通过使用相同的方法来获得解决方案,如下所示:

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))

这段代码的输出如下:

COALESCEsolution不会以这种方式对表格进行排序。它弄乱至少有一个单元格的NULL 值所在的行。它的输出如下:

希望这会有所帮助并等待批评。

【讨论】:

  • 这对于可为空的列将无法按预期工作。原因是 MIN 聚合函数忽略了空值。 sqlFiddle 目前已关闭,所以我无法给您展示一个实时示例,但您可以使用 sql from this link 并尝试运行它。
  • @ZoharPeled 是的,它当然会忽略,但有错吗?
  • 在我看来这是错误的,因为默认情况下,ORDER BY 子句 not 忽略空值。如果您按可空列升序排序,则空值将位于结果集的顶部。除非另有说明,否则解决方案应该激发模仿这种行为。这就是我首先评论可空列的原因。
  • @ZoharPeled 好的,为什么将 both 列都是NULL 的行放在顶部是错误的?如果一行至少有 一个 列具有 正确 值,则该值将用于 ORDER BY 子句。
  • 没有错,但是把有一个日期的行放在有两个日期的行后面是错误的。当我运行代码时,我得到了this result。如您所见,空日期位于第 5 行和最后一行,它们显然不属于该行。
【解决方案4】:

这可能是一个替代解决方案,不需要像CASE WHEN 这样的分支。这是基于max(a,b)=1/2(a+b+|a−b|) 所描述的公式here。我们使用 DATEDIFF 和参考日期 ('1773-01-01') 获得 a 和 b 的绝对值。

ORDER BY (DATEDIFF(d,'17730101' ,isnull(Startdate,enddate)) + DATEDIFF(d,'17730101' ,isnull(EndDate,Startdate)) 
    -  ABS(DATEDIFF(d,isnull(Startdate,enddate),isnull(EndDate,Startdate))))

测试数据

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')

完整查询

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】:

    如果您不想在Order By 中使用Case statement,那么这是另一种方法,只需将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
    

    【讨论】:

      【解决方案6】:

      我更喜欢这种方式来处理可为空的列:

      SELECT Id, Date1, Date2
      FROM YourTable
      ORDER BY 
         CASE 
           WHEN Date1 < Date2 OR Date1 IS NULL THEN Date1 
           ELSE Date2 
         END 
      

      【讨论】:

        【解决方案7】:

        Code for max

        我正在使用CROSS APPLY,我不确定性能,但在我的经验中CROSS APPLY 通常有更好的性能。

        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
        

        【讨论】:

          【解决方案8】:

          我会将重点从 如何 转移到 为什么 你需要这个 - 并建议改为更改架构。经验法则是:如果您需要通过特技来访问您的数据,则存在一个糟糕的设计决策。

          正如您所见,此任务对于 SQL 来说是非常不典型的,因此尽管有可能,但与普通的 ORDER BY 相比,所有建议的方法都慢得令人痛苦。

          • 如果您需要经常这样做,那么这两个日期中的最小值必须对您的应用程序具有独立的物理意义。
            • 这证明了一个单独的列(或者可能是替换两者之一的列)的合理性 - 由触发器维护,或者甚至手动维护,如果含义足够独立以至于列在某些情况下可能两者都不是。

          【讨论】:

            【解决方案9】:

            我认为,当您想对date1date2 的两个字段进行排序时,您应该将它们都放在ORDER BY 部分中,如下所示:

            SELECT *
            FROM aTable
            ORDER BY 
                CASE WHEN date1 < date2 THEN date1 
                ELSE date2 END, 
                CASE WHEN date1 < date2 THEN date2 
                ELSE date1 END
            

            结果可以是这样的:

            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
            

            要获得具有Null 值的完美结果,请使用:

            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
            

            结果会是这样的:

            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
            

            【讨论】:

              【解决方案10】:

              还有另一种选择。您可以通过所需的逻辑计算结果列,并通过外部的选择覆盖您的列排序。在这种情况下,代码如下:

              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
              

              此解决方案的好处是您可以添加必要的过滤查询(在内部选择中)并且索引仍然有用。

              【讨论】:

                【解决方案11】:

                我会按照以下规则对行进行排序:

                1. 当两者都为空时
                2. 当 date1 为空时
                3. 当日期 2 为空时
                4. 分钟(日期1,日期2)

                要做到这一点,嵌套案例将简单高效(除非表非常大)according to this 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
                

                【讨论】:

                • 不行Operand type clash: int is incompatible with date
                • 它确实对我有用,但我已经更新了我的答案,因此它不会成为任何 sql server 版本的问题。
                • 还是不行。 Date1 &gt; Date2 总是假的,以防其中一个是 NULL
                • ORDER BY 中的案例并不是该问题的最简单解决方案。
                • @dyatchenko 我真的不知道你想要什么。 Both of the queries work fine on sql server 2012,我没有办法在其他版本上测试它们,但我认为它们在那里也应该没问题。与此同时,似乎大多数选民确实认为按顺序排列的案件是解决这个问题的最简单的方法,而我恰好是其中之一。
                【解决方案12】:

                您可以在order by 子句中使用min 函数:

                select * 
                from [table] d
                order by ( select min(q.t) from (
                           select d.date1 t union select d.date2) q
                         )
                

                您也可以在order by 子句中使用case 语句,但是您知道将(&gt;&lt;)任何值(null 或 none null)与 null 进行比较的结果不是 true,即使您已将 ansi_nulls 设置为 off。所以为了保证你想要的排序,你需要处理nulls,正如你在case子句中知道的那样,如果when的结果是true,那么进一步的when语句不会被评估,所以你可以说:

                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
                

                如果您的情况不同,这里还有一些其他解决方案,也许您评估比较单独字段内的多个列(或计算)的结果,最后按该计算字段排序,而不使用您的 order by 子句中的任何条件。

                【讨论】:

                  【解决方案13】:
                  SELECT ID, Date1, Date2
                  FROM YourTable
                  ORDER BY (SELECT TOP(1) v FROM (VALUES (Date1), (Date2)) AS value(v) ORDER BY v)
                  

                  与@dyatchenko 的答案非常相似,但没有 NULL 问题

                  【讨论】:

                    猜你喜欢
                    • 2011-09-30
                    • 2019-06-03
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多