我试图找出实现以下目标的最有效方法:
我有两张table:
word_translations(id, word_id, language_id)
字符串
和
translations(id, text, word_translation_id, created_at, updated_at, user_id)
型
因此word_translations
和translations
表之间存在一对多的关系。如果创建的翻译user_id
不为null,则用户已经提供了自己的翻译,因此覆盖了默认翻译(每个用户的每个word_translation最多只能有一个(用户)翻译覆盖,但每个word_translation有多个默认翻译)。
我正在尝试编写SQL查询,它返回一个具有以下条件的翻译ID列表:
- 每个word_translation_id只有一个翻译id
- translation id指向特定word_translation(max(id))的最新翻译,除非有其他具有相同word_translation_id且user_id不为null的翻译。在这种情况下,它应该指向translation id,其中user_id不为null。
我已经写了下面的查询来这样做,但我不确定它的性能:
SELECT IF(t2.id IS NULL, t1.id, t2.id) as id, t1.word_translation_id
FROM (
SELECT MAX(t.id) as id, t.word_translation_id
FROM translations t
WHERE t.user_id IS NULL
GROUP BY t.word_translation_id) as t1
LEFT JOIN (
SELECT t.id, t.word_translation_id
FROM translations t
WHERE t.user_id IS NOT NULL) t2
ON t1.word_translation_id = t2.word_translation_id;
型
有没有更有效的方法来做到这一点?(另外,你看到上面的查询有任何问题吗?)
3条答案
按热度按时间mqkwyuun1#
使用两个与
UNION
结合使用的合并查询。查询1返回没有任何用户翻译的单词的最新默认翻译。
字符串
查询2返回用户翻译。
型
最终查询:
型
wlsrxk512#
您的第一个子查询(derived table)应该很快,索引在
(user_id, word_translation_id, id)
上。一个
LEFT JOIN
到一个 derived table 可能会非常慢,因为没有索引来支持连接。(在LEFT JOIN
中)使用外部表的索引。但某些派生表可以有索引。这是当子查询无论如何都必须排序时的情况,例如,在GROUP BY
查询或带有ORDER BY
和LIMIT
的查询中。例如,您的第一个子查询将在GROUP BY
列上具有索引。尝试将第二子查询更改为
字符串
这应该会导致派生表在
(word_translation_id, id )
上有一个索引,这应该能够支持ON
子句。但是这也会导致 filesort,因为我们在WHERE子句中有一个范围条件。在虚拟列上使用索引的技巧甚至可以摆脱 filsort。您需要的是在WHERE子句中的相等条件,如
WHERE t.user_isset = 1
。因此,让我们创建虚拟列和索引:
型
这是一个完美的索引
型
然而-在你的情况下,第二个子查询甚至是不必要的。你也可以这样写查询:
型
但是在这里,
t2.user_id IS NOT NULL
也有同样的问题,这是一个范围条件。所以你需要另一个索引,以word_translation_id
开始,理想情况下是在(word_translation_id, user_id, id)
上。或者你可以使用上面的虚拟列和索引,并将条件t2.user_id IS NOT NULL
改为t2.user_isset = 1
,使用一个完美的覆盖索引。另一个简单的解决方案是使用
LIMIT 1
的相关子查询来为每个word_translations.id
获取所需的translations.id
。型
这个查询的最佳索引是
(word_translation_id, user_id, id)
。但这依赖于NULL总是先排序。如果你想避免它,你可以再次使用一个带索引的虚拟列:型
而查询:
型
order by t.user_isset desc, t.id desc
在这里工作,因为我们要么得到唯一一行user_isset = 1
,或者如果不存在-行user_isset = 0
和最高的id
。这个查询应该是相当快的,在许多数据集上很难超越性能。它是 O(m * log(n)),其中 m =
word_translations
中的行,n =translations
中的行。rryofs0p3#
我想你可以使用anti-join来简化它:
字符串
对于每一个翻译,查找同一个单词的另一个翻译,该翻译的id或user_id不是null(
JOIN
条件)。然后排除任何为true的行(WHERE
子句)。