【问题标题】:SQL: Most efficient way to select sequences of rows from a tableSQL:从表中选择行序列的最有效方法
【发布时间】:2015-07-04 06:42:21
【问题描述】:

我有一个带标签的文本语料库,存储在如下 SQL 表中:

id  tag1 tag2  token  sentence_id                          
0     a    e   five        1
1     b    f  score        1
2     c    g  years        1
3     d    h    ago        1

我的任务是在表格中搜索满足特定条件的标记序列,有时每个标记之间存在间隙。

例如:

我希望能够搜索类似于以下的序列:

  • 令牌在tag1 列中的值为a,并且
  • 第二个标记与第一个标记相距一到两行,并且在tag2 中具有值g 或在tag1 中具有b,并且
  • 第三个标记应至少相隔三行,并且在token 列中包含ago

在 SQL 中,这将类似于以下内容:

SELECT * FROM my_table t1 
JOIN my_table t2 ON t1.sentence_id = t2.sentence_id 
JOIN my_table t3 ON t3.sentence_id = t1.sentence_id 
WHERE t1.tag1 = 'a' AND (t2.id = t1.id + 1 OR t2.id = t1.id + 2) 
AND (t2.tag2 = 'g' OR t2.tag1 = 'b') 
AND t3.id >= t1.id + 3 AND t3.token = 'ago' 

到目前为止,每次我在序列中指定一个新标记(例如JOIN my_table t4)时,我只能通过自行加入表来实现这一点,但是对于数百万行,这会变得非常慢。有没有更有效的方法来做到这一点?

【问题讨论】:

  • 我认为您的问题过于笼统,您应该将其限制为一两个典型查询,并展示您针对此查询的解决方案。
  • 另外:为什么你使用不同DBMS的标签?你不使用特定的系统?
  • @Renzo 已修改,感谢您的建议!
  • 非常有趣的问题。如果您在tag1tag2token 上有索引,您可能使用(SELECT * FROM my_table WHERE token = 'ago') AS ago 之类的子查询并使用您现有的连接方法(看起来真正 对我)在这些上。

标签: sql postgresql


【解决方案1】:

您可以尝试这种分阶段的方法:

  1. 将每个条件(各种距离条件除外)作为子查询应用
  2. 计算满足条件的token之间的距离
  3. 分别应用所有距离条件。

如果您在 tag1tag2token 列上有索引,这可能会有所改善:

SELECT DISTINCT sentence_id FROM
(
  -- 2. Here we calculate the distances
  SELECT cond1.sentence_id,
  (cond2.id - cond1.id) as cond2_distance,
  (cond3.id - cond1.id) as cond3_distance
  FROM
  -- 1. These are all the non-distance conditions
  (
    SELECT * FROM my_table WHERE tag1 = 'a'
  ) cond1
  INNER JOIN
  (
    SELECT * FROM my_table WHERE 
    (tag1 = 'b' OR tag2 = 'g')
  ) cond2
  ON cond1.sentence_id = cond2.sentence_id
  INNER JOIN
  (
    SELECT * FROM my_table WHERE token = 'ago'
  ) cond3
  ON cond1.sentence_id = cond3.sentence_id
) conditions
-- 3. Now apply the distance conditions
WHERE cond2_distance BETWEEN 0 AND 2
AND cond3_distance >= 3
ORDER BY sentence_id;

如果您将此查询应用于this SQL fiddle,您会得到:

| sentence_id |
|-------------|
|           1 |
|           4 |

这就是你想要的。现在无论它是否更快,只有您(拥有百万行数据库)才能真正判断,但从必须实际编写这些查询的角度来看,您会发现它们要容易得多阅读、理解和维护。

【讨论】:

    【解决方案2】:

    您需要编辑您的问题并提供有关这些标记序列如何工作的更多详细信息(例如,“每次我在序列中指定一个新标记”在实践中是什么意思?)。

    中,您可以使用window function 解决此类查询。按照您上面的确切规格:

    SELECT *,
           CASE
             WHEN lead(tag2, 2) OVER w = 'g' THEN lead(token, 2) OVER w
             WHEN lead(tag1) OVER w = 'b' THEN lead(token) OVER w
             ELSE NULL::text
           END AS next_token
    FROM my_table
    WHERE tag1 = 'a'
      AND next_token IS NOT NULL
    WINDOW w AS (PARTITION BY sentence_id ORDER BY id);
    

    lead() 函数从窗口框架中的当前行向前看一些行(默认为 1,当未指定时),在这种情况下,所有具有相同 sentence_id 的行在窗口定义。因此,lead(tag1, 2) 查看前面两行 tag1 的值以与您的条件进行比较,lead(token, 2) 从前面两行返回 token 作为列 next_token 在当前行中 > 并具有相同的sentence_id。如果第一个CASE 条件失败,则评估第二个条件;如果失败,则返回 NULL。请注意,CASE 子句中的条件顺序很重要:不同的顺序会产生不同的结果。

    显然,如果您继续为后续标记添加条件,则查询会变得非常复杂,您可能必须将各个搜索条件放在单独的存储过程中,然后根据您的要求调用它们。

    【讨论】:

    • 认为您可能需要一个介于WINDOW w 及其定义之间的AS
    • 还认为您在两个THEN 子句中都需要OVER w
    猜你喜欢
    • 2012-05-10
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-26
    相关资源
    最近更新 更多