it-swarm-eu.dev

Jak získám aktuální a další větší hodnotu v jednom výběru?

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)?

18
Martin Hennings

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

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:

1. Najděte časy větší než čas řádku

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   |
+-----+------------+--------------+

2. Najděte řádky, kde more_time je nejen větší, ale next_time

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ěď!

4
luisfsns

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 ...

2
Andrew Spencer

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í:

  • Stejné jako vaše: připojte se ke dvěma tabulkám, ten pravý dostane pouze vyšší časy
  • GROUP BY oba sloupce z levé tabulky: to zajistí, že dostaneme všechny kombinace (id, time) (Které jsou také známé jako jedinečné).
  • Pro každý (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á.

2
Shlomi Noach