优化 SQL 计数

optimizing SQL counts

我必须 select 来自一个 table 的目录列表,并在另外两个 table 中执行计数:商店和类别。计数器应显示有多少商店和类别链接到每个目录。 我已经设法使用此 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;

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

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

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 更改为 s.company_id = c.company_id。好像是关联子句。

您可以通过将 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 操作,这样您仍然会看到目录条目,即使它们没有在您的类别或商店表格中提及。