【问题标题】:Improving the performance of a MySQL left join sub query提高 MySQL 左连接子查询的性能
【发布时间】:2020-11-27 12:17:47
【问题描述】:

我有以下 MySQL 查询,它计算给定日期范围内每个月的订单总数,例如一年。查询可以正常工作,但性能很慢(大约 250 毫秒)。

关于如何重写它以提高效率的任何想法?

WITH recursive `dates` AS (
    (
        SELECT '2019-11-28' AS item
    )
    UNION
    ALL (
        SELECT
            item + INTERVAL 1 DAY
        FROM
            `dates`
        WHERE
            item + INTERVAL 1 DAY <= '2020-11-27'
    )
)
SELECT
    DATE_FORMAT(`item`, '%b %y') AS `date`,
    COUNT(`orders`.`id`) AS `total`
FROM
    `dates`
    LEFT JOIN (
        SELECT
            `orders`.`id`,
            `orders`.`created_at`
        FROM
            `orders`
            INNER JOIN `locations` ON `orders`.`location_id` = `locations`.`id`
        WHERE
            `orders`.`shop_id` = 10379184
            AND `locations`.`country_id` = 128
            AND `orders`.`created_at` >= '2019-11-28 12:01:42'
            AND `orders`.`created_at` <= '2020-11-27 12:01:42'
    ) AS `orders` ON DATE(`orders`.`created_at`) = `dates`.`item`
GROUP BY
    `date`

更新:有些人建议使用两个左连接,但是如果我这样做,则不会应用 country_id 过滤器:

WITH recursive `dates` AS (
    (
        SELECT
            '2019-11-28' AS item
    )
    UNION
    ALL (
        SELECT
            item + INTERVAL 1 DAY
        FROM
            `dates`
        WHERE
            item + INTERVAL 1 DAY <= '2020-11-27'
    )
)
SELECT
    DATE_FORMAT(`item`, '%b %y') AS `date`,
    COUNT(`orders`.`id`) AS `total`
FROM
    `dates`
    LEFT JOIN `orders` USE INDEX (`orders_created_at_index`) ON DATE(`created_at`) = `dates`.`item`
    AND `orders`.`shop_id` = 10379184
    AND `orders`.`created_at` >= '2019-11-28 12:22:43'
    AND `orders`.`created_at` <= '2020-11-27 12:22:43'
    LEFT JOIN `locations` ON `orders`.`location_id` = `locations`.`id`
    AND `locations`.`country_id` = 128
GROUP BY
    `date`

谢谢!

【问题讨论】:

  • (LEFT JOIN) 子查询的目的是什么?你不能简单地 LEFT JOIN 只是 order 表吗?
  • 限定列名,以便清楚列的来源。
  • 为什么不简单地... FROM dates LEFT JOIN orders LEFT JOIN locations ...?使用子查询的原因是什么?
  • 子查询的原因是如果我使用两个左连接,那么 country_id 不会被应用,例如sql LEFT JOIN `orders` USE INDEX (`orders_created_at_index`) ON DATE(`created_at`) = `dates`.`item` AND `orders`.`shop_id` = 10379184 AND `orders`.`created_at` &gt;= '2019-11-28 12:22:43' AND `orders`.`created_at` &lt;= '2020-11-27 12:22:43' LEFT JOIN `locations` ON `orders`.`location_id` = `locations`.`id` AND `locations`.`country_id` = 128
  • 需要左连接来保留生成的WITH RECURSIVE 日期。

标签: mysql sql mysql-8.0


【解决方案1】:

我建议使用相关子查询:

SELECT DATE_FORMAT(d.item, '%b %y') AS `date`,
       (SELECT COUNT(*)
        FROM orders o JOIN
             locations l
             ON o.location_id = l.id
        WHERE shop_id = 10379184 AND
              country_id = 128 AND
              o.created_at >= d.item AND
              o.created_at < d.item + interval 1 day
       ) as total
FROM dates d;

这避免了外部聚合,这通常是一种性能改进。

此外,索引可能有助于查询,但尚不清楚country_id 和shop_id 等列的来源。

【讨论】:

  • 感谢您的建议,但计数没有给出正确的值。 country_id 在 locations 表上。 shop_id 在 orders 表上。
  • 您将如何按date 分组并将total 相加?
  • @MattKingshott。 . .这将计算日期的总数。不需要聚合。
【解决方案2】:

经过多次修改,我制作了以下运行时间不到 40 毫秒的代码,这足以满足我的需求。我仍然认为这并不理想,欢迎任何改进......

SELECT
    `date`,
    COUNT(`order`)
FROM
    (
        WITH recursive `dates` AS (
            (
                SELECT
                    '2019-11-28' AS item
            )
            UNION
            ALL (
                SELECT
                    item + INTERVAL 1 DAY
                FROM
                    `dates`
                WHERE
                    item + INTERVAL 1 DAY <= '2020-11-27'
            )
        )
        SELECT
            DATE_FORMAT(`item`, '%b %y') AS `DATE`,
            `orders`.`id` AS `order`,
            `locations`.`id` AS `location`
        FROM
            `dates`
        LEFT JOIN 
            `orders` 
        ON 
            DATE(`created_at`) = `dates`.`item`
        AND 
            `orders`.`shop_id` = 10379184
        AND 
            `orders`.`created_at` >= '2019-11-28 12:22:43'
        AND 
            `orders`.`created_at` <= '2020-11-27 12:22:43'
        LEFT JOIN 
            `locations` 
        ON 
            `orders`.`location_id` = `locations`.`id`
        AND 
            `locations`.`country_id` = 209
    ) AS items
WHERE
    (
        `order` IS NULL
        AND `location` IS NULL
    )
    OR (
        `order` IS NOT NULL
        AND `location` IS NOT NULL
    )
GROUP BY
    `date`

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-03
    • 2023-04-08
    • 1970-01-01
    • 2023-03-14
    • 2012-08-31
    相关资源
    最近更新 更多