SQL查询挑战

时间:2009-10-05 20:55:13

标签: sql oracle analytics

所以这是另一个'向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。如果有帮助,可以使用分析函数。

谢谢!

8 个答案:

答案 0 :(得分: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

此查询首先确定某行是否与之前启动的任何行相邻。然后,我们将连续的行分组。

答案 1 :(得分: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

答案 2 :(得分:2)

基本上,你不能在纯集理论中做到这一点(找到森林的覆盖分区集)(例如,作为没有循环的有界#查询)。

以最像集合的方式进行,

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

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

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

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

  4. 即可
       select min(p1.start_time), max(p2.end_time), p1.partition,p2.partition
       from partitions p1, partitions p2
       where p1.partition = p2.partition
       group by p1.partition,p2.partition
    
    
        /* This will need to be tweaked using COALESCE 
           to deal with NULL end times in obvious way) */
    
    

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

答案 3 :(得分:0)

SELECT machineid, min(start_time), max(ifnull(end_time, '3000-01-01 00:00'))
FROM faults
GROUP BY machineid

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

答案 4 :(得分: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;

答案 5 :(得分:0)

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

像:

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

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

答案 6 :(得分:0)

答案 7 :(得分:0)

鹤鹤。

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

一样简单

SELECT machineID,period FROM Faults。

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

但是,由于你可能被迫在SQL中解决这个问题,而且系统不支持区间类型,我只能希望你有很大的勇气。

两个提示:

可以使用复杂的CASE结构在SQL中处理两个时间间隔的并集(如果是interval_values_overlap,那么那就是最低时间_最高时间,最高时间是最高时间)。

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