在SQL查询中选择相邻的行

时间:2014-06-13 19:15:49

标签: sql database sqlite postgresql optimization

以下是一个不太适合RDBMS的问题,我想,但这就是我所处理的问题。

我正在尝试编写一个工具来搜索存储在数据库中的日志。 有些行可能是:

Time             | ID | Object | Description 
2012-01-01 13:37 | 1  | 1      | Something happened
2012-01-01 13:39 | 2  | 2      | Something else happened
2012-01-01 13:50 | 3  | 2      | Bad
2012-01-01 14:08 | 4  | 1      | Good
2012-01-01 14:27 | 5  | 1      | Bad
2012-01-01 14:30 | 6  | 2      | Good

Object是外键。在实践中,时间将随ID增加,但这不是实际约束。实际上还有更多的领域。它是一个Postgres数据库 - 我也希望能够支持SQLite,但我知道这可能是不可能的。

现在,我希望能够运行查询,例如,发生在对象2上的所有错误事件:

SELECT * FROM table WHERE Object = 2 AND Description = 'Bad';

但是,在结果周围查看一些上下文通常很有用 - 就像-C grep选项在搜索文本日志时非常有用。 对于上面的查询,如果我们想要任意一行上下文,除了行3之外,我们还需要第2行和第6行。

如果原始查询返回多行,则需要检索更多上下文。 请注意,不会从与对象1关联的事件中检索上下文;我们只消除对描述的限制。 此外,所涉及的顺序,以及因此确定与什么相邻的内容,是由时间字段引起的。

这指明了我想要实现的目标,但是相关的数据库相当大,至少与它运行的机器的功能相比。

最常被引用的获取相邻行的解决方案要求您在我将调用基本查询的每个结果中运行一个额外查询;这不好,因为这可能是成千上万的查询。

我当前最不好的解决方案是运行查询来检索可能是上下文的所有可能行的ID - 在上面的示例中,这将搜索与Object 2相关的所有行。然后我得到ID匹配基本查询,将(使用所有可能ID的列表)扩展到与基本查询匹配的行的ID列表或在上下文中,然后最终检索这些ID的数据。 这有效,但不够优雅和缓慢。 从远程计算机使用该工具时速度特别慢,因为ID的初始列表可能非常大,并且检索它然后只是通过互联网传输它可能是过度的。

我尝试过的另一个解决方案是使用子查询或视图来计算"缓冲序列"的行。 以下是添加此字段后表格的内容:

Time             | ID | Sequence | Object | Description 
2012-01-01 13:37 | 1  | 1        | 1      | Something happened
2012-01-01 13:39 | 2  | 1        | 2      | Something else happened
2012-01-01 13:50 | 3  | 2        | 2      | Bad
2012-01-01 14:08 | 4  | 2        | 1      | Good
2012-01-01 14:27 | 5  | 3        | 1      | Bad
2012-01-01 14:30 | 6  | 3        | 2      | Good

在此表上运行基本查询,然后允许您通过在Sequence值中添加或减去来生成所需的ID列表。 这消除了通过线路传输行的负载的问题,但现在数据库必须运行这个复杂的子查询,并且它的速度慢得令人无法接受 - 特别是在第一次运行时 - 鉴于用例,查询是零星的并且缓存不是很有效。

如果我负责架构,我可能只是将这个字段存储在数据库中,但我不是,所以欢迎任何改进建议。谢谢!

2 个答案:

答案 0 :(得分:2)

您应该使用ROW_NUMBER窗口函数

http://www.postgresql.org/docs/current/static/functions-window.html

邻接是一个抽象结构,依赖于显式排序(或PARTITION OVER)...你的意思是具有前面时间戳的那个吗?

决定你如何决定"邻近"你想要的,然后得到ROW_NUMBER超过这个标准。

完成后,您只需JOINROW_NUMBER +/- 1

答案 1 :(得分:0)

您可以尝试使用sqlite

SELECT DISTINCT t2.*
  FROM  (SELECT * FROM t WHERE object=2 AND description='Bad') t1
      JOIN
        (SELECT * FROM t WHERE object=2) t2
      ON t1.id = t2.id OR
        t2.id IN (SELECT id FROM t WHERE object=2 AND t.time<t1.time ORDER BY t.time DESC LIMIT 1) OR
        t2.id IN (SELECT id FROM t WHERE object=2 AND t.time>t1.time ORDER BY t.time ASC  LIMIT 1)
ORDER BY t2.time
;

按更多上下文更改限制值