【问题标题】:MySQL: Optimize left join on formatted dateMySQL:优化格式化日期的左连接
【发布时间】:2021-05-14 14:12:37
【问题描述】:

我正在尝试优化此查询的速度:

      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 在这里给出的解决方案: How to optimize mysql group by with DATE_FORMAT 我通过这个 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 - 函数或表达式 'date_format()' 不能在 datestr 的 GENERATED ALWAYS AS 子句中使用

我做错了什么?我的数据库是 Maria DB 10.4.8

最好的问候 - 乌尔里希

【问题讨论】:

  • 你应该修复右侧并将t_date提升为datetime...
  • 注意函数不能使用索引:-(

标签: mysql datetime mariadb query-optimization virtual-column


【解决方案1】:

date_format() 也不能用于持久生成的列。而且在索引中它不能只是虚拟的,它必须被持久化。

我在手册中找不到明确的声明,但我相信这是因为date_format() 的输出可能取决于语言环境,因此不是严格确定的。

您可以使用诸如concat()year()month()day()lpad() 等确定性函数来构建字符串,而不是date_format()

...
datestr varchar(10) AS (concat(year(visit_date),
                               '-',
                               lpad(month(visit_date), 2, '0'),
                               '-',
                               lpad(day(visit_date), 2, '0')))
...

但正如我在评论中已经提到的那样,您正在修复错误的结局。日期/时间不应存储为字符串。因此,您应该将temp_dates.t_date 提升为date 并使用date() 在生成的索引列中提取visit_datedate 部分

...
visit_date_date date AS (date(visit_date))
...

您可能还想尝试索引temp_dates.t_date

【讨论】:

  • 非常感谢,切换到 DATETIME 并使用 DATE 大大提高了速度,提高了 10 倍以上。但是出现了其他问题。我稍后会更新我的问题。
  • @Sempervivum:很高兴听到这个消息。但如果有其他问题,请考虑问一个新问题,以便让这个问题保持专注。
  • 好的,我会这样做,并在此处将您的答案标记为解决方案。
  • 注意,date_format 的 3 args 形式是确定性的。
【解决方案2】:

这对你有用吗?

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

如果是这样,您的问题有一个可行的解决方案:

  1. 使用visit_date 对象上的确定性DATE() 函数添加DATE 列。像这样。

    ALTER TABLE visits ADD COLUMN dateval DATE AS (DATE(visit_date)) VIRTUAL; 
    CREATE INDEX idx_visit_date on visits(dateval);
    
  2. 然后在另一个表中创建一个虚拟列(将格式精美的日期塞入您的 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

【讨论】:

    【解决方案3】:

    DATE_FORMAT(expr, format) 不能用于虚拟列,因为它取决于连接的区域设置(MariaDB 问题MDEV-11553)。

    date_format 创建了一个 3 参数表单,用于添加语言环境。

    DATE_FORMAT(visit_date, '%Y-%m-%d', 'en_US') 可以在 MariaDB-10.3+ 稳定版本的虚拟列表达式中使用。

    绝对推荐使用DATE 或更改查询以不使用列表达式周围的函数。

    【讨论】:

      【解决方案4】:

      函数不是“可分析的”。

      考虑:

      ON  v.visit_date >= t.t_date
      AND v.visit_date  < t.t_date + INTERVAL 1 DAY
      

      【讨论】:

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