在SQL Server中选择一组日期

时间:2015-12-10 15:20:03

标签: sql sql-server

我有一个记录表来捕获某个对象的更新,除了其他信息之外,还会捕获此更新发生的时间。我想要做的是SELECT MIN(LogDate)对应于某个ActionTaken列。

更具体地说,历史记录表可能包含ActionTaken = 1的其他(更近期)行,但我想捕获日期ActionTaken 变为 1。

示例:

SELECT  MIN(LogDate) AS FirstActionDate
FROM    HistoryTable
WHERE   ID = 123
    AND FirstActionTaken = 1

SELECT  MIN(LogDate) AS SecondActionDate
FROM    HistoryTable
WHERE   ID = 123
    AND SecondActionTaken = 1

SELECT  MIN(LogDate) AS ThirdActionDate
FROM    HistoryTable
WHERE   ID = 123
    AND ThirdActionTaken = 1

这很有效,我收到了正确的日期而没有问题。我遇到麻烦的地方就是select来自这个群组的MAX(LogDate)

SELECT  MAX(LogDate) AS LastActionDate
FROM    HistoryTable
WHERE   ID = 123
    AND LogDate IN 
    (
            (   SELECT  MIN(LogDate) AS FirstActionDate
                FROM    HistoryTable
                WHERE   ID = 123
                    AND FirstActionTaken = 1    ),

            (   SELECT  MIN(LogDate) AS SecondActionDate
                FROM    HistoryTable
                WHERE   ID = 123
                    AND SecondActionTaken = 1   ),

            (   SELECT  MIN(LogDate) AS ThirdActionDate
                FROM    HistoryTable
                WHERE   ID = 123
                    AND ThirdActionTaken = 1    )
    )

这也有效,但我讨厌这样做。我可以将之前的语句保存到变量中,只保存SELECT MAX();它肯定会更具可读性,但这个查询的JOIN语法是什么样的?

有没有办法将前三个SELECT语句合并为一个返回所有三个日期的语句,并且不是一个难以理解的混乱?

如何从此结果集中获取最新LogDate(作为单独的列)并且没有(看似不必要的)重复SELECT语句?

编辑:

以下是我发现的与目前答案相关的一些链接:

希望这些能够帮助其他人寻找类似问题的解决方案!

5 个答案:

答案 0 :(得分:3)

使用规范化数据结构会更容易。这是一种使用条件聚合来计算三个最小日期的方法。然后它取这些值的最大值:

SELECT v.dt
FROM (SELECT MIN(CASE WHEN FirstActionTaken = 1 THEN LogDate END) AS d1,
             MIN(CASE WHEN SecondActionTaken = 1 THEN LogDate END) AS d2,
             MIN(CASE WHEN ThirdActionTaken = 1 THEN LogDate END) AS d3      
     FROM HistoryTable
     WHERE ID = 123
    ) ht OUTER APPLY
    (SELECT MAX(dt) as dt
     FROM (VALUES (d1), (d2), (d3) ) v(dt)
    ) v;

答案 1 :(得分:2)

编辑2

基于可以从OP自己的答案中获取的新信息(关于如何定义最新的行动日期),查询可以进一步简化为:

select coalesce(
         min(case when ThirdActionTaken = 1 then LogDate end),
         min(case when SecondActionTaken = 1 then LogDate end),
         min(case when FirstActionTaken = 1 then LogDate end)
       ) as LastActionDate
  from HistoryTable
 where id = 123

也可以使用Unpivot:

 select max(ActionDate)
   from (select min(case when FirstActionTaken = 1 then LogDate end) as FirstActionDate,
                min(case when SecondActionTaken = 1 then LogDate end) as SecondActionDate,
                min(case when ThirdActionTaken = 1 then LogDate end) as ThirdActionDate
           from HistoryTable
          where id = 123) t
unpivot (ActionDate for ActionDates in (FirstActionDate, SecondActionDate, ThirdActionDate)) unpvt

编辑:简短说明

这个答案与Gordon非常相似,它使用条件聚合在一个查询中获得3个最小日期。

所以查询的以下部分:

select min(case when FirstActionTaken = 1 then LogDate end) as FirstActionDate,
       min(case when SecondActionTaken = 1 then LogDate end) as SecondActionDate,
       min(case when ThirdActionTaken = 1 then LogDate end) as ThirdActionDate
  from HistoryTable
 where id = 123

......可能会返回类似......

的内容
FirstActionDate   SecondActionDate   ThirdActionDate
---------------   ----------------   ---------------
     2015-01-01         2015-12-01            (null)

然后,unpivot条款是" unpivots"将3列放入一个包含3行但只有一列的结果集中:

ActionDate
----------
2015-01-01
2015-12-01
    (null)

一旦结果采用这种格式,就可以应用简单的max聚合函数(select max(ActionDate))来获取3行的最大值。

答案 2 :(得分:1)

您可以使用UNION加入IN语句的3个查询。

