如何有效地获取子查询的行索引或使用 row_number() 加入 table

How to efficiently get the row indicies of a subquery or joined table using row_number()

我有 table 个 'games',其中一些游戏(但不是全部)按活动 ID 分组到 'campaigns'。

我正在尝试编写一个 SQL 查询,该查询将获取包含有关游戏的各种信息的数据集,但特别是:如果给定游戏是广告系列的一部分,则该广告系列中有多少游戏总数(我有这个工作)和活动中该游戏的索引(例如活动中最早的游戏是索引“1”,下一个是“2”等等)。

我已经做到了,但是执行计划看起来很糟糕,而且修复它的明显方法也行不通,但我想得太早了。

这是工作查询,删除了一些无关的东西:

        g1.`id` AS `game_id`,
        (SELECT
            COUNT(*)
            FROM `games` g3
            WHERE g3.`campaign` = g1.`campaign`
        ) AS `campaign_length`,
        ca2.`ri` AS `campaign_index`,
        ca1.`id` AS `campaign_id`, ca1.`name` AS `campaign_name`
    FROM `games` g1
    LEFT JOIN `campaigns` ca1 ON ca1.`id` = g1.`campaign`
    LEFT JOIN (
        SELECT 
            g4.`id` AS `id`,
            ROW_NUMBER() OVER (
                PARTITION BY g4.`campaign`
                ORDER BY g4.`start` ASC) AS `ri`
            FROM `games` g4
        ) AS ca2 ON ca2.`id` = g1.`id`
    WHERE g1.`end` > CURRENT_TIMESTAMP()
    AND g1.`gamemaster` = 25
    ORDER BY g1.`start` ASC
    ;

此版本的问题在于,对于 table g4,执行计划列出了完整的 table 扫描 - 目前这很好,因为只有几百条记录,但长期来看对性能来说很糟糕,特别是因为这个查询(或非常相似的查询)将在我网站的许多不同页面上执行。我相信这是因为 ROW_NUMBER() 函数需要在 LEFT JOIN 的 ON 语句将它们过滤到我实际需要的行之前对所有行进行编号。

最明显的解决方案是添加 WHERE g4.`campaign` = g1.`campaign`FROM `games` g4 之后; 这样 ROW_NUMBER() 只需要对那些有可能在数据集中返回的记录进行编号。但是,这不起作用,因为 g1.`campaign` 不在范围内。

我可以 WHERE g4.`campaign` IS NOT NULL 至少将执行计划降低到条件索引而不是完整的 table 扫描,但它仍然不会随着活动中的游戏数量很好地扩展随着时间的推移而增长。

我知道我的“显而易见的解决方案”不会因为范围问题而起作用,但是有人建议我如何在没有糟糕的执行计划的情况下实现我想要做的事情吗?

根据您的意见,campaign_index 必须 计算 应用 WHERE 子句之前。这意味着 campaign_index 的计算将 总是 需要完整的 table 扫描,因为 WHERE 子句 不能减少正在计算的行数。

但是,您可以使用窗口函数而不是自连接和相关子查询...

WITH
  games AS
(
  SELECT
    *,
    COUNT(*)
      OVER (
        PARTITION BY `campaign`
      )
        AS `campaign_length`,
    ROW_NUMBER()
      OVER (
        PARTITION BY `campaign`
            ORDER BY `start`
      )
        AS `campaign_index`
    FROM
        games
)
SELECT
    games.*,
    campaigns.`name`   AS `campaign_name`
FROM
  games
LEFT JOIN
  campaigns
    ON campaigns.`id` = games.`campaign`
WHERE
      games.`end`        > CURRENT_TIMESTAMP()
  AND games.`gamemaster` = 25
ORDER BY
  games.`start`
;

用新的 AUTO_INCREMENT id 将 table 复制到新的 table 中。这将快速添加行号。

CREATE TABLE new_list (
    row_num INT AUTO_INCREMENT NOT NULL,
    INDEX(row_num) ) ENGINE=InnoDB
  SELECT ... FROM ...
      ORDER BY ...   -- this will do the sorting before numbering