【问题标题】:MySQL date diff iteration query - streamline query or optimize data structureMySQL date diff 迭代查询 - 简化查询或优化数据结构
【发布时间】:2012-05-06 11:02:56
【问题描述】:

作为介绍...
我遇到了这个问题:Difference between 2 adjacent fields - Date - PHP MYSQL 并试图实现目标,即使用纯 MySQL 遍历日期并获得差异。
那里的另一个问题 (Subtracting one row of data from another in SQL) 帮助我了解如何使用 MySQL 制作类似的东西。它没有解决问题,因为解决方案仍然依赖于固定值或假定的数据顺序,但它确实帮助我理解了方法。
还有另一个问题 (How to get next/previous record in MySQL?) 的答案描述了如何从下一行/上一行获取值。它仍然依赖于一些固定值,但我学会了如何使用该技术。

假设我有这张桌子foo

CREATE TABLE `foo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dateof` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  id | dateof
-----+------------
   1 | 2012-01-01
   2 | 2012-01-02
  11 | 2012-01-04
  12 | 2012-01-01
  13 | 2012-01-02
  14 | 2012-01-09
 111 | 2012-01-01
 112 | 2012-01-01
 113 | 2012-01-01

有两个假设:

  1. 主键 (id) 按升序排列,允许“空洞”。
  2. dateof 列中的每个日期都是有效的,意思是:没有 NULLs 并且没有默认值 (0000-00-00)。 我想遍历每一行并计算上一个条目经过的天数,以接收:
  id | date       | days_diff
-----+------------+-----------
   1 | 2012-01-01 |     0
   2 | 2012-01-02 |     1
  11 | 2012-01-04 |     2
  12 | 2012-01-01 |    -3
  13 | 2012-01-02 |     1
  14 | 2012-01-09 |     7
 111 | 2012-01-01 |    -8
 112 | 2012-01-01 |     0
 113 | 2012-01-01 |    30

根据我所学到的所有知识,我来到了这个解决方案(比如说解决方案 1,因为还有另一个):

SELECT
    f.id,
    DATE_FORMAT(f.dateof, '%b %e, %Y') AS date,
    (SELECT DATEDIFF(f.dateof, f2.dateof)
        FROM foo f2
        WHERE f2.id = (
            SELECT MAX(f3.id) FROM foo f3 WHERE f3.id < f.id
        )
    ) AS days_diff
FROM foo f;

(此处的小提琴示例:http://sqlfiddle.com/#!2/099fc/3)。

这就像一个魅力......直到 db 中只有几个条目。越多情况就越糟:

EXPLAIN:
id select_type        table type   possible_keys key     key_len ref    rows  Extra
1  PRIMARY            f     ALL    NULL          NULL    NULL    NULL   17221   
2  DEPENDENT SUBQUERY f2    eq_ref PRIMARY       PRIMARY 4       func   1     Using where
3  DEPENDENT SUBQUERY f3    index  PRIMARY       PRIMARY 4       NULL   17221 Using where; Using index

18031 行:持续时间:8.672 秒。获取:228.515 秒。

我想在dateof 列上添加索引:

CREATE TABLE `foo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dateof` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `dateof` (`dateof`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

...并获得了微小的改进:

EXPLAIN:
id select_type        table type   possible_keys key     key_len ref  rows  Extra
1  PRIMARY            f     index  NULL          dateof  4       NULL 18369 Using index
2  DEPENDENT SUBQUERY f2    eq_ref PRIMARY       PRIMARY 4       func 1     Using where
3  DEPENDENT SUBQUERY f3    index  PRIMARY       dateof  4       NULL 18369 Using where; Using index

18031 行:持续时间:8.406 秒。获取:219.281 秒。

我记得在某处读过有关 MyISAM 在某些情况下优于 InnoDB 的优势。所以我把它改成了 MyISAM:

ALTER TABLE `foo` ENGINE = MyISAM;

18031 行:持续时间:5.671 秒。获取:151.610 秒。

当然更好,但仍然很慢。

我尝试了另一种算法(解决方案 2):

SELECT
  f.id,
  DATE_FORMAT(f.dateof, '%b %e, %Y') AS date,
  (SELECT DATEDIFF(f.dateof, f2.dateof)
    FROM foo f2
    WHERE f2.id < f.id
    ORDER BY f2.id DESC
    LIMIT 1
  ) AS days_diff
FROM foo f;

...但它更慢:

18031 行:持续时间:15.609 秒。获取:184.656 秒。


是否有任何其他方法可以优化此查询或数据结构以更快地执行此任务?

【问题讨论】:

  • 我认为不同的数据结构可能更适合您的需求。你能多说一下你是如何使用这些数据的吗?
  • @eggyal 没什么特别的。我只是想学习一些可能有用的东西:)

标签: mysql optimization query-optimization iteration datediff


【解决方案1】:

即使对于中等大小的桌子,您的方法也很慢,这并不奇怪。

理论上应该可以使用LAG 分析函数在 O(n) 时间内计算结果,遗憾的是 MySQL 不支持该函数。但是,您可以使用变量在 MySQL 中模拟 LAG

SELECT
    id,
    DATE_FORMAT(f.dateof, '%b %e, %Y') AS date,
    DATEDIFF(dateof, @prev) AS days_diff,
    @prev := dateof
FROM FOO, (SELECT @prev := NULL) AS vars
ORDER BY id

这应该比您尝试执行的操作快几个数量级。

【讨论】:

  • 他可能也从 CROSS APPLY 类型语句中受益,但遗憾的是,这在 MySQL 中也不可用。提醒我为什么 MySQL 如此受欢迎?
  • 不错的答案,顺便说一句。这是显示它工作的小提琴链接 - sqlfiddle.com/#!2/099fc/5
  • 这个技巧很棒。查询是使用我的示例数据集立即执行的,大约 1 秒有 200 万行。感谢您的解决方案,尤其是技术 - 它肯定很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多