这样的东西
SELECT
    MAX(ht1.LogDate) AS LastActionDate
FROM
    HistoryTable ht1
WHERE
    ht1.ID = 123
    AND ht1.LogDate IN (SELECT
                        MIN(LogDate) AS FirstActionDate
                    FROM
                        HistoryTable ht2
                    WHERE
                        ht2.ID = ht1.ID
                        AND ht2.FirstActionTaken = 1
                    UNION
                    SELECT
                        MIN(LogDate) AS FirstActionDate
                    FROM
                        HistoryTable ht2
                    WHERE
                        ht2.ID = ht1.ID
                        AND ht2.SecondActionTaken = 1
                    UNION
                    SELECT
                        MIN(LogDate) AS FirstActionDate
                    FROM
                        HistoryTable ht2
                    WHERE
                        ht2.ID = ht1.ID
                        AND ht2.ThirdActionTaken = 1)

答案 3 :(得分:0)

您可以在不使用PIVOT的情况下解决此问题。以下代码扩展了您的初始代码,以将MIN值存储到变量中,然后计算它们中的最大值:

DECLARE @FirstActionDate  DATETIME = NULL;
DECLARE @SecondActionDate DATETIME = NULL;
DECLARE @ThirdActionDate  DATETIME = NULL;
DECLARE @LastActionDate   DATETIME = NULL;

SELECT  @FirstActionDate = MIN(LogDate)
FROM    HistoryTable
WHERE   ID = 123
    AND FirstActionTaken = 1

SELECT  @SecondActionDate = MIN(LogDate)
FROM    HistoryTable
WHERE   ID = 123
    AND SecondActionTaken = 1

SELECT  @ThirdActionDate = MIN(LogDate)
FROM    HistoryTable
WHERE   ID = 123
    AND ThirdActionTaken = 1

-- calculate @LastActionDate as the greater from @FirstActionDate, @SecondActionDate and @ThirdActionDate. 
SET @LastActionDate = @FirstActionDate;
IF (@SecondActionDate > @LastActionDate) SET @LastActionDate = @SecondActionDate;
IF (@ThirdActionDate > @LastActionDate)  SET @LastActionDate = @ThirdActionDate;

SELECT @FirstActionDate AS [FirstActionDate]
, @SecondActionDate     AS [SecondActionDate]
, @ThirdActionDate      AS [ThirdActionDate]
, @LastActionDate       AS [LastActionDate]

如果您想要绝对上次操作日期,可以将原始代码更改为单个语句,如下所示:

SELECT MAX(LogDate) AS [LastActionDate]
, MIN(CASE WHEN FirstActionTaken = 1  THEN LogDate ELSE NULL END) AS [FirstActionDate]
, MIN(CASE WHEN SecondActionTaken = 1 THEN LogDate ELSE NULL END) AS [SecondActionDate]
, MIN(CASE WHEN ThirdActionTaken = 1  THEN LogDate ELSE NULL END) AS [ThirdActionDate]
FROM    HistoryTable
WHERE   ID = 123

答案 4 :(得分:0)

我自己尝试重构最终的SELECT声明:

SELECT  MIN(ht2.LogDate) AS FirstActionDate,
        MIN(ht3.LogDate) AS SecondActionDate,
        MIN(ht4.LogDate) AS ThirdActionDate,
        COALESCE (
            MIN(ht4.LogDate),
            MIN(ht3.LogDate),
            MIN(ht2.LogDate)
        ) AS LastActionDate
FROM    HistoryTable ht
    INNER JOIN HistoryTable ht2 
        ON ht2.ID = ht.ID AND ht2.FirstActionTaken = 1
    INNER JOIN HistoryTable ht3 
        ON ht3.ID = ht.ID AND ht3.SecondActionTaken = 1
    INNER JOIN HistoryTable ht4 
        ON ht4.ID = ht.ID AND ht4.ThirdActionTaken = 1
WHERE   ht.ID = 123
GROUP BY ht.ID

每个JOINS列的HistoryTable返回xActionTaken,每个SELECTS MIN(LogDate)ThirdAction。然后,我们向后浏览结果(SecondActionFirstActionLastActionTaken)并返回我们找到的第一个UNPIVOT

不可否认,这有点乱,但我认为展示另一种检索相同数据的方法会很好。

还值得注意性能:

在针对OUTER APPLYSSMS Execution Plan方法运行我的答案后,UNPIVOT显示OUTER APPLY50%大致相等(大约{{1}每个执行时间。)

将我的方法与其中任何一个进行比较时,我的方法需要大约。执行时间为88%,其中UNPIVOT / OUTER APPLY仅执行12% - 因此UNPIVOTOUTER APPLY的执行速度要快得多(至少在这个例子)。

我的方法需要花费更长时间的原因是SQL每次加入时都HistoryTable进行表扫描,总计4次扫描。使用其他两种方法,此操作仅执行一次。