Mysql 左连接执行时间太长
我有两张桌子。一个是日历,第二个是最终注册,如下所示:Mysql 左连接执行时间太长,mysql,sql,Mysql,Sql,我有两张桌子。一个是日历,第二个是最终注册,如下所示: *--------------------------* | calender_id | datefield | *--------------------------* | 1 | 2015-07-13 | | 2 | 2015-07-14 | | 3 | 2015-07-15 | | 4 | 2015-07-16 | | - | --
*--------------------------*
| calender_id | datefield |
*--------------------------*
| 1 | 2015-07-13 |
| 2 | 2015-07-14 |
| 3 | 2015-07-15 |
| 4 | 2015-07-16 |
| - | ---------- |
| - | ---------- |
| - | ---------- |
| 5647 | 2030-12-28 |
| 5648 | 2030-12-29 |
| 5649 | 2030-12-30 |
| 5650 | 2030-12-31 |
*--------------------------*
所以我的第一张表大约是5650条记录
现在,第二个表是我的注册表,我在其中存储用户信息和预订日期
*--------------------------------------------------*
| id | name | booking_date | ticket_status |
*--------------------------------------------------*
| 1 | RAM | 2018-12-24 12:54:53 | active |
| 2 | RAO | 2018-12-24 12:54:53 | active |
| 3 | RAT | 2018-12-24 12:54:53 | active |
| 4 | PAL | 2018-11-24 12:54:53 | active |
| 5 | TOM | 2018-10-24 12:54:53 | active |
| 6 | SAM | 2018-10-24 12:54:53 | active |
| 7 | RAT | 2018-09-24 12:54:53 | active |
| 8 | MAT | 2019-12-24 12:54:53 | active |
| 9 | NOT | 2019-12-24 12:54:53 | active |
| 10 | RAM | 2019-12-24 12:54:53 | active |
*--------------------------------------------------*
现在我想统计一下2018年哪本书的注册情况,按月份分类
| booking_date | countT |
| 2018-01 | 0 |
| 2018-02 | 0 |
| 2018-03 | 0 |
| 2018-04 | 0 |
| 2018-05 | 0 |
| 2018-06 | 0 |
| 2018-07 | 0 |
| 2018-08 | 0 |
| 2018-09 | 1 |
| 2018-10 | 2 |
| 2018-11 | 1 |
| 2018-12 | 3 |
我使用下面的查询,我的查询给出了正确的输出,但问题是执行时间。至少10分钟的执行时间太长
选择
日期\u FORMATcalendar.datefield,'%Y-%m'作为预订日期,
COUNTfinal\u registration.booking\u日期为countT
从日历
左加入最终注册日期格式最终注册日期,%Y-%m-%d'=
日期\u FORMATcalendar.datefield,'%Y-%m-%d'
和最终注册。票证状态为“活动”、“取消”
其中DATE\u FORMATcalendar.datefield,'%Y'=年
按日期分组\u FORMATcalendar.datefield,'%Y-%m'
我建议使用相关子查询和索引:
SELECT yyyymm,
(SELECT COUNT(*)
FROM final_registration fr
WHERE fr.status IN ('active', 'cancelled') AND
fr.booking_date >= c.month_start AND
fr.booking_date < c.month_start + interval 1 month
) as countT
FROM (SELECT DATE_FORMAT(c.datefield, '%Y-%m') as yyyymm,
MIN(c.datefield) as month_start
FROM calendar c
WHERE YEAR(c.datefield) = ? -- PASS IN AS PARAMETER!!!
GROUP BY yyyymm
) c
ORDER BY c.yyyymm;
您想要的索引位于final_registrationdatefield的status上
这比您的查询有几个好处:
它可以使用索引进行日期比较,因为在第二个日期中的日期列上没有使用函数。
它避免了昂贵的外部分组。
还要注意使用参数,而不是使用文本值来填充查询 我建议使用相关子查询和索引:
SELECT yyyymm,
(SELECT COUNT(*)
FROM final_registration fr
WHERE fr.status IN ('active', 'cancelled') AND
fr.booking_date >= c.month_start AND
fr.booking_date < c.month_start + interval 1 month
) as countT
FROM (SELECT DATE_FORMAT(c.datefield, '%Y-%m') as yyyymm,
MIN(c.datefield) as month_start
FROM calendar c
WHERE YEAR(c.datefield) = ? -- PASS IN AS PARAMETER!!!
GROUP BY yyyymm
) c
ORDER BY c.yyyymm;
您想要的索引位于final_registrationdatefield的status上
这比您的查询有几个好处:
它可以使用索引进行日期比较,因为在第二个日期中的日期列上没有使用函数。
它避免了昂贵的外部分组。
还要注意使用参数,而不是使用文本值来填充查询 我想这是索引中的问题。 只有当您具有基于日期的函数索引\u format final\u registration.booking\u DATE、%Y-%m-%d时,您的查询才能正常工作。我不知道你有哪个版本的MySQL,它是否提供了这样一个选项 但无论如何,我打赌你有一个关于最终注册日期的简单索引。在这种情况下,join子句是不正确的,因为不会使用索引。因此,您不应将日期转换为字符以使索引正常工作:
LEFT JOIN final_registration ON final_registration.booking_date = calendar.datefield
顺便说一下,WHERE子句也有这个问题。始终更喜欢转换参数而不是表字段,例如:
WHERE calendar.datefield BETWEEN str_to_date(concat("01-01-", year(now())), "%d-%m-%Y") AND str_to_date(concat("31-12-", year(now())), "%d-%m-%Y")
我想这是索引中的问题。 只有当您具有基于日期的函数索引\u format final\u registration.booking\u DATE、%Y-%m-%d时,您的查询才能正常工作。我不知道你有哪个版本的MySQL,它是否提供了这样一个选项 但无论如何,我打赌你有一个关于最终注册日期的简单索引。在这种情况下,join子句是不正确的,因为不会使用索引。因此,您不应将日期转换为字符以使索引正常工作:
LEFT JOIN final_registration ON final_registration.booking_date = calendar.datefield
顺便说一下,WHERE子句也有这个问题。始终更喜欢转换参数而不是表字段,例如:
WHERE calendar.datefield BETWEEN str_to_date(concat("01-01-", year(now())), "%d-%m-%Y") AND str_to_date(concat("31-12-", year(now())), "%d-%m-%Y")
我建议在加入之前执行聚合,实际计算出所需范围的开始和结束,并使用中间值;如果在where条件中使用诸如DATE_格式甚至YEAR之类的函数,那么如果在调用它们的日期字段上没有索引,则会破坏性能。。。。另外,确保你有一个关于预订日期的索引 如果您有一个支持CTE的MySQL版本,您甚至可以不使用日历表。可以使用生成数字1-12作为预订月的CTE,并在该字段上加入
WITH calendar_months AS (
SELECT 1 AS booking_month
UNION SELECT booking_month + 1 FROM calendar_months WHERE booking_month < 12
)
SELECT [year] AS booking_year, cm.booking_month, bookingSummary.countT
FROM calendar_months AS cm
LEFT JOIN (
SELECT MONTH(booking_date) AS booking_month
, COUNT(*) AS countT
FROM final_registration AS fr
WHERE fr.ticket_status IN ('active', 'cancelled')
AND fr.booking_date BETWEEN [firstdayofyear] AND [lastdayofyear]
GROUP BY booking_month
) AS bookingSummary
USING (booking_month)
;
注意:将我的[字段]符号视为参数的占位符;我建议使用CTE版本而不是我介绍的第一个版本的原因之一是,它需要维护的参数少了一个。我建议在加入之前执行聚合,实际计算出所需范围的开始和结束,并使用中间值;如果在where条件中使用诸如DATE_格式甚至YEAR之类的函数,那么如果在调用它们的日期字段上没有索引,则会破坏性能。。。。另外,确保你有一个关于预订日期的索引 如果您有一个支持CTE的MySQL版本,您甚至可以不使用日历表。可以使用生成数字1-12作为预订月的CTE,并在该字段上加入
WITH calendar_months AS (
SELECT 1 AS booking_month
UNION SELECT booking_month + 1 FROM calendar_months WHERE booking_month < 12
)
SELECT [year] AS booking_year, cm.booking_month, bookingSummary.countT
FROM calendar_months AS cm
LEFT JOIN (
SELECT MONTH(booking_date) AS booking_month
, COUNT(*) AS countT
FROM final_registration AS fr
WHERE fr.ticket_status IN ('active', 'cancelled')
AND fr.booking_date BETWEEN [firstdayofyear] AND [lastdayofyear]
GROUP BY booking_month
) AS bookingSummary
USING (booking_month)
;
注意:将我的[字段]符号视为参数的占位符;我建议使用CTE版本而不是我介绍的第一个版本的原因之一是,它需要维护的参数少了一个。…例如,这个。。。其中,在这种情况下,c.datefield介于“2019-01-01”和“2019-12-31”之间而不是YEARSubquery比分组更昂贵,因为它将对主查询的每一行执行一次,而不是联接。@BitLord。绝对不是。您不知道查询是如何处理索引的。这不就是计数吗
每天,并仅以年和月标记每天?@uuerdo。非常感谢。我只是错过了订票日期并不是一个真正的日期,只是一年和一个月…比如这个。。。其中,在这种情况下,c.datefield介于“2019-01-01”和“2019-12-31”之间而不是YEARSubquery比分组更昂贵,因为它将对主查询的每一行执行一次,而不是联接。@BitLord。绝对不是。你不知道查询是如何处理索引的。这难道不只是获取每天的计数,并仅以年和月来标记每天吗?@uuerdo。非常感谢。我只是错过了预订日期不是真正的日期,只是年和月。日期字段和预订日期字段是日期/日期时间/时间戳数据类型还是varchar/char数据类型?日期字段和预订日期字段是日期/日期时间/时间戳数据类型还是varchar/char数据类型?