5

作为介绍...
我遇到了这个问题:2 个相邻字段之间的差异 - 日期 - PHP MYSQL并试图实现目标,即使用纯 MySQL 遍历日期并获得差异。
那里的另一个问题(在 SQL 中从另一行中减去一行数据)帮助我了解如何使用 MySQL 制作类似的东西。它没有解决问题,因为解决方案仍然依赖于固定值或假定的数据顺序,但它确实帮助我理解了该方法。
还有另一个问题(如何在 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 )。

这就像一个魅力......直到数据库中只有几个条目。当更多时它变得更糟:

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秒。


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

4

1 回答 1

5

即使对于中等大小的桌子,您的方法也很慢也就不足为奇了。

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

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

这应该比您尝试做的快几个数量级。

于 2012-04-25T18:05:10.857 回答