【问题标题】:Efficient way to get last 10 rows of each group in SQLite在 SQLite 中获取每个组的最后 10 行的有效方法
【发布时间】:2016-02-02 17:00:20
【问题描述】:

假设有一个具有以下架构的作业运行历史记录表:

job_runs
(
    run_id integer not null, -- identifier of the run
    job_id integer not null, -- identifier of the job
    run_number integer not null, -- job run number, run numbers increment for each job
    status text not null, -- status of the run (running, completed, killed, ...)
    primary key (run_id)
    -- ...
)

并且每个作业都需要使用status != 'running' 获得最后10 次运行(作业因job_id 而不同)。为此,我编写了以下查询:

SELECT
    *
FROM
    job_runs AS JR1
WHERE
    JR1.run_number IN
    (
        SELECT
            JR2.run_number
        FROM
            job_runs AS JR2
        WHERE
            JR2.job_id = JR1.job_id
            AND
            JR2.status != 'running'
        ORDER BY
            JR2.run_number
        DESC
        LIMIT
            10
    )

它可以满足我的需要,但即使在 job_runs 表的 job_idrun_num 字段上有一个多字段索引,查询也很慢,因为它会扫描 job_runs 表,并且它的每一行都运行子查询.索引每次都能帮助子查询快速运行,但嵌套查询扫描整个表的事实会降低性能。那么如何调整查询的性能呢?

一些想法:

作业数量(不同的job_ids)很少,如果 SQLite 中有 FOR 循环,则很容易遍历所有不同的 job_ids 并运行子查询 传递作业 ID 而不是 JR1.job_id 然后 UNION 所有结果。

重要:

请不要建议在我的应用程序的源代码中运行循环。我需要纯 SQL 解决方案。

【问题讨论】:

  • 我认为你不走运。 This has been asked before 他们想出了你的解决方案。 SQLite 非常有限,尝试在其中做所有事情并不总是最好的选择。为什么需要纯 SQL 解决方案?
  • 你可能想试试dba.stackexchange.com
  • @Schwern 感谢 dba.stackexchange.com,顺便说一句,有没有办法解决这个问题? “纯 SQL 解决方案”呢,是应用程序设计的问题。
  • 如果你的应用程序被设计成所有数据查询都必须 100% 在 SQLite 中执行,那么你就有一个严重的设计问题。 SQLite 不够灵活。你会再次遇到这个问题。我建议您切换数据库或更改您的应用程序设计。
  • 不,@Schwern 并非如此。

标签: performance sqlite select


【解决方案1】:

您可以通过为其创建covering index 来进一步提高子查询的性能:

CREATE INDEX xxx ON job_runs(job_id, run_number, status);

但最大的性能问题是子查询是针对每一行执行的,尽管您只需要为每个唯一的作业 ID 运行它。

所以,首先,只获取唯一的作业 ID:

SELECT DISTINCT job_id
FROM job_runs

然后,对于这些 ID 中的每一个,确定第十大的运行次数:

SELECT job_id,
       (SELECT run_number
        FROM job_runs
        WHERE job_id = job_ids.job_id
          AND status != 'running'
        ORDER BY run_number DESC
        LIMIT 1 OFFSET 9
       ) AS first_run_number
FROM (SELECT DISTINCT job_id
      FROM job_runs) AS job_ids

但是如果一个作业的运行编号少于十个,子查询会返回 NULL,所以让我们将其替换为一个较小的数字,以便下面的比较 (run_number >= first_run_number) 起作用:

SELECT job_id,
       IFNULL((SELECT run_number
               FROM job_runs
               WHERE job_id = job_ids.job_id
                 AND status != 'running'
               ORDER BY run_number DESC
               LIMIT 1 OFFSET 9
              ), -1) AS first_run_number
FROM (SELECT DISTINCT job_id
      FROM job_runs) AS job_ids

所以现在我们对每个作业进行了第一次有趣的运行。 最后,将这些值连接回原始表:

SELECT job_runs.*
FROM job_runs
JOIN (SELECT job_id,
             IFNULL((SELECT run_number
                     FROM job_runs
                     WHERE job_id = job_ids.job_id
                       AND status != 'running'
                     ORDER BY run_number DESC
                     LIMIT 1 OFFSET 9
                    ), -1) AS first_run_number
      FROM (SELECT DISTINCT job_id
            FROM job_runs) AS job_ids
     ) AS firsts
  ON job_runs.job_id = firsts.job_id
 AND job_runs.run_number >= firsts.first_run_number;

【讨论】:

  • 不知道OFFSET,谢谢!会试一试的。
猜你喜欢
  • 2014-03-03
  • 1970-01-01
  • 2011-08-17
  • 2021-02-22
  • 1970-01-01
  • 2011-03-27
  • 2020-05-21
  • 2022-01-11
  • 2016-09-02
相关资源
最近更新 更多