【问题标题】:Optimize MySQL query with dependent sub-query使用依赖子查询优化 MySQL 查询
【发布时间】:2014-03-26 00:09:45
【问题描述】:

我需要找到一种方法来消除依赖子查询。

我有一个可以有多种语言的文章表。 简化表结构如下:

id、标题、语言、translation_set_id

1 A    en 0
2 B    en 2
3 B_ru ru 2
4 C    en 4
5 C_ru ru 4
6 D    en 6
7 D_fr fr 6

当文章没有翻译时,translation_set_id 为 0,或者设置为基本翻译的 id。所以B是英文原文文章,B_ru是文章的俄文翻译。

我需要一个允许我返回所有俄语文章的查询,或者如果它们不存在原始语言文章。 所以它会返回。

1 A    en 0
3 B_ru ru 2
5 C_ru ru 4
6 D    en 6

到目前为止,我有这个:

SELECT id, title, language, translation_set_id
FROM articles a
WHERE 
  a.translation_set_id = 0
  OR (a.language = 'ru')
  OR (a.id = a.translation_set_id AND
       0 = (SELECT COUNT(ac.id)
            FROM articles ac
            WHERE ac.translation_set_id = a.translation_set_id 
            AND ac.language = 'ru')
     )

但这会为每一行执行子查询,创建一个依赖查询。 有没有办法消除依赖查询?

更新: Neels 的解决方案似乎有效,谢谢!

但我想知道是否有一种方法可以将解决方案推广到多种语言后备?首先尝试获取法语,如果不存在,尝试俄语,如果不存在,显示基本翻译(英语或任何其他,取决于原始创建语言)?

更新 2: 我已经使用 Neel 的解决方案和 DRapp 的解决方案构建了更新问题所需的查询。可以在这里找到http://www.sqlfiddle.com/#!2/28ca8/18,但为了完整起见,我也会在此处跳过查询。

修订数据:

CREATE TABLE articles (
  id INT,
  title VARCHAR(20),
  language VARCHAR(20),
  translation_set_id INT);

INSERT INTO articles values
  (1,'A','en',0),
  (2,'B','en',2),
  (3,'B_ru','ru',2),
  (4,'C','en',4),
  (5,'C_ru','ru',4),
  (6,'D','en',6),
  (7,'D_fr','fr',6),
  (8,'E_ru','ru', 0),
  (9,'F_fr','fr', 0),
  (10,'G_ru','ru', 10),
  (11,'G_fr','fr', 10),
  (12,'G_en','en', 10);

带有 2 个相关子查询的原始查询:

SELECT id, title, language, translation_set_id
FROM articles a
WHERE
  a.translation_set_id = 0
  OR (a.language = 'fr')
  OR (a.language = 'ru' AND
       0 = (SELECT COUNT(ac.id)
            FROM articles ac
            WHERE ac.translation_set_id = a.translation_set_id
            AND ac.language = 'fr'))
  OR (a.id = a.translation_set_id AND
       0 = (SELECT COUNT(ac.id)
            FROM articles ac
            WHERE ac.translation_set_id = a.translation_set_id
            AND (ac.language = 'fr' OR ac.language = 'ru'))
     );

修改后的查询:

SELECT  a.*
FROM articles a
LEFT JOIN articles ac ON ac.translation_set_id = a.id
  AND ac.language = 'fr'
LEFT JOIN articles ac2 ON ac2.translation_set_id = a.id
  AND ac2.language = 'ru'
WHERE a.translation_set_id = 0
  OR a.language = 'fr'
  OR (a.language = 'ru' AND ac.id IS NULL)
  OR (a.id = a.translation_set_id AND ac2.id IS NULL AND ac.id IS NULL);

