首页 > 解决方案 > mysql sub select很慢

问题描述

我有一张这样的桌子:

-------------------------------
| id | valid_until |  username|
-------------------------------
| 1  | 2020-01-01  |   user1  |
-------------------------------  
| 1  | 2020-01-01  |   user2  |
-------------------------------  
| 1  | 2020-01-02  |   user3  |
-------------------------------  
| 1  | 2020-01-02  |   user4  |
-------------------------------  
| 1  | 2020-01-03  |   user5  |
-------------------------------  
| 1  | 2020-01-03  |   user6  |
-------------------------------  
| 1  | 2020-01-03  |   user7  |
-------------------------------  

这是用户订阅表,valid_until表示订阅何时结束。

我想知道每天的活跃订阅,所以我有一个范围,例如2020-01-01TO 2020-01-03,这是我的查询:

SELECT 
    valid_until qva,
    (SELECT 
            COUNT(iod.id) AS ct
        FROM
            `order_detail` iod
        WHERE
            valid_until > qva)
FROM
    `order_detail`
WHERE
    valid_until >= '2020-01-01'
        AND valid_until <= '2020-01-03'
GROUP BY qva

但是这个查询太慢了,我的查询有什么问题?响应时间(230 秒)

标签: mysqlsqlmariadb

解决方案


根据 SQL 标准,您的查询无效。您有GROUP BY qva(并且通常不允许在此处使用别名,但在 MySQL 和 MariaDB 中是这样),但您没有对子查询结果应用聚合函数。MySQL 以违反标准而闻名,它们默默地应用于ANY_VALUE此类未聚合的表达式。

这会使查询变慢。对于日期范围内的每个订单,都会评估计数,仅在最后每个日期选择其中一个计数。

所以,让我们从头开始构建查询。您希望给定日期范围内的每一天都有一个结果行。那是:

select distinct valid_until
where valid_until between date '2020-01-01' and date '2020-01-03'
from order_detail;

然后,对于这些日期中的每一个,您都想获得计数。您可以像在原始查询中一样在子查询中执行此操作。我想它必须>=不是>,因为订单在一天之前有效,当天仍然有效(或者至少这是我所期望的)。

SELECT 
  dates.day,
  (
    select count(*) 
    from order_detail od
    where od.valid_until >= dates.day
  ) as ct
FROM
(
  select distinct valid_until as day
  where valid_until between date '2020-01-01' and date '2020-01-03'
  from order_detail
) dates
order by dates.day;

(这假设这valid_until只是一个日期。如果它是一个日期时间,你将不得不稍微调整这个查询。)

更新:

正如 ysth 在请求评论中告诉您的那样,您的查询只会让您获得在给定范围内存valid_until在于您的表中的天数。我的也是。如果您无论如何都想要三天,则必须替换子查询并使其独立于表,例如

FROM
(
  select date '2020-01-01' as day union all
  select date '2020-01-02' as day union all
  select date '2020-01-03' as day
) dates

推荐阅读