SELECT ... LIMIT não é suportado em subconsultas, receio, então é hora de quebrar a mágica da auto-junção:
SELECT article.*
FROM article
JOIN (
SELECT a0.category_id AS id, MIN(a2.article_id) AS lim
FROM article AS a0
LEFT JOIN article AS a1 ON a1.category_id=a0.category_id AND a1.article_id>a0.article_id
LEFT JOIN article AS a2 ON a2.category_id=a1.category_id AND a2.article_id>a1.article_id
GROUP BY id
) AS cat ON cat.id=article.category_id
WHERE article.article_id<=cat.lim OR cat.lim IS NULL
ORDER BY article_id;
O bit no meio está calculando o ID do artigo com o terceiro ID mais baixo para cada categoria, tentando juntar três cópias da mesma tabela em ordem crescente de ID. Se houver menos de três artigos para uma categoria, as junções à esquerda garantirão que o limite seja NULL, portanto, o WHERE externo também precisa selecionar esse caso.
Se o seu requisito “top 3” pode mudar para “top n” em algum momento, isso começa a ficar complicado. Nesse caso, convém reconsiderar a ideia de consultar a lista de categorias distintas primeiro e depois unir as consultas por categoria.
ETA:Pedido em duas colunas:eek, novos requisitos! :-)
Depende do que você quer dizer:se você está apenas tentando ordenar os resultados finais, pode acertar no final sem problemas. Mas se você precisar usar essa ordenação para selecionar quais três artigos devem ser escolhidos, as coisas são muito mais difíceis.
Estamos usando uma auto-junção com '<' para reproduzir o efeito que 'ORDER BY article_id' teria. Infelizmente, embora você possa fazer 'ORDER BY a, b', você não pode faça '(a, b)<(c, d)'... nem você pode fazer 'MIN(a, b)'. Além disso, você estaria ordenando por três colunas, issticky, publicado e article_id, porque você precisa garantir que cada valor de pedido seja exclusivo, para evitar que quatro ou mais linhas sejam retornadas.
Enquanto você poderia crie seu próprio valor ordenável por algum inteiro bruto ou combinação de strings de colunas:
LEFT JOIN article AS a1
ON a1.category_id=a0.category_id
AND HEX(a1.issticky)+HEX(a1.published_at)+HEX(a1.article_id)>HEX(a0.issticky)+HEX(a0.published_at)+HEX(a0.article_id)
isso está ficando inviável, e os cálculos vão acabar com qualquer chance de usar os índices para tornar a consulta eficiente. Nesse ponto, é melhor simplesmente fazer as consultas LIMITadas por categoria separadas.