如何有效地获取子查询的行索引或使用 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
我有 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