【问题讨论】:

    标签: mysql optimization query-optimization mysql-dependent-subquery


    【解决方案1】:

    根据 Ypercube 对更简化的 where 子句的细微修改调整,并且您不需要使用 coalesce(),我在下面对此进行了修改。

    获取 Translated = 0 或 ID 与 Translated 相同的所有文章,表明它在被翻译为其他内容之前必须是原始文档。也就是说,我们保证您获得所有原始文件。

    现在,左连接。如果有相应的“俄语”文章(或其他感兴趣的语言翻译),请获取该 ID 及其翻译标题。所以返回的记录既有原文也有翻译后的参考文献。

    SELECT
          a1.id as OriginalAricleID,
          a1.title as OriginalTitle,
          a1.language as OriginalLanguage,
          a2.id as TranslatedAricleID,
          a2.title as TranslatedTitle
       from
          Articles a1
             LEFT JOIN Articles a2
                ON a1.id = a2.translation_set_id
                AND a2.language = 'ru'
       where
             a1.translation_set_id = 0
          OR a1.id = a1.translation_set_id 
    

    它遍历表一次,没有重复。左连接指向同一个文章表,但仅适用于基于原始文章的俄语集。

    【讨论】:

    • 这是一个聪明的解决方案,但不幸的是,由于 CMS 的限制,我无法使用 coalesce。除此之外,效果很好。
    • 可以将3个where条件改为where a1.language = 'en' ;
    • @ypercube 但是,如果 'fr' 中有原始语言并翻译成 'ch' 怎么办。没有“en”版本作为包含的基础。这就是我这样做的原因。第一篇文章的翻译 ID 为 0。如果不是,那么任何后续翻译的文档都会获得原始 ID,并且在原始文档上加盖自己的 ID,从而进行相等性测试。
    • @Placinta,为什么不使用 coalesce(),你能摆脱一个案例/什么时候?或 IF()?,否则您将需要一个联合来获取所有俄语,然后将所有其他人放入相同的列名。
    • @ypercube,是的,但我们无法猜测其数据的所有底层元素 :) 但是翻译到自身会指示原始数据,即使它是一个更高的数字,但它会使拥有一份原始文件更有意义,然后再拥有一份更新的文件。
    【解决方案2】:

    看看这个 SQL Fiddle:

    http://www.sqlfiddle.com/#!2/c05d0/15

    您可以使用这个简单的查询来实现您的结果。

    SELECT  a.*
    FROM articles a
    LEFT OUTER JOIN articles ac ON ac.translation_set_id = a.translation_set_id 
    AND ac.language = 'ru'
    WHERE a.translation_set_id = 0
    OR a.language = 'ru'
    OR (a.id = a.translation_set_id AND ac.id IS NULL); 
    

    【讨论】:

    • 这个查询可以给出重复值,不是吗?我认为它应该有一个DISTINCT 或一个GROUP BY
    • 我想给出一个 DISTINCT,但从数据来看似乎没有必要,因为只有一个标题满足 WHERE 子句中的条件。
    • 这似乎可行,谢谢!我接近了这样的东西,但我加入了 a.id = ac.translation_set_id,而不是两个翻译 id。另外,我不确定是否可以在同一个地方问另一个问题,但是否可以概括查询,通过多种语言回退?所以,先尝试显示俄语,如果没有找到,尝试显示法语,如果没有找到,原始基础翻译(英文)?
    【解决方案3】:

    你可以使用LEFT JOIN:

    SELECT a.id, a.title, a.language, a.translation_set_id
      FROM articles a
     LEFT JOIN articles ac ON ac.translation_set_id = a.translation_set_id 
                          AND ac.language = 'ru'
     WHERE a.translation_set_id = 0
        OR (a.language = 'ru')
        OR (    a.id = a.translation_set_id 
            AND ac.id IS NULL
           )
     GROUP BY a.id, a.title, a.language, a.translation_set_id
    

    【讨论】:

    • 为什么需要 Group By?没有任何聚合表达式的 group by 子句也合法吗?
    • 这是合法的。它是必需的,因为我们使用的是join,并且可能会发生这样的情况,如果没有GROUP BY,则会出现重复的结果(当然取决于表的数据)
    【解决方案4】:

    重写这部分:

    AND
           0 = (SELECT COUNT(ac.id)
                FROM articles ac
                WHERE ac.translation_set_id = a.translation_set_id 
                AND ac.language = 'ru')
    

    进入反连接条件:

    AND NOT EXISTS (
                    SELECT 1
                    FROM articles ac
                    WHERE ac.translation_set_id = a.translation_set_id 
                    AND ac.language = 'ru'
    )
    

    这可能会加快查询速度,因为 MySql 必须始终读取所有行才能获得 count(),
    但是当使用 NOT EXISTS(或 EXISTS)时,它会在找到第一行时停止读取表符合标准。

    【讨论】:

    • 它可能会加快查询速度,但我想知道是否有一个解决方案,无需为每一行运行子查询。
    猜你喜欢
    • 2014-07-31
    • 1970-01-01
    • 2017-04-17
    • 2011-11-27
    • 2021-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多