1回の選択で現在の値と次に大きい値を取得するにはどうすればよいですか?


18

InnoDBテーブル 'idtimes'(MySQL 5.0.22-log)に列があります

`id` int(11) NOT NULL,
`time` int(20) NOT NULL, [...]

複合一意キー

UNIQUE KEY `id_time` (`id`,`time`)

そのため、IDごとに複数のタイムスタンプがあり、タイムスタンプごとに複数のIDがあります。

私はすべてのエントリに加えて、存在する場合は各エントリの次の大きな時間を取得するクエリを設定しようとしているので、たとえば次のように返されます:

+-----+------------+------------+
| id  | time       | nexttime   |
+-----+------------+------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 |       NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 |       NULL |
+-----+------------+------------+

今私はこれまでのところです:

SELECT l.id, l.time, r.time FROM 
    idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id
    WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;

もちろん、これは最初の行だけでなく、r.time> l.timeのすべての行を返します...

私は次のようなサブセレクトが必要だと思います

SELECT outer.id, outer.time, 
    (SELECT time FROM idtimes WHERE id = outer.id AND time > outer.time 
        ORDER BY time ASC LIMIT 1)
    FROM idtimes AS outer ORDER BY outer.id ASC, outer.time ASC;

しかし、現在の時刻を参照する方法がわかりません(上記は有効なSQLではないことを知っています)。

単一のクエリでこれを行うにはどうすればよいですか(そして、テーブルを一度に1行ずつステップし、最後の値を覚えていることに依存する@変数を使用したくない)?

回答:


20

JOINを実行することは、必要なことの1つです。

SELECT l.id, l.time, r.time FROM 
    idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id

外部結合は意図的であり、nullを取得したいと考えています。それについては後で詳しく説明します。

WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;

rのみが必要です。l.timeよりも高い最低(MIN)時間を持つ行。これは、サブクエリが必要な場所です。

WHERE r.time = (SELECT MIN(time) FROM idtimes r2 where r2.id = l.id AND r2.time > l.time)

さて、ヌルに。「次の時間がない」場合、SELECT MIN()はnull(またはさらに悪い)に評価され、それ自体は何にも等しく比較されないため、WHERE句は満たされず、「最高時間」各IDについて、結果セットに表示されることはありません。

JOINを削除し、スカラーサブクエリをSELECTリストに移動することで解決します。

SELECT id, time, 
    (SELECT MIN(time) FROM idtimes sub 
        WHERE sub.id = main.id AND sub.time > main.time) as nxttime
  FROM idtimes AS main 

4

私は常にサブクエリを使用することを避けますSELECTブロックまたはFROMコードを「ダーティ」にし、時には効率を低下させるためます。

よりエレガントな方法は次のとおりだと思います:

1. 時間より大きい時間を見つけるの行の

これは、idtimesJOIN間テーブルとそれ自体を使用して行うことができ、同じid時間よりも長い時間に結合を制限します現在の行の。

現在の行の1つより大きいLEFT JOIN行が存在しない行を除外しないようにするために使用する必要があります。

SELECT
    i1.id,
    i1.time AS time,
    i2.time AS greater_time
FROM
    idtimes AS i1
    LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time

先ほど触れたように、問題はnext_timetimeよりも大きい複数の行があることです。

+-----+------------+--------------+
| id  | time       | greater_time |
+-----+------------+--------------+
| 155 | 1300000000 | 1311111111   |
| 155 | 1300000000 | 1322222222   |
| 155 | 1311111111 | 1322222222   |
| 155 | 1322222222 |       NULL   |
| 156 | 1312345678 | 1318765432   |
| 156 | 1318765432 |       NULL   |
+-----+------------+--------------+

2.列検索greater_timeが唯一大きくないが、NEXT_TIMEを

これらの無用のすべての行をフィルタリングするための最良の方法があるかどうかを確認することで時間の間の時間(より大きい)とgreater_time(より少ない)は、このためのID

SELECT
    i1.id,
    i1.time AS time,
    i2.time AS next_time,
    i3.time AS intrudor_time
