【问题标题】:sum of time based on flag from multiple rows SQL Server基于多行 SQL Server 的标志的时间总和
【发布时间】:2023-03-31 17:15:01
【问题描述】:

我很难执行其中一个查询。我需要根据同一张表中的标志找到点火时间。表格如下,

UnitId      eventtime               ign
----------- ----------------------- -----
356         2011-05-04 10:41:00.000 1
356         2011-05-04 10:42:00.000 1
356         2011-05-04 10:43:00.000 1
356         2011-05-04 10:45:00.000 1
356         2011-05-04 10:47:00.000 1
356         2011-05-04 10:48:00.000 0
356         2011-05-04 11:14:00.000 1
356         2011-05-04 11:14:00.000 1
356         2011-05-04 11:15:00.000 1
356         2011-05-04 11:15:00.000 1
356         2011-05-04 11:15:00.000 1
356         2011-05-04 11:16:00.000 0
356         2011-05-04 11:16:00.000 0
356         2011-05-04 11:16:00.000 0
356         2011-05-04 14:49:00.000 1
356         2011-05-04 14:50:00.000 1
356         2011-05-04 14:50:00.000 1
356         2011-05-04 14:51:00.000 1
356         2011-05-04 14:52:00.000 0
356         2011-05-04 14:52:00.000 0
356         2011-05-04 20:52:00.000 0

这里,Ign 标志将确定ignition_on 和iginition_off 时间。 所以上表我们可以得到点火对

2011-05-04 10:41:00.000 - 2011-05-04 10:48:00.000
2011-05-04 11:14:00.000 - 2011-05-04 11:16:00.000
2011-05-04 14:49:00.000 - 2011-05-04 14:52:00.000

所以从上面的一对来看,我可以说我的单元运行了 7 + 2 + 3 = 12 分钟。我不希望在我的结果中出现上述对,仅用于示例。我的目标是在 12 分钟内得到这个结果。

如何使用单个查询来实现它,现在我正在使用 CURSOR 循环,但这件事需要多天和多个单位的更多时间。无论如何我可以在没有 CURSOR 的情况下实现它吗?

【问题讨论】:

  • 您的表中没有其他列?没有主键?
  • 我将身份列设置为 PK。我没有在上面展示它;)但你可以考虑一个
  • 如果最后一行的ign1 怎么办?那么什么时间应该被视为“直到”时间?
  • 如果最后一行的 ign 是 1 那么我们必须忽略它,我们只需要找到对。如果当前单元状态为 ON,那么我们必须等到它关闭才能计算分钟。我认为这是简单的解决方案,不会增加我的问题;)

标签: sql sql-server


【解决方案1】:

如果我们有一个额外的标准来区分具有相同ign 值的连续事件序列,我们可以从每个具有ign=1 的序列中提取其最早事件并将其与对应的最早事件联系起来ign=0 序列。

可以添加这样的标准,如下所示。我将先发布解决方案,然后解释它是如何工作的。

首先,设置:

DECLARE @atable TABLE (
  Id int IDENTITY,
  UnitId int,
  eventtime datetime,
  ign bit
);
INSERT INTO @atable (UnitId, eventtime, ign)
SELECT 356, '2011-05-04 10:41:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 10:42:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 10:43:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 10:45:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 10:47:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 10:48:00.000', 0 UNION ALL
SELECT 356, '2011-05-04 11:14:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 11:14:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 11:15:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 11:15:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 11:15:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 11:16:00.000', 0 UNION ALL
SELECT 356, '2011-05-04 11:16:00.000', 0 UNION ALL
SELECT 356, '2011-05-04 11:16:00.000', 0 UNION ALL
SELECT 356, '2011-05-04 14:49:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 14:50:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 14:50:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 14:51:00.000', 1 UNION ALL
SELECT 356, '2011-05-04 14:52:00.000', 0 UNION ALL
SELECT 356, '2011-05-04 14:52:00.000', 0 UNION ALL
SELECT 356, '2011-05-04 20:52:00.000', 0;

现在是查询:

