优化 SQL 计数

时间:2021-05-04 10:45:22

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

我必须从一个表中选择一个目录列表,并在另外两个表中执行计数:Stores 和 Categories。计数器应显示有多少商店和类别链接到每个目录。 我设法使用此 SQL 查询获得了所需的功能:

 SELECT `catalog`.`id` AS `id`,
       `catalog`.`name` AS `name`,
       (
            SELECT COUNT(*)
              FROM `category`
              WHERE `category`.`catalog_id` = `catalog`.`id`
               AND `category`.`is_archive` = 0
               AND `category`.`company_id` = 2
       ) AS `category_count`,
       (
            SELECT COUNT(*)
              FROM `store`
              WHERE `store`.`catalog_id` = `catalog`.`id`
               AND `store`.`is_archive` = 0
               AND `store`.`company_id` = 2
       ) AS `store_count`
  FROM `catalog`
 WHERE `catalog`.`company_id` = 2
   AND `catalog`.`is_archive` = 0
 ORDER BY `catalog`.`id` ASC;

这按预期工作。但我不喜欢执行子查询,因为它们很慢,而且这个查询在 LARGE 列表上可能执行得很差。有没有什么方法可以使用 JOIN 优化这个 SQL? 提前致谢。

2 个答案:

答案 0 :(得分:2)

您可以通过将 SELECT 子句中的依赖子查询重构为(如您所述)JOINed 聚合子查询,从而加快速度。

您可以用这种方式编写的第一个子查询。

                SELECT COUNT(*) num, catalog_id, company_id
                  FROM category
                 WHERE is_archive = 0
                 GROUP BY catalog_id, company_id

第二个是这样的。

                SELECT COUNT(*) num, catalog_id, company_id
                  FROM store
                 WHERE is_archive = 0
                 GROUP BY catalog_id, company_id

然后,在主查询中使用它们,就像它们是包含您想要的计数的表一样。

SELECT catalog.id,
       catalog.name,
       category.num category_count,
       store.num store_count
  FROM catalog
  LEFT JOIN (
            SELECT COUNT(*) num, catalog_id, company_id
              FROM category
             WHERE is_archive = 0
             GROUP BY catalog_id, company_id
       ) category  ON catalog.id = category.catalog_id
                  AND catalog.company_id = category.company_id
  LEFT JOIN (
            SELECT COUNT(*) num, catalog_id, company_id
              FROM store
             WHERE is_archive = 0
             GROUP BY catalog_id, company_id
       ) store  ON catalog.id = store.catalog_id
               AND catalog.company_id = store.company_id
 WHERE catalog.is_archive = 0
   AND catalog.company_id = 2
 ORDER BY catalog.id ASC;

这比您的示例更快,因为每个子查询只需要运行一次,而不是每个目录条目一次。它还有一个不错的功能,您只需说一次 WHERE catalog.company_id = 2。 MySQL 优化器知道如何处理它。

我建议执行 LEFT JOIN 操作,这样即使您的类别或商店表中未提及目录条目,您仍会看到它们。

答案 1 :(得分:0)

子查询很好,但您可以简化查询:

function toInt($str)
{
    return (int)$str;
}

为了性能,您需要索引:

  • SELECT c.id, c.name, COUNT(*) OVER (PARTITION BY c.catalog_id) as category_count, (SELECT COUNT(*) FROM store s WHERE s.catalog_id = s.id AND s.is_archive = 0 AND s.company_id = c.company_id ) AS store_count FROM catalog c WHERE c.company_id = 2 AND c.is_archive = 0 ORDER BY c.id ASC;
  • catalog(company_id, is_archive, id)

由于外部查询中的过滤,相关子查询可能是从 store(catalog_id, company_id, is_archive) 获取结果的最佳执行方式。

还要注意查询的一些变化:

  • 我删除了反引号。它们是不必要的,只会使查询变得混乱。
  • store 这样的表达式是多余的。无论如何,表达式被赋予 c.id as id 作为别名。
  • 我将 id 更改为 s.company_id = 2。这似乎是一个相关条款。