FROM
    idtimes AS i1
    LEFT JOIN idtimes AS i2 ON i1.id = i2.id AND i2.time > i1.time
    LEFT JOIN idtimes AS i3 ON i2.id = i3.id AND i3.time > i1.time AND i3.time < i2.time

ops、まだnext_timeが間違っている

+-----+------------+--------------+---------------+
| id  | time       | next_time    | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111   |         NULL  |
| 155 | 1300000000 | 1322222222   |    1311111111 |
| 155 | 1311111111 | 1322222222   |         NULL  |
| 155 | 1322222222 |       NULL   |         NULL  |
| 156 | 1312345678 | 1318765432   |         NULL  |
| 156 | 1318765432 |       NULL   |         NULL  |
+-----+------------+--------------+---------------+

このイベントが発生する行をフィルタリングし、WHERE以下の制約を追加します

WHERE
    i3.time IS NULL

ほら、必要なものがあります!

+-----+------------+--------------+---------------+
| id  | time       | next_time    | intrudor_time |
+-----+------------+--------------+---------------+
| 155 | 1300000000 | 1311111111   |         NULL  |
| 155 | 1311111111 | 1322222222   |         NULL  |
| 155 | 1322222222 |       NULL   |         NULL  |
| 156 | 1312345678 | 1318765432   |         NULL  |
| 156 | 1318765432 |       NULL   |         NULL  |
+-----+------------+--------------+---------------+

4年経ってもまだ回答が必要であることを願っています!


それは賢いです。しかし、理解しやすいかどうかはわかりません。is nulli3へのandの結合をで置き換えた場合where not exists (select 1 from itimes i3 where [same clause])、コードは表現したいものをより密接に反映すると思います。
アンドリュースペンサー

あなたは私の(次の)日を救ったthx男!
ヤコブ

2

ソリューションを提示する前に、私はそれがきれいではないことに注意する必要があります。AUTO_INCREMENTテーブルに列があればもっと簡単でしょう(そうですか?)

SELECT 
  l.id, l.time, 
  SUBSTRING_INDEX(GROUP_CONCAT(r.time ORDER BY r.time), ',', 1)
FROM 
  idtimes AS l 
  LEFT JOIN idtimes AS r ON (l.id = r.id)
WHERE 
  l.time < r.time
GROUP BY
  l.id, l.time

説明:

  • あなたと同じ結合:2つのテーブルを結合します。正しいテーブルはより高い時間を取得します
  • 左側の表の両方の列のGROUP BY:これにより、すべての(id, time)組み合わせ(一意であることがわかっている)が取得されます。
  • それぞれについて(l.id, l.time)、より大きい最初の r.timeものを取得しl.timeます。これは最初にr.timesを並べることで起こり、GROUP_CONCAT(r.time ORDER BY r.time)最初のトークンをスライスすることでSUBSTRING_INDEX

幸運を祈ります。このテーブルが大きい場合、良いパフォーマンスを期待しないでください。


2

また、あなたがから欲しいものを得ることができるmin()GROUP BYなし、内側を選択して:

SELECT l.id, l.time, min(r.time) 
FROM idtimes l 
LEFT JOIN idtimes r on (r.id = l.id and r.time > l.time)
GROUP BY l.id, l.time;

私はでしょうほとんどオプティマイザはとにかくアーウィンSmoutの答えと同じことにこれをオンにし、それがどんな明確だかどうか議論の余地のですが、それは完全にするために存在することが多額の金を賭け...


1
何その価値については、SSMS&SQLServerの2016はアーウィンさん(2Sランタイム対24Sは〜24kの結果セットに実行時)よりも多くのクエリを気に入っ
ネイサンラファティ

アンドリューは賭けに負けたようです:-)
アーウィンスモート

興味深いのは、PK列の1つによって外側のクエリテーブルに結合するサブクエリがgroup byと同じであるという一般的なケースであるべきだからです。他のデータベースで最適化できるのではないかと思います。(データベースオプティマイザーについてはほとんど知りません。ただ興味があります。)
アンドリュースペンサー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.