WITH
marked AS (
  SELECT
    *,
    Grp = ROW_NUMBER() OVER (PARTITION BY UnitId ORDER BY eventtime) -
     ROW_NUMBER() OVER (PARTITION BY UnitId, ign ORDER BY eventtime)
  FROM @atable
),
ranked AS (
  SELECT
    *,
    seqRank = DENSE_RANK() OVER (PARTITION BY UnitId, ign ORDER BY Grp),
    eventRank = ROW_NUMBER() OVER (PARTITION BY UnitId, ign, Grp ORDER BY eventtime)
  FROM marked
),
final AS (
  SELECT
    s.UnitId,
    EventStart = s.eventtime,
    EventEnd   = e.eventtime
  FROM ranked s
    INNER JOIN ranked e ON s.UnitId = e.UnitId AND s.seqRank = e.seqRank
  WHERE s.ign = 1
    AND e.ign = 0
    AND s.eventRank = 1
    AND e.eventRank = 1
)
SELECT *
FROM final
ORDER BY
  UnitId,
  EventStart

这就是它的工作原理。

marked 公用表表达式 (CTE) 为我们提供了我在开始时谈到的附加标准。它生成的结果集如下所示:

Id  UnitId  eventtime                ign  Grp
--  ------  -----------------------  ---  ---
1   356     2011-05-04 10:41:00.000  1    0
2   356     2011-05-04 10:42:00.000  1    0
3   356     2011-05-04 10:43:00.000  1    0
4   356     2011-05-04 10:45:00.000  1    0
5   356     2011-05-04 10:47:00.000  1    0
6   356     2011-05-04 10:48:00.000  0    5
7   356     2011-05-04 11:14:00.000  1    1
8   356     2011-05-04 11:14:00.000  1    1
9   356     2011-05-04 11:15:00.000  1    1
10  356     2011-05-04 11:15:00.000  1    1
11  356     2011-05-04 11:15:00.000  1    1
12  356     2011-05-04 11:16:00.000  0    10
13  356     2011-05-04 11:16:00.000  0    10
14  356     2011-05-04 11:16:00.000  0    10
15  356     2011-05-04 14:49:00.000  1    4
16  356     2011-05-04 14:50:00.000  1    4
17  356     2011-05-04 14:50:00.000  1    4
18  356     2011-05-04 14:51:00.000  1    4
19  356     2011-05-04 14:52:00.000  0    14
20  356     2011-05-04 14:52:00.000  0    14
21  356     2011-05-04 20:52:00.000  0    14

您可以亲眼看到每个具有相同ign 的事件序列现在可以通过其自己的键(UnitId, ign, Grp) 轻松与其他事件区分开来。所以现在我们可以对每个序列以及序列中的每个事件进行排名,这就是ranked CTE 所做的。它产生以下结果集:

Id  UnitId  eventtime                ign  Grp  seqRank  eventRank
--  ------  -----------------------  ---  ---  -------  ---------
1   356     2011-05-04 10:41:00.000  1    0    1        1
2   356     2011-05-04 10:42:00.000  1    0    1        2
3   356     2011-05-04 10:43:00.000  1    0    1        3
4   356     2011-05-04 10:45:00.000  1    0    1        4
5   356     2011-05-04 10:47:00.000  1    0    1        5
6   356     2011-05-04 10:48:00.000  0    5    1        1
7   356     2011-05-04 11:14:00.000  1    1    2        1
8   356     2011-05-04 11:14:00.000  1    1    2        2
9   356     2011-05-04 11:15:00.000  1    1    2        3
10  356     2011-05-04 11:15:00.000  1    1    2        4
11  356     2011-05-04 11:15:00.000  1    1    2        5
12  356     2011-05-04 11:16:00.000  0    10   2        1
13  356     2011-05-04 11:16:00.000  0    10   2        2
14  356     2011-05-04 11:16:00.000  0    10   2        3
15  356     2011-05-04 14:49:00.000  1    4    3        1
16  356     2011-05-04 14:50:00.000  1    4    3        2
17  356     2011-05-04 14:50:00.000  1    4    3        3
18  356     2011-05-04 14:51:00.000  1    4    3        4
19  356     2011-05-04 14:52:00.000  0    14   3        1
20  356     2011-05-04 14:52:00.000  0    14   3        2
21  356     2011-05-04 20:52:00.000  0    14   3        3

