Toto je největší problém-n-per-group a je to velmi častá SQL otázka.
Zde je návod, jak to řeším s vnějšími spojeními:
SELECT i1.*
FROM item i1
LEFT OUTER JOIN item i2
ON (i1.category_id = i2.category_id AND i1.item_id < i2.item_id)
GROUP BY i1.item_id
HAVING COUNT(*) < 4
ORDER BY category_id, date_listed;
Předpokládám primární klíč item
tabulka je item_id
, a že je to monotónně rostoucí pseudoklíč. To znamená vyšší hodnotu v item_id
odpovídá novějšímu řádku v item
.
Funguje to takto:pro každou položku existuje určitý počet dalších položek, které jsou novější. Například existují tři položky novější než čtvrtá nejnovější položka. Není zde nula položek novějších než úplně nejnovější položka. Chceme tedy porovnat každou položku (i1
) k sadě položek (i2
), které jsou novější a mají stejnou kategorii jako i1
. Pokud je počet těchto novějších položek menší než čtyři, i1
je jedním z těch, které zahrnujeme. V opačném případě jej nezahrnujte.
Krása tohoto řešení spočívá v tom, že funguje bez ohledu na to, kolik kategorií máte, a funguje i nadále, pokud kategorie změníte. Funguje také, i když je počet položek v některých kategoriích menší než čtyři.
Další řešení, které funguje, ale spoléhá na funkci uživatelských proměnných MySQL:
SELECT *
FROM (
SELECT i.*, @r := IF(@g = category_id, @r+1, 1) AS rownum, @g := category_id
FROM (@g:=null, @r:=0) AS _init
CROSS JOIN item i
ORDER BY i.category_id, i.date_listed
) AS t
WHERE t.rownum <= 3;
MySQL 8.0.3 zavedlo podporu pro standardní funkce okna SQL. Nyní můžeme tento druh problému vyřešit způsobem, jakým to dělají ostatní RDBMS:
WITH numbered_item AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY item_id) AS rownum
FROM item
)
SELECT * FROM numbered_item WHERE rownum <= 4;