我有一个记录表来捕获某个对象的更新,除了其他信息之外,还会捕获此更新发生的时间。我想要做的是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
语句?
编辑:
以下是我发现的与目前答案相关的一些链接:
希望这些能够帮助其他人寻找类似问题的解决方案!
答案 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
。然后,我们向后浏览结果(SecondAction
,FirstAction
,LastActionTaken
)并返回我们找到的第一个UNPIVOT
。
不可否认,这有点乱,但我认为展示另一种检索相同数据的方法会很好。
还值得注意性能:
在针对OUTER APPLY
和SSMS Execution Plan
方法运行我的答案后,UNPIVOT
显示OUTER APPLY
和50%
大致相等(大约{{1}每个执行时间。)
将我的方法与其中任何一个进行比较时,我的方法需要大约。执行时间为88%
,其中UNPIVOT
/ OUTER APPLY
仅执行12%
- 因此UNPIVOT
和OUTER APPLY
的执行速度要快得多(至少在这个例子)。
我的方法需要花费更长时间的原因是SQL
每次加入时都HistoryTable
进行表扫描,总计4
次扫描。使用其他两种方法,此操作仅执行一次。