3

長々とすみません、完全な説明をしたかったのです!別のテーブルからの ID に関する情報を表示するレポートを表示する必要があります。ID に対してテーブルに同じ国エントリを複数回含める方法に注意してください (情報は定期的に複数回クエリされますが、その間に移動していない可能性があるため)、異なる国エントリを持つこともできます (国を変更します)。

データの簡単な説明: 私は以下の表を持っています:

CREATE TABLE IF NOT EXISTS `country` (
`id` mediumint(8) unsigned NOT NULL,
`timestamp` datetime NOT NULL,
`country` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`,`timestamp`),
KEY `country` (`country`),
KEY `timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

エントリは次のようになります。

41352   2012-03-26 15:46:01     Jamaica
41352   2012-03-05 22:49:41     Jamaican Applicant
41352   2012-02-26 15:46:01     Jamaica
41352   2012-02-16 12:11:19     Jamaica
41352   2012-02-05 23:00:30     Jamaican Applicant

このテーブルには現在、合計約 214,590 行ありますが、テスト データが実際のデータに置き換えられると、数百万行になります。

私が欲しいのは、y 時間以降に x 国を離れたすべての人に関する情報です。上記のデータで実行されたと仮定して、出力したい方法は次のとおりです。

id  name    last    country     TIMESTAMP   o_timestamp
41352 Sweet Mercy   Jamaica     2012-03-26 15:46:01     2012-03-05 22:49:41
41352 Sweet Mercy   Jamaica     2012-02-16 12:11:19     2012-02-05 23:00:30

o_timestamp が特定の日付 (たとえば 100) よりも新しい場合、country は移動先であり、元の国 (表示されていません) はクエリに渡すものです (上記のデータに基づくジャマイカの申請者)。

要件を満たすために次のクエリを作成し、特定の ID を使用してテストしました。

SELECT a.id,
       c.name,
       c.last,
       a.country,
       a.timestamp,
       b.timestamp AS o_timestamp
FROM   country a
       INNER JOIN user_info c
         ON ( a.id = c.id )
       LEFT JOIN country AS b
         ON ( a.id = b.id
              AND a.timestamp != b.timestamp
              AND a.country != b.country )
WHERE  b.timestamp = (SELECT c.timestamp
                      FROM   country c
                      WHERE  a.id = c.id
                             AND a.timestamp > c.timestamp
                      ORDER  BY c.timestamp DESC
                      LIMIT  1) 
       AND a.id = 965

私はこれを完了しました (合計 7、クエリは 0.0050 秒かかりました)

そして拡張説明により、次のことが明らかになりました。

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   PRIMARY     c   const   PRIMARY     PRIMARY     3   const   1   100.00  
1   PRIMARY     a   ref     PRIMARY     PRIMARY     3   const   16  100.00  
1   PRIMARY     b   eq_ref  PRIMARY,timestamp   PRIMARY     11  const,func  1   100.00  Using where
2   DEPENDENT SUBQUERY  c   index   PRIMARY,timestamp   timestamp   8   NULL    1   700.00  Using where; Using index

だから私は私がかなり上手だと思って、これに飛び込んだ:

SELECT a.id,
       c.name,
       c.last,
       a.country,
       a.timestamp,
       b.timestamp AS o_timestamp
FROM   country a
       INNER JOIN user_info c
         ON ( a.id = c.id )
       LEFT JOIN country AS b
         ON ( a.id = b.id
              AND a.timestamp != b.timestamp
              AND a.country != b.country )
WHERE  b.timestamp = (SELECT c.timestamp
                      FROM   country c
                      WHERE  a.id = c.id
                             AND a.timestamp > c.timestamp
                      ORDER  BY c.timestamp DESC
                      LIMIT  1) 
       AND b.country = "whatever" AND timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)

