所以这是另一个'向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。如果有帮助,可以使用分析函数。
谢谢!
答案 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)
基本上,你不能在纯集理论中做到这一点(找到森林的覆盖分区集)(例如,作为没有循环的有界#查询)。
以最像集合的方式进行,
为林分区创建一个临时表(10或11列,4个来自失败#1,4个来自失败#2,1个来自分区ID,1个用于插入节点,1个用于各种优化)我想不到发烧38C。
运行循环(BFS或DFS,无论您发现什么,都可以更轻松地实现林分区算法)。与图形相比,棘手的部分是,您可以将许多子树从顶部连接到当前子树
您可以使用模拟器的查询作为循环的基本构建块(例如,找到2个连接的节点)
完成分区循环后,只需执行
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)
请参阅此讨论 - 使用靠近底部的解决方案:http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=2bae93da-c70e-4de4-a58b-d8cc0bf8ffd5
答案 7 :(得分:0)
鹤鹤。
在支持区间类型的SIRA_PRISE中,解决这个问题就像
一样简单SELECT machineID,period FROM Faults。
其中'period'是时间间隔类型的属性,其起点和终点是SQL表的start_time和end_time。
但是,由于你可能被迫在SQL中解决这个问题,而且系统不支持区间类型,我只能希望你有很大的勇气。
两个提示:
可以使用复杂的CASE结构在SQL中处理两个时间间隔的并集(如果是interval_values_overlap,那么那就是最低时间_最高时间,最高时间是最高时间)。
由于您无法预先知道将合并多少行,您可能会发现自己被迫编写递归SQL。