9

所以这是另一个“向 X 编写查询”挑战。

我正在监视许多联网的自动售货机。每台机器都有许多部件,例如钞票接收器、硬币系统、打印机等。

机器零件的问题记录在表中,我们称之为“故障”,它看起来像这样(省略了不相关的字段):

machineid           partid         start_time            end_time
---------           ------         ----------------      ----------------
       1                2          2009-10-05 09:00      NULL
       1                3          2009-10-05 08:00      2009-10-05 10:00
       2                2          2009-09-30 12:00      2009-09-30 14:00
       3                4          2009-09-28 13:00      2009-09-28 15:00
       3                2          2009-09-28 12:00      2009-09-28 14:00

如果问题当前正在进行,则 end_date 为 NULL。

我需要一个查询来显示整个机器停机的时间段,并且可以解释重叠范围,将它们折叠成一条记录。因此,对于上面的示例数据,它将产生:

machineid          start_time            end_time
---------          ----------------      ----------------
       1           2009-10-05 08:00      NULL
       2           2009-09-30 12:00      2009-09-30 14:00
       3           2009-09-28 12:00      2009-09-28 15:00

编写程序代码来逐行执行此操作并不难,但一个好的声明性 SQL 查询会更有用、更优雅。似乎应该是可能的,但我只是无法到达那里。

SQL方言是Oracle。如果有帮助,可以使用分析函数。

谢谢!

4

8 回答 8

7

使用分析,您可以构建一个对数据进行单次传递的查询(对于大型数据集,这将是最有效的):

SELECT machineid, MIN(start_time), MAX(end_time)
  FROM (SELECT machineid, start_time, end_time, 
               SUM(gap) over(PARTITION BY machineid 
                             ORDER BY start_time) contiguous_faults
           FROM (SELECT machineid, start_time, 
                        coalesce(end_time, DATE '9999-12-31') end_time,
                         CASE
                            WHEN start_time > MAX(coalesce(end_time, 
                                                           DATE '9999-12-31'))
                                              over(PARTITION BY machineid 
                                                   ORDER BY start_time 
                                                   ROWS BETWEEN UNBOUNDED PRECEDING
                                                            AND 1 preceding)
                            THEN 1
                         END gap
                    FROM faults))
 GROUP BY machineid, contiguous_faults
 ORDER BY 1, 2

此查询首先确定一行是否与之前开始的任何行相邻。然后我们对连续的行进行分组。

于 2009-10-05T22:37:12.043 回答
2

