MySQL日期差异迭代查询 - 简化查询或优化数据结构

时间:2012-04-25 17:57:58

标签: mysql optimization query-optimization iteration datediff

通过介绍......
我遇到了这个问题: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列中的每个日期都有效,其含义为:无NULL s且无默认值(0000-00-00)。 我想迭代每一行并计算上一次输入的天数,以便接收:
  3.   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 秒。


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

1 个答案:

答案 0 :(得分:5)

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

理论上应该可以使用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

这应该比你想要做的快几个数量级。