Este é o maior problema de n por grupo e é uma pergunta SQL muito comum.
Aqui está como eu resolvo isso com associações externas:
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;
Estou assumindo a chave primária do
item
tabela é item_id
, e que é uma pseudochave monotonicamente crescente. Ou seja, um valor maior em item_id
corresponde a uma linha mais recente em item
. Veja como funciona:para cada item, há alguns outros itens que são mais recentes. Por exemplo, há três itens mais recentes que o quarto item mais recente. Há zero itens mais recentes do que o item mais recente. Então, queremos comparar cada item (
i1
) ao conjunto de itens (i2
) que são mais recentes e têm a mesma categoria que i1
. Se o número desses itens mais recentes for menor que quatro, i1
é um dos que incluímos. Caso contrário, não inclua. A beleza dessa solução é que ela funciona independentemente de quantas categorias você tenha e continua funcionando se você alterar as categorias. Também funciona mesmo que o número de itens em algumas categorias seja inferior a quatro.
Outra solução que funciona, mas depende do recurso de variáveis de usuário do 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;
O MySQL 8.0.3 introduziu suporte para funções de janela padrão SQL. Agora podemos resolver esse tipo de problema da mesma forma que outros RDBMS fazem:
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;