基本上,在纯集合论中(例如,作为有界 # 的没有循环的查询),您无法做到(找到森林的覆盖分区集)。

以最像集合的方式来做,

  1. 为林分区创建一个临时表(10 或 11 列,4 个来自故障 #1,4 个来自故障 #2,1 个分区 ID,1 个用于插入节点的轮次,1 个用于我想不到的各种优化发烧38度。

  2. 运行一个循环(BFS 或 DFS,无论你发现什么更容易实现森林分区算法)。与图表相比,棘手的部分是您可以将许多子树从顶部连接到当前子树

    您可以使用sheepsimulator 的查询作为循环的基本构建块(例如查找2 个连接节点)

  3. 分区循环完成后,只需执行

   选择 min(p1.start_time), max(p2.end_time), p1.partition,p2.partition
   从分区 p1,分区 p2
   其中 p1.partition = p2.partition
   按 p1.partition,p2.partition 分组
   

    /* 这需要使用 COALESCE 进行调整
       以明显的方式处理 NULL 结束时间)*/

我很抱歉没有拼写森林分区的确切代码(它可能在树分区下归档) - 我累死了,我确信现在一些谷歌搜索会产生一个,因为你知道 tdata 结构和问题名称(或者你可以将其发布为 StackOverflow 上的更精确公式化的 Q - 例如“如何实现一种算法,以在 SQL 中将森林完全划分为循环”。

于 2009-10-05T21:40:43.913 回答
2
SELECT  DISTINCT 
        t1.machineId, 
        MIN(t2.start_time) start_time, 
        MAX(COALESCE(t2.end_time, '3210/01/01')) end_time
FROM FAULTS t1
JOIN FAULTS t2 ON t1.machineId = t2.machineId
                  AND ((t2.start_time >= t1.start_time
                       AND (t1.end_time IS NULL OR t2.start_time <= t1.end_time)
                  )
                  OR
                  (t1.start_time >= t2.start_time 
                       AND (t2.end_time IS NULL OR t1.start_time <= t2.end_time) 
                  ))
GROUP BY t1.machineId, t1.part_id

我在以下数据上测试了这个查询:

machine_id   |part_id |start_time           |end_time
-------------------------------------------------------------------------
1           |2       |05 Oct 2009 09:00:00  |NULL
1           |3       |05 Oct 2009 08:00:00  |05 Oct 2009 10:00:00
2           |2       |30 Sep 2009 12:00:00  |30 Sep 2009 14:00:00
2           |3       |30 Sep 2009 15:00:00  |30 Sep 2009 16:00:00
2           |4       |30 Sep 2009 16:00:00  |30 Sep 2009 17:00:00
3           |2       |28 Sep 2009 12:00:00  |28 Sep 2009 14:00:00
3           |4       |28 Sep 2009 13:00:00  |28 Sep 2009 15:00:00

我懂了:

machine_id   |start_time             |end_time
-----------------------------------------------------------------
1           |05 Oct 2009 08:00:00   |01 Jan 3210 00:00:00
2           |30 Sep 2009 12:00:00   |30 Sep 2009 14:00:00
2           |30 Sep 2009 15:00:00   |30 Sep 2009 17:00:00
3           |28 Sep 2009 12:00:00   |28 Sep 2009 15:00:00
于 2009-10-05T21:37:53.990 回答
0

我相信您需要一个存储过程来执行此操作,或者类似递归的“公共表表达式 (CTE)”(存在于 SQL srever 中),或者其他(在单个 SQL 语句中)您将无法获得正确的答案当 3 行或更多行共同构成一个连续的涵盖日期范围时。

喜欢:

 |----------|
           |---------------|
                        |----------------|

在没有实际进行练习的情况下,我可能会建议在存储过程中,构建一个包含所有“候选日期”的表,然后构建一个包含现有行中日期范围未涵盖的所有日期的表,然后构建你的通过“否定”这个集合来输出结果集。

于 2009-10-05T21:46:54.427 回答
0
SELECT machineid, min(start_time), max(ifnull(end_time, '3000-01-01 00:00'))
FROM faults
GROUP BY machineid

应该完成这项工作(如果需要,用等效的 Oracle 函数替换 ifnull )。

于 2009-10-05T21:03:10.363 回答
0

我希望我有时间给出一个完整的答案,但这里有一个提示可以找到重叠的停机时间:

select a.machineid, a.start_time, a.end_time, b.start_time, b.end_time
from faults a,
     faults b,
where a.machineid = b.machineid
  and b.start_time >= a.start_time
  and b.start_time <= a.end_time;
于 2009-10-05T21:10:18.460 回答
0

请参阅此讨论-底部附近有一个解决方案:http: //www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg= microsoft.public.sqlserver.programming&tid=2bae93da-c70e-4de4-a58b -d8cc0bf8ffd5

于 2009-10-05T22:03:07.167 回答
0

呵呵。

在支持区间类型的 SIRA_PRISE 中,解决这个问题就像

SELECT machineID, period FROM Faults。

IN which 'period' 是时间间隔类型的属性,其起点和终点是 SQL 表的 start_time 和 end_time。

但既然你大概是被迫在 SQL 中解决这个问题,并且系统不支持区间类型,我只能祝你有很大的勇气。

两个提示:

可以使用复杂的 CASE 构造在 SQL 中处理两个区间的联合(如果 interval_values_overlap 然后是最低开始时间最高结束时间,所有这些东西)。

由于您无法事先知道将合并多少行,因此您可能会发现自己被迫编写递归 SQL。

于 2009-10-05T22:27:46.753 回答