您可以看到ign=1 序列现在可以在seqRank 的帮助下与ign=0 序列匹配。并且只从每个序列中挑选最早的事件(按eventRank=1 过滤),我们将获得所有ign=1 序列的开始和结束时间。所以final CTE 的结果是:

UnitId  EventStart               EventEnd
------  -----------------------  -----------------------
356     2011-05-04 10:41:00.000  2011-05-04 10:48:00.000
356     2011-05-04 11:14:00.000  2011-05-04 11:16:00.000
356     2011-05-04 14:49:00.000  2011-05-04 14:52:00.000

显然,如果最后一个ign=1 序列后面没有ign=0 事件,它将不会显示在最终结果中,因为最后一个ign=1 序列将没有匹配的ign=0 序列,使用上述方法。

有一种可能的情况是此查询无法按原样工作。当事件列表以ign=0 事件而不是ign=1 开始时。如果这确实可行,您只需将以下过滤器添加到 ranked CTE:

WHERE NOT (ign = 0 AND Grp = 0)
-- Alternatively: WHERE ign <> 0 OR Grp <> 0

它利用了Grp 的第一个值将始终为0 这一事实。因此,如果将0 分配给具有ign=0 的事件,则应排除这些事件。


有用的阅读:

【讨论】:

  • 我今天只需要这个查询一次,您的帮助又为我节省了很多时间。非常感谢
  • 嘿,你能回来告诉我真是太好了,谢谢! (更是如此,因为我也能够发现旧的错字。)
  • 你能帮我修改上面的查询来计算点火时间吗?我一直在尝试,但没有什么对我有用
  • @user867198:可能对final CTE 进行以下更改就足够了:#1) 在EventStartEventEnd 之间交换表别名(s e,仅在 SELECT 子句中),#2)将s.seqRank = e.seqRank 更改为s.seqRank = e.seqRank + 1(在连接条件中)。如果这没有帮助,请考虑发布一个新问题。
【解决方案2】:

时间不早了,这次不把所有的代码都写出来,我就总结一下怎么做吧:

将表连接到自身,其中右侧的连接条件之一是相关子查询,该查询从表中选择前 1 分钟(事件时间),即 > 左侧的当前行。然后使用 where 子句将其过滤到左侧 ign 列 到右侧 ign 列的行。这会让你得到你的改变。使用类似的技术第三次连接回表,您将获得具有开始时间和结束时间的记录,您可以使用日期函数来获取分钟值。

【讨论】:

    【解决方案3】:

    你可以根据row_number()加入你自己的表

    select t1.RowNo as t1Row, t2.RowNo as t2Row, t1.StarTime, t2.EndTime as EndTime, cast(t2.EndTime - t1.StarTime as time) as Duration, t1.ign as IgnPeriod
    from (select eventtime as StarTime, ign, ROW_NUMBER() over (order by eventtime) as RowNo from UnitTable)  as t1
    inner join (select * from (select eventtime as EndTime, ROW_NUMBER() over (order by eventtime) as RowNo from UnitTable) as t_inner) t2 on t1.RowNo + 1 = t2.RowNo
    

    然后您可以将 ign=1 周期的持续时间相加:

    select cast(dateadd(millisecond,sum(datediff(millisecond,0,duration)),0) as time) from 
    (select cast(t2.EndTime - t1.StarTime as time) as Duration, t1.ign as IgnPeriod
    from (select eventtime as StarTime, ign, ROW_NUMBER() over (order by eventtime) as RowNo from UnitTable)  as t1
    inner join (select * from (select eventtime as EndTime, ROW_NUMBER() over (order by eventtime) as RowNo from UnitTable) as t_inner) t2 on t1.RowNo + 1 = t2.RowNo
    ) t_outer
    where t_outer.IgnPeriod = 1
    

    【讨论】:

    • 谢谢,它没有给我正确的答案。您已经编写了 t1.RowNo +1 = t2.RowNo 条件,我认为这会导致错误的结果。
    • t1.RowNo +1 = t2.RowNo 是将 t1 中的一个 StartTime 映射到 t2 中的下一个 EndTime。在我的带有您的样本数据的测试表中,我得到了 12 分钟。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多