Mám tabulku InnoDB 'idtimes' (MySQL 5.0.22-log) se sloupci
`id` int(11) NOT NULL,
`time` int(20) NOT NULL, [...]
se složeným jedinečným klíčem
UNIQUE KEY `id_time` (`id`,`time`)
takže může existovat více časových razítek na jedno ID a více ID na časové razítko.
Snažím se nastavit dotaz, kde získám všechny záznamy plus další větší čas pro každou položku, pokud existuje, tak by se měla vrátit např .:
+-----+------------+------------+
| id | time | nexttime |
+-----+------------+------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 | NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 | NULL |
+-----+------------+------------+
Právě teď jsem zatím:
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;
ale samozřejmě to vrátí všechny řádky s r.time> l.time a nejen první ...
Myslím, že budu potřebovat subelect
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;
ale nevím, jak odkazovat na aktuální čas (vím, že výše uvedené není platné SQL).
Jak to mohu udělat s jediným dotazem (a já bych raději nepoužívat @variables, které závisí na krokování i když tabulka po jednom řádku najednou a zapamatování poslední hodnoty)?
Dělat PŘIPOJENÍ je jedna věc, kterou možná budete potřebovat.
SELECT l.id, l.time, r.time FROM
idtimes AS l LEFT JOIN idtimes AS r ON l.id = r.id
Předpokládám, že vnější spojení je úmyslné a vy chcete být nulové. Více o tom později.
WHERE l.time < r.time ORDER BY l.id ASC, l.time ASC;
Chceš pouze r. řádek, který má nejnižší (MIN) čas, který je vyšší než l.time. To je místo, kde potřebujete subquerying.
WHERE r.time = (SELECT MIN(time) FROM idtimes r2 where r2.id = l.id AND r2.time > l.time)
Teď k nulovým hodnotám. Pokud není „žádný další vyšší čas“, pak se SELECT MIN () vyhodnotí jako null (nebo horší) a to samo o sobě nikdy nebude srovnatelné s ničím, takže vaše klauzula WHERE nebude nikdy uspokojena a „nejvyšší čas“ pro každé ID se nikdy nemohla objevit v sadě výsledků.
Řešíte to odstraněním JOIN a přesunutím skalárního poddotazu do seznamu VYBRAT:
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
Vždy se vyhýbám použití poddotazů buď v bloku SELECT
nebo v bloku FROM
, protože to způsobuje, že kód je "špinavější" a někdy méně účinný.
Myslím, že elegantnější způsob, jak to udělat, je:
To lze provést pomocí tabulky JOIN
mezi tabulkou idtimes se sebou samým, čímž se omezí spojení na stejné id a na časy větší než čas aktuálního řádku.
Měli byste použít LEFT JOIN
abyste se vyhnuli vyloučení řádků, ve kterých nejsou žádné časy větší než jeden z aktuálních řádků.
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
Problém, jak jste zmínil, je, že máte více řádků, kde next_time je větší než čas .
+-----+------------+--------------+
| id | time | greater_time |
+-----+------------+--------------+
| 155 | 1300000000 | 1311111111 |
| 155 | 1300000000 | 1322222222 |
| 155 | 1311111111 | 1322222222 |
| 155 | 1322222222 | NULL |
| 156 | 1312345678 | 1318765432 |
| 156 | 1318765432 | NULL |
+-----+------------+--------------+
Nejlepší způsob, jak filtrovat všechny tyto zbytečné řádky, je zjistit, zda existují časy mezi časem (větší než) a delší dobu (menší než) pro toto 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, stále máme 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 |
+-----+------------+--------------+---------------+
Jednoduše filtrujte řádky, ve kterých k této události dochází, a přidejte omezení WHERE
níže
WHERE
i3.time IS NULL
Voilà, máme to, co potřebujeme!
+-----+------------+--------------+---------------+
| 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 |
+-----+------------+--------------+---------------+
Doufám, že po 4 letech stále potřebujete odpověď!
Můžete také získat to, co chcete od min()
a GROUP BY
bez vnitřního výběru:
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;
Já bych téměř vsadil velkou částku peněz, že to optimalizátor promění ve stejnou věc jako odpověď Erwina Smouta, a je sporné, zda je to jasnější, ale je to pro úplnost ...
Před představením řešení bych si měl všimnout, že to není hezké. Bylo by mnohem jednodušší, kdybyste měli na stole nějaký sloupec AUTO_INCREMENT
(Ano?)
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
Vysvětlení:
(id, time)
(Které jsou také známé jako jedinečné).(l.id, l.time)
Získejte prvnír.time
, Který je větší než l.time
. K tomu dochází při prvním objednání r.time
S pomocí GROUP_CONCAT(r.time ORDER BY r.time)
, krájením prvního tokenu přes SUBSTRING_INDEX
.Hodně štěstí a neočekávejte dobrý výkon, pokud je tato tabulka velká.