如何在使用JOIN时优化COUNT子查询

时间:2013-07-23 11:51:55

标签: mysql sql join subquery

我正在构建一个查询,用于根据所选的Items生成Category的索引页,通过计算Likes的数量和数量来排序相对受欢迎程度该项目在过去24小时内已添加到List的次数。查询的单个输入是主类别ID。

这涉及总共4个表,其中一个是嵌套集,因此它并非完全无关紧要。我一般都非常擅长编写合理有效的SQL,但我很难让JOIN按照我想要的方式工作。

分类

由于类别是嵌套的并且项目被分配到单个类别,因此必须首先选择查询输入中指定的类别之下的所有类别。

我使用awesome_nested_set gem来完成这项工作。它添加了lftrgt列,可以毫不费力地从层次结构中进行选择:

SELECT c2.*
FROM categories c1
JOIN categories c2
    ON c2.lft >= c1.lft AND c2.rgt <= c1.rgt
WHERE c1.id = [MAIN CATEGORY ID]

然后扩展上述内容以选择项目非常简单:

SELECT i.*
FROM categories c1
JOIN categories c2
    ON c2.lft >= c1.lft AND c2.rgt <= c1.rgt
JOIN items i
    ON i.category_id = c2.id
WHERE c1.id = [MAIN CATEGORY ID]

到目前为止,一切都运行良好。快速执行。最后要做的事情(当然忽略分页)就是订购它们。

人气

项目按人气排序。计算项目受欢迎程度的方法是:

(number of likes) + (number of times added to list) * 5

e.g。如果一个项目已被添加到32个列表&amp;喜欢483次,人气指标是643。

根据用户是查看“最受欢迎时间”还是“趋势”,我们可能会将这些指标的计算限制为过去一天发生的喜欢/列表。

我认为这将是相对微不足道的,但它最终并非如此。当您使用COUNTJOIN时,显然会出现问题,如果项目有0个喜欢/列表,我需要使用LEFT JOIN。

目前正在运行的代码如下:

SELECT
    q.*,
    (q.likes + q.lists * 5) AS popularity
FROM
(
    SELECT
        i.*,
        (SELECT COUNT(*) FROM likes l WHERE i.id = l.item_id AND l.created_at > DATE_SUB(NOW(), INTERVAL 1 day)) AS likes,
        (SELECT COUNT(*) FROM list_items li WHERE i.id = li.item_id AND li.created_at > DATE_SUB(NOW(), INTERVAL 1 day)) AS lists
    FROM categories c1
    JOIN categories c2
        ON c2.lft >= c1.lft AND c2.rgt <= c1.rgt
    JOIN items i
        ON i.category_id = c2.id
    WHERE c1.id = 37
) q
ORDER BY popularity

然而,这显然是非常可怕的代码。每个项目需要制作两个子查询和然后整个事情需要被包裹才能做一些算术(虽然我认为这不是太糟糕)。

我尝试了以下方法,但由于各种原因,它们没有奏效:

SELECT
    i.*,
    (SELECT COUNT(*) FROM likes l WHERE i.id = l.item_id AND l.created_at > DATE_SUB(NOW(), INTERVAL 1 day)) AS likes,
    (SELECT COUNT(*) FROM list_items li WHERE i.id = li.item_id AND li.created_at > DATE_SUB(NOW(), INTERVAL 1 day)) AS lists,
    (likes + lists * 5) AS popularity

出于某种原因,您无法对正在选择的其他列进行数学运算。

SELECT
    i.*,
    COUNT(l.id) as likes,
    COUNT(li.id) as lists
FROM categories c1
JOIN categories c2
    ON c2.lft >= c1.lft AND c2.rgt <= c1.rgt
JOIN items i
    ON i.category_id = c2.id
LEFT JOIN likes l
    ON l.item_id = i.id
LEFT JOIN list_items li
    ON li.item_id = i.id
WHERE c1.id = 37

由于某种原因,您只能得到一个结果。我不明白这个原因。

SELECT
    i.*,
    COUNT(l.id) as likes,
    COUNT(li.id) as lists
FROM categories c1
JOIN categories c2
    ON c2.lft >= c1.lft AND c2.rgt <= c1.rgt
JOIN items i
    ON i.category_id = c2.id
LEFT JOIN likes l
    ON l.item_id = i.id
LEFT JOIN list_items li
    ON li.item_id = i.id
WHERE c1.id = 37
GROUP BY i.id

添加GROUP BY会使所有项目都返回,但是喜欢/列表编号现在完全错误。我认为它正在添加它们或其他东西。

基本上,我有点卡住了。上面带有子查询的示例可以工作,但我不认为它以理想的方式工作。我想让它仅与JOIN一起使用,但我很难理解如何。

非常感谢任何帮助:)

1 个答案:

答案 0 :(得分:2)

执行按item_id分组的子查询以获取计数,并对这些子查询进行LEFT JOIN。

这样的事情: -

SELECT
    q.*,
    (q.likes + q.lists * 5) AS popularity
FROM
(
    SELECT
        i.*,
        IFNULL(likes_count, 0) AS likes,
        IFNULL(lists_count, 0) AS lists
    FROM categories c1
    JOIN categories c2
        ON c2.lft >= c1.lft AND c2.rgt <= c1.rgt
    JOIN items i
        ON i.category_id = c2.id
    LEFT OUTER JOIN
    (
        SELECT item_id, COUNT(*) AS likes_count FROM likes WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 day) GROUP BY item_id
    ) likes
    ON likes.item_id = i.id
    LEFT OUTER JOIN
    (
        SELECT item_id, COUNT(*) AS lists_count FROM list_items li WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 day) GROUP BY item_id
    ) lists
    ON lists.item_id = i.id
    WHERE c1.id = 37
) q
ORDER BY popularity