MySQL: 优化格式化日期的左连接
MySQL: Optimize left join on formatted date
我正在尝试优化此查询的速度:
SELECT t.t_date td, v.visit_date vd
FROM temp_dates t
LEFT JOIN visits v ON DATE_FORMAT(v.visit_date, '%Y-%m-%d') = t.t_date
ORDER BY t.t_date
v.visit_date 是 DATETIME 类型,t.t_date 是格式为 '%Y-%m-%d' 的字符串。
简单地在 v.visitdate 上创建索引并没有提高速度。因此我打算尝试@oysteing 在这里给出的解决方案:
我通过这个 SQL 成功创建了一个虚拟列
ALTER TABLE visits ADD COLUMN datestr varchar(10) AS (DATE_FORMAT(visit_date, '%Y-%m-%d')) VIRTUAL;
但是,当我尝试通过以下方式在此列上创建索引时
CREATE INDEX idx_visit_date on visits(datestr)
我收到这个错误:
#1901 - Function or expression 'date_format()' cannot be used in the GENERATED ALWAYS AS clause of datestr
我做错了什么?我的数据库是 Maria DB 10.4.8
此致 - 乌尔里希
date_format()
也不能用于持久生成的列。在索引中它不能只是虚拟的,它必须被持久化。
我在手册中找不到明确的声明,但我相信这是因为 date_format()
的输出可能取决于语言环境,因此不是严格确定的。
您可以使用 concat()
、year()
、month()
、day()
和 [=19= 等确定性函数来构建字符串,而不是 date_format()
].
...
datestr varchar(10) AS (concat(year(visit_date),
'-',
lpad(month(visit_date), 2, '0'),
'-',
lpad(day(visit_date), 2, '0')))
...
但是正如我在评论中提到的那样,您正在修复错误的一端。 Dates/times 永远不应存储为字符串。因此,您应该将 temp_dates.t_date
提升为 date
并使用 date()
在生成的索引列
中提取 visit_date
的 date
部分
...
visit_date_date date AS (date(visit_date))
...
您可能还想尝试索引 temp_dates.t_date
。
这对你有用吗?
SELECT t.t_date td, v.visit_date vd
FROM temp_dates t
LEFT JOIN visits v ON DATE(v.visit_date) = DATE(t.t_date)
ORDER BY t.t_date
如果是这样,那么您的问题有一个可行的解决方案:
在您的 visit_date
对象上使用确定性 DATE()
函数添加 DATE
列。像这样。
ALTER TABLE visits ADD COLUMN dateval DATE AS (DATE(visit_date)) VIRTUAL;
CREATE INDEX idx_visit_date on visits(dateval);
然后在另一个 table 中创建一个虚拟列(日期格式正确的那个被塞进你的 VARCHAR() 列。
ALTER TABLE temp_dates ADD COLUMN dateval DATE AS (DATE(t_date)) VIRTUAL;
CREATE INDEX idx_temp_dates_date on temp_dates (dateval);
之所以有效,是因为 DATE()
是确定性的,与 DATE_FORMAT()
不同。
那么你的查询应该是。
SELECT t.t_date td, v.visit_date vd
FROM temp_dates t
LEFT JOIN visits v ON v.dateval = t.dateval
ORDER BY t.t_date
此解决方案为您提供(虚拟)DATE
列的索引。这很好,因为在这些列上进行索引匹配是有效的。
但是,您最好的解决方案是将 temp_date.t_date
的数据类型从 VARCHAR()
更改为 DATE
。
DATE_FORMAT(expr, format)
不能在虚拟列中使用,因为它取决于连接的区域设置(MariaDB 问题 MDEV-11553)。
为 date_format 创建了一个 3 参数表单,它添加了语言环境。
DATE_FORMAT(visit_date, '%Y-%m-%d', 'en_US')
可以在 MariaDB-10.3+ 稳定版的虚拟列表达式中使用。
绝对推荐使用 DATE
或更改您的查询以不在列表达式周围使用函数。
函数不是“sargeable”的。
考虑:
ON v.visit_date >= t.t_date
AND v.visit_date < t.t_date + INTERVAL 1 DAY
我正在尝试优化此查询的速度:
SELECT t.t_date td, v.visit_date vd
FROM temp_dates t
LEFT JOIN visits v ON DATE_FORMAT(v.visit_date, '%Y-%m-%d') = t.t_date
ORDER BY t.t_date
v.visit_date 是 DATETIME 类型,t.t_date 是格式为 '%Y-%m-%d' 的字符串。
简单地在 v.visitdate 上创建索引并没有提高速度。因此我打算尝试@oysteing 在这里给出的解决方案:
ALTER TABLE visits ADD COLUMN datestr varchar(10) AS (DATE_FORMAT(visit_date, '%Y-%m-%d')) VIRTUAL;
但是,当我尝试通过以下方式在此列上创建索引时
CREATE INDEX idx_visit_date on visits(datestr)
我收到这个错误:
#1901 - Function or expression 'date_format()' cannot be used in the GENERATED ALWAYS AS clause of
datestr
我做错了什么?我的数据库是 Maria DB 10.4.8
此致 - 乌尔里希
date_format()
也不能用于持久生成的列。在索引中它不能只是虚拟的,它必须被持久化。
我在手册中找不到明确的声明,但我相信这是因为 date_format()
的输出可能取决于语言环境,因此不是严格确定的。
您可以使用 concat()
、year()
、month()
、day()
和 [=19= 等确定性函数来构建字符串,而不是 date_format()
].
...
datestr varchar(10) AS (concat(year(visit_date),
'-',
lpad(month(visit_date), 2, '0'),
'-',
lpad(day(visit_date), 2, '0')))
...
但是正如我在评论中提到的那样,您正在修复错误的一端。 Dates/times 永远不应存储为字符串。因此,您应该将 temp_dates.t_date
提升为 date
并使用 date()
在生成的索引列
visit_date
的 date
部分
...
visit_date_date date AS (date(visit_date))
...
您可能还想尝试索引 temp_dates.t_date
。
这对你有用吗?
SELECT t.t_date td, v.visit_date vd
FROM temp_dates t
LEFT JOIN visits v ON DATE(v.visit_date) = DATE(t.t_date)
ORDER BY t.t_date
如果是这样,那么您的问题有一个可行的解决方案:
在您的
visit_date
对象上使用确定性DATE()
函数添加DATE
列。像这样。ALTER TABLE visits ADD COLUMN dateval DATE AS (DATE(visit_date)) VIRTUAL; CREATE INDEX idx_visit_date on visits(dateval);
然后在另一个 table 中创建一个虚拟列(日期格式正确的那个被塞进你的 VARCHAR() 列。
ALTER TABLE temp_dates ADD COLUMN dateval DATE AS (DATE(t_date)) VIRTUAL; CREATE INDEX idx_temp_dates_date on temp_dates (dateval);
之所以有效,是因为 DATE()
是确定性的,与 DATE_FORMAT()
不同。
那么你的查询应该是。
SELECT t.t_date td, v.visit_date vd
FROM temp_dates t
LEFT JOIN visits v ON v.dateval = t.dateval
ORDER BY t.t_date
此解决方案为您提供(虚拟)DATE
列的索引。这很好,因为在这些列上进行索引匹配是有效的。
但是,您最好的解决方案是将 temp_date.t_date
的数据类型从 VARCHAR()
更改为 DATE
。
DATE_FORMAT(expr, format)
不能在虚拟列中使用,因为它取决于连接的区域设置(MariaDB 问题 MDEV-11553)。
为 date_format 创建了一个 3 参数表单,它添加了语言环境。
DATE_FORMAT(visit_date, '%Y-%m-%d', 'en_US')
可以在 MariaDB-10.3+ 稳定版的虚拟列表达式中使用。
绝对推荐使用 DATE
或更改您的查询以不在列表达式周围使用函数。
函数不是“sargeable”的。
考虑:
ON v.visit_date >= t.t_date
AND v.visit_date < t.t_date + INTERVAL 1 DAY