web-dev-qa-db-ja.com

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

列を持つ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行ずつステップ実行して最後の値を記憶することに依存する@variablesを使用したくない)。

18
Martin Hennings

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 
20
Erwin Smout

私は常にサブクエリの使用を避けますSELECTブロックまたはFROMブロックのいずれかで使用します。コードが「ダーティ」になり、効率が低下することがあります。

それを行うためのよりエレガントな方法は次のとおりです。

1.行のtimeより大きいtimesを見つけます

これは、JOIN between idtimesテーブル自体を使用して行うことができ、結合を同じidtimesに制限します現在の行のtimeよりも。

現在の行の行よりも大きいtimesがない行を除外しないようにするには、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である行を見つけます

これらの不要な行のすべてをフィルタリングする最良の方法は、time(より大きい)とgreater_time(より小さい)の間にtimesがあるかどうかを調べることです。 )このため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、まだfalse 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

Voilà、私たちは必要なものを持っています!

+-----+------------+--------------+---------------+
| 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年経ってもまだ回答が必要です。

4
luisfsns

内部選択なしで、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;

私はほぼオプティマイザがこれをとにかくアーウィン・スムートの答えと同じものに変えるという多額のお金を賭けるでしょう、そしてそれがより明確であるかどうかは議論の余地がありますが、完全性のためにあります...

2
Andrew Spencer

ソリューションを提示する前に、それはきれいではないことに注意してください。テーブルに_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_より大きいfirst _l.time_を取得します。これは、_r.time_を介して最初のトークンをスライスすることにより、GROUP_CONCAT(r.time ORDER BY r.time)を介して_SUBSTRING_INDEX_ sを最初に順序付けするときに発生します。

幸運を祈ります。このテーブルが大きい場合、良いパフォーマンスは期待できません。

2
Shlomi Noach