5

導入として...
私はこの質問に出くわしました: 2 つの隣接するフィールドの違い - 日付 - PHP MYSQLで、目標を達成しようとしていました。
もう 1 つの質問 ( Subtracting one row of data from another in SQL ) は、MySQL で同様のものを作成する方法を理解するのに役立ちました。解決策はまだ固定値またはデータの想定順序に依存しているため、問題は解決しませんでしたが、方法論を理解するのに役立ちました。
もう 1 つ質問があります ( 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

次の 2 つの仮定があります。

  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にエントリが2つだけになるまで。次の場合はさらに悪化します。

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

場合によっては、InnoDB に対する MyISAM の利点についてどこかで読んだことを思い出しました。だから私は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

適度なサイズのテーブルであっても、アプローチが非常に遅いのは当然のことです。

理論的には、分析関数を使用して O(n) 時間で結果を計算できるはずLAGですが、残念ながら 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 に答える