Questo è il più grande problema di n-per-gruppo ed è una domanda SQL molto comune.
Ecco come lo risolvo con gli outer join:
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;
Sto assumendo la chiave primaria dell'item
la tabella è item_id
, e che è una pseudochiave monotonicamente crescente. Cioè, un valore maggiore in item_id
corrisponde a una riga più recente in item
.
Ecco come funziona:per ogni articolo, ci sono un certo numero di altri articoli che sono più recenti. Ad esempio, ci sono tre elementi più recenti del quarto elemento più recente. Non ci sono articoli più recenti dell'articolo più recente. Quindi vogliamo confrontare ogni articolo (i1
) all'insieme di elementi (i2
) che sono più recenti e hanno la stessa categoria di i1
. Se il numero di questi elementi più recenti è inferiore a quattro, i1
è uno di quelli che includiamo. In caso contrario, non includerlo.
Il bello di questa soluzione è che funziona indipendentemente dal numero di categorie che hai e continua a funzionare se cambi le categorie. Funziona anche se il numero di elementi in alcune categorie è inferiore a quattro.
Un'altra soluzione che funziona ma si basa sulla funzionalità delle variabili utente di 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 ha introdotto il supporto per le funzioni della finestra standard SQL. Ora possiamo risolvere questo tipo di problema come fanno gli altri 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;