このクエリは、200 件のレコードがあり、完了しなかった国で完了するのに驚くべき 6 分 54 秒かかりました (午後と夜に外出し、

データベースに9000件のレコードがある国では、家に帰るので合計約8時間です。実際のデータでは、国は 10000 倍簡単にそこにある可能性があります。100kは不合理ではありません。

だから私は拡張を説明し、これを取得します:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   PRIMARY     <derived2>  ALL     NULL    NULL    NULL    NULL    3003    100.00  
1   PRIMARY     c   eq_ref  PRIMARY     PRIMARY     3   b.id    1   100.00  
1   PRIMARY     a   ref     PRIMARY     PRIMARY     3   b.id    7   100.00  Using where
3   DEPENDENT SUBQUERY  c   index   PRIMARY,timestamp   timestamp   8   NULL    1   700.00  Using where; Using index
2   DERIVED     country     range   country,timestamp   country     195     NULL    474     100.00  Using where; Using index

そのため、大きく見えますが、不当ではありません。

[スペースの構成変数を削除しました。必要に応じて、パフォーマンス情報もお知らせください。おそらくクエリの問題です。]

何か見逃した場合はお知らせください。

4

3 に答える 3

2

問題は基準を追加することではありません。ダメージを与えているものを落としています。元のクエリでは、次のものがありました。

AND a.id = 965

aこれは、クエリの実行で( country) テーブル全体を読み取る必要がないことを意味します。パフォーマンスが低下した 2 番目のクエリでは、その基準を次のように変更します。

AND b.country = "whatever"
AND timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)

に対して本当に制限的な基準がなくなったaため、物事の動作ははるかに遅くなります。

bが への別の参照であることがわかると、事態はさらに複雑になりcountryます。それにもかかわらず、条件aからb(b外部結合の外側にある場所) への変更は簡単ではありません。クエリ条件を処理するのにより多くの時間がかかります。


特定のIDを探しているわけではないので、運が悪いということですか?

与えられたクエリ構造では、答えは「はい」のように見えますが、与えられたクエリ構造は最適ではないかもしれません。

あなたの「1つのIDで作業するのに十分速い」クエリは次のとおりです。

SELECT a.id,
       c.name,
       c.last,
       a.country,
       a.timestamp,
       b.timestamp AS o_timestamp
FROM   country a
       INNER JOIN user_info c
         ON ( a.id = c.id )
       LEFT JOIN country AS b
         ON ( a.id = b.id
              AND a.timestamp != b.timestamp
              AND a.country != b.country )
WHERE  b.timestamp = (SELECT c.timestamp
                      FROM   country c
                      WHERE  a.id = c.id
                             AND a.timestamp > c.timestamp
                      ORDER  BY c.timestamp DESC
                      LIMIT  1) 
       AND a.id = 965

このクエリとそれが何をしようとしているのかを完全には理解していません。外部結合は内部結合よりもコストがかかることに注意する必要があり、外部結合テーブルの条件は次のようになります。

b.timestamp = (...correlated sub-query...)

非常に高価です。1 つの問題は、bを含む列に NULL が存在する可能性があるtimestampことですが、値が非 null でない限り条件が満たされないため、サブクエリが無駄になるため、「なぜ OUTER 結合なのか」という疑問が生じます。 ?

a改訂された条件を追加すると、タイムスタンプがまたはからのものである可能性があるため、「あいまいな列名」エラーが表示されるはずcです。また、条件は値が null でないb.country = "whatever"場合にのみ意味をなす別の条件であるため、ここでも OUTER 結合は疑わしいものです。b

私が理解しているように、countryテーブルには誰がどの国にいつ入国したかに関する記録が含まれています。また、FWIW、user_infoテーブルとの結合が無視できるパフォーマンスの問題であることはかなり確信しています。問題はすべて、テーブルへの 3 つの参照にかかっていcountryます。


いくつかの説明から判断すると、おそらくこのようなクエリを段階的に構築できます。

  1. レコードが時系列で隣接している国レコードの各ペアを検索します。ペアidの古い方が特定の国 (「ジャマイカ申請者」) 用で、新しい方が別の国用です。

    これの簡単な部分は次のとおりです。

    SELECT a.id, a.country, a.timestamp, b.country, b.timestamp
      FROM country AS a
      JOIN country AS b
        ON a.id = b.id
       AND b.timestamp > a.timestamp
       AND a.country = 'Jamaica Applicant'
       AND b.country != a.country
    

    これによりほとんどの作業が行われますが、エントリの隣接性は保証されません。 これを行うには、2 つのタイムスタンプの間に (ただし含まれていない)country同じレコードがテーブルにないと主張する必要があります。これは追加の NOT EXISTS 条件です。ida.timestampb.timestamp

    SELECT a.id,
           a.country   AS o_country,
           a.timestamp AS o_timestamp,
           b.country   AS n_country,
           b.timestamp AS n_timestamp
      FROM country AS a
      JOIN country AS b
        ON a.id = b.id
       AND b.timestamp > a.timestamp
       AND a.country = 'Jamaica Applicant'
       AND b.country != a.country
     WHERE NOT EXISTS
           (SELECT *
              FROM country AS c
             WHERE c.timestamp > a.timestamp
               AND c.timestamp < b.timestamp
               AND c.id = a.id
           )
    

    BETWEEN AND 表記は適していないことに注意してください。範囲内の端点が含まれていますが、端点を除外する必要があることを明示的に示しています。

  2. 上記の国のエントリのリストを考えると、次の行だけを選択する必要があります...うーん、基準は何ですか? 選択できると思いますが、結果はuser_info簡単にテーブルに結合できます。

    SELECT e.id, u.name, u.last, e.o_country, e.o_timestamp, e.n_country, e_n_timestamp
      FROM (SELECT a.id,
                   a.country   AS o_country,
                   a.timestamp AS o_timestamp,
                   b.country   AS n_country,
                   b.timestamp AS n_timestamp
              FROM country AS a
              JOIN country AS b
                ON a.id = b.id
               AND b.timestamp > a.timestamp
               AND a.country = 'Jamaica Applicant'
               AND b.country != a.country
             WHERE NOT EXISTS
                   (SELECT *
                      FROM country AS c
                     WHERE c.timestamp > a.timestamp
                       AND c.timestamp < b.timestamp
                       AND c.id = a.id
                   )
           ) AS e
      JOIN user_info AS u ON e.id = u.id
     WHERE e.o_timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY);
    

パフォーマンスが向上することを保証するつもりはありません (または、構文的に正しいことでさえありません。SQL DBMS を超えたことはありません)。しかし、隣接する日付を取得するための複雑なクエリ構造は、元のコードよりもすっきりしていて、おそらくパフォーマンスが優れていると思います。特に、外部結合、(明示的な) 順序付け、または制限句を使用しないことに注意してください。それは役立つはずです。

于 2012-04-09T02:20:07.673 に答える
0

私はこれを完成した解決策として提案しているわけではありませんが、それは私が戻るべき出発点です. これがテストデータセットに対してどのように機能するか教えてください -

SELECT ui.*, c1.*, MAX(c2.timestamp)
FROM country c1
INNER JOIN user_info ui
    ON c1.id = ui.id
INNER JOIN country c2
    ON c1.id = c2.id
    AND c1.timestamp > c2.timestamp
    AND c1.country <> c2.country
WHERE c2.timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
AND c2.country = 'somewhere'
GROUP BY c1.id

次のステップは、LEFT JOIN を追加して、間に他のレコードがないことを確認することです。

SELECT ui.*, c1.*, c2.timestamp
FROM country c1
INNER JOIN user_info ui
    ON c1.id = ui.id
INNER JOIN country c2
    ON c1.id = c2.id
    AND c1.timestamp > c2.timestamp
    AND c1.country <> c2.country

LEFT JOIN country c3
    ON c1.id = c3.id
    AND c1.timetsamp > c3.timestamp
    AND c2.timestamp < c2.timetsamp

WHERE c2.timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
AND c2.country = 'somewhere'
AND c3.id IS NULL
于 2012-04-09T02:57:21.537 に答える
0

この参照を確認する必要があります: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_now

およびhttp://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-add

つまり、NOW() 関数は (コンテキストに応じて) 文字列を返すことができ、date_add は (パラメーターに応じて) 文字列を返すことができます。私の推測では、文字列を取得してから、比較時に日付にキャストするだけです (これはすべてのレコードで発生します)。AND タイムスタンプ > cast(DATE_SUB(NOW(), INTERVAL 7 DAY) as datetime) を試してみてください。パフォーマンスが向上する可能性があります。

于 2012-04-09T02:33:26.320 に答える