这里有一些竞争因素:
-
您应该使用SmallDateTime、DateTime2 或DateTimeOffset 类型的列来存储日志中的实际时间,这些类型允许使用DateDiff() 和DateAdd() 和其他日期/基于时间的比较逻辑,其中Timestamp 旨在用作货币令牌,您可以使用它来确定一条记录是否比另一条记录更新,您不应该尝试使用它来确定实际 时间。
-
您没有解释预期的工作流程,我们只能假设流程是 [OPEN]=>[In Progress]=>[CLAIM FIXED]。也没有提到“进行中”,我们认为这是一个临时状态。这里实际发生的是,这种结构实际上只能告诉您在“进行中”状态所花费的时间,这可能符合您的需求,因为这是实际工作所花费的时间,但重要的是要认识到我们没有首先知道何时将错误更改为“打开”,除非也记录了该错误,但我们需要查看数据来解释这一点。
-
您的示例数据集没有涵盖足够的组合,因此您不会注意到,一旦您添加了 1 个以上的错误,现有逻辑就会失败。更重要的是,您要求计算小时数,但您的示例数据仅显示变化分钟数,并且根本没有错误完成的示例。
- 如果没有一组真实的数据进行测试,您会发现很难调试您的逻辑,并且在针对更大的数据集执行此操作之前很难接受它确实有效。编写脚本场景会有所帮助,就像您在此处的帖子一样,但您应该创建数据以反映该脚本。
- 您在示例中使用
'FIXED',但在查询中使用'CLAIM FIXED',那么它是哪一个?
第 1 步:结构
将CurrentTime 的数据类型更改为基于日期时间 列。您的应用程序逻辑可能会推动这里的需求。如果您的系统是基于云的或国际化的,那么您可能会从使用DateTimeOffset 而不必转换为UTC 中看到好处,否则如果您不需要在日志中进行高精度计时,则使用SmallDateTime 是很常见的记录。
- 许多 ORM 和应用程序框架将允许您将基于 DateTime 的列配置为并发令牌,您完全需要一个。如果您不喜欢使用较低精度的并发值,那么您可以将两列并排放置,以比较两条记录之间的时间差,我们需要使用基于 DateTime 的 类型。
- 在日志的情况下,我们很少允许或期望编辑日志,如果您的日志是只读的,那么可能根本不需要并发令牌,特别是如果您只使用并发令牌来确定并发个人记录的编辑。
注意:您应该考虑为 Status 概念使用枚举或 FK。在您的示例数据集中,'In Progerss' 已经存在拼写错误,使用数字比较状态可能会带来一些性能优势,但它有助于防止拼写错误,尤其是在任何应用程序逻辑中使用 FK 或查找列表时。
第 2 步:示例数据
如果要求是计算记录之间花费的小时数,那么我们需要创建一些显示几个小时差异的简单示例,然后添加一些相同错误的示例被打开,修复,然后重新打开。
- 错误 #1 在 2020 年 1 月 1 日 2 小时内修复,然后在 2020 年 12 月 12 日重新打开并在 3 小时内修复
下表显示了已知的数据状态和预期的小时数,我们需要再添加一些数据故事来验证最终查询是否处理了明显的边界条件,例如多个错误和重叠日期
| BUG # |
Time |
Previous State |
New State |
Hrs In Progress |
| 1 |
2020-01-01 08:00:00 |
OPEN |
In Progress |
|
| 1 |
2020-01-01 10:00:00 |
In Progress |
FIXED |
(2 hrs) |
| 1 |
2020-12-10 09:00:00 |
FIXED |
OPEN |
|
| 1 |
2020-12-12 9:30:00 |
OPEN |
In Progress |
|
| 1 |
2020-12-12 12:30:00 |
In Progress |
FIXED |
(3 hrs) |
| 2 |
2020-03-17 11:15:00 |
OPEN |
In Progress |
|
| 2 |
2020-03-17 14:30:00 |
In Progress |
FIXED |
(3.25 hrs) |
| 3 |
2020-08-22 10:00:00 |
OPEN |
In Progress |
|
| 3 |
2020-08-22 16:30:00 |
In Progress |
FIXED |
(6.5 hrs) |
第三步:查询
这里要注意的是,'In Progress' 实际上是要查询的重要状态。我们真正想要的是查看OldStatus 为'In Progress' 的所有行,并且我们希望将该行链接到具有相同BugID 的最新记录之前这一行并且 NewStatus 等于 'In Progress'
上表中有趣的是,并非所有预期的小时数都是整数(整数),这使得使用DateDiff 有点棘手,因为它只计算边界变化,而不是总小时数。为了突出这一点,看看接下来的两个查询,第一个代表 59 分钟,另一个只有 2 分钟:
SELECT DateDiff(HOUR, '2020-01-01 08:00:00', '2020-01-01 08:59:00') -- 0 (59m)
SELECT DateDiff(HOUR, '2020-01-01 08:59:00', '2020-01-01 09:01:00') -- 1 (1m)
但是,SQL 结果显示第一个查询为0 小时,但第二个查询返回1 小时。那是因为它只比较HOUR 列,实际上根本没有做 time 值的减法。
要解决这个问题,我们可以使用 MINUTE 或 MI 作为日期部分参数并将结果除以 60。
SELECT CAST(ROUND(DateDiff(MI, '2020-01-01 08:00:00', '2020-01-01 08:59:00')/60.0,2) as Numeric(10,2)) -- 0.98
SELECT CAST(ROUND(DateDiff(MI, '2020-01-01 08:59:00', '2020-01-01 09:01:00')/60.0,2) as Numeric(10,2)) -- 0.03
您可以通过计算模数来选择以其他方式对其进行格式化,以获得整数而不是分数的分钟,但这超出了本文的范围,了解DateDiff的局限性是重要的更进一步。
有多种方法可以关联同一个表中的先前记录,如果您需要记录中的其他值,那么您可以使用带有子查询的连接来从之前的所有记录中返回 TOP 1当前一个,您可以使用窗口查询或CROSS APPLY 来执行嵌套查找。以下使用CROSS APPLY,这是所有 RDBMS 的非标准,但我觉得它使 MS SQL 查询非常干净:
SELECT [Fixed].BugID, [start_time], [Fixed].[CurrentTime] as [finish_time]
, DATEDIFF(MI, [start_time], [Fixed].[CurrentTime]) / 60 AS Time_Spent_Hr
, DATEDIFF(MI, [start_time], [Fixed].[CurrentTime]) % 60 AS Time_Spent_Min
FROM Log as Fixed
CROSS APPLY (SELECT MAX(CurrentTime) AS start_time
FROM Log as Started
WHERE Fixed.BugID = Started.BugID
AND Started.NewStatus = 'In Progress'
AND CurrentTime < Fixed.CurrentTime) as Started
WHERE Fixed.OldStatus = 'In Progress'
你可以玩这个小提琴:http://sqlfiddle.com/#!18/c408d4/3
然而结果表明:
| BugID |
start_time |
finish_time |
Time_Spent_Hr |
Time_Spent_Min |
| 1 |
2020-01-01T08:00:00Z |
2020-01-01T10:00:00Z |
2 |
0 |
| 1 |
2020-12-12T09:30:00Z |
2020-12-12T12:30:00Z |
3 |
0 |
| 2 |
2020-03-17T11:15:00Z |
2020-03-17T14:30:00Z |
3 |
15 |
| 3 |
2020-08-22T10:00:00Z |
2020-08-22T16:30:00Z |
6 |
30 |