Inquérito: contar vários agregados por item
-
07-07-2019 - |
Pergunta
Muitas vezes, você precisa mostrar uma lista de itens de banco de dados e certos números agregados sobre cada item. Por exemplo, quando você digita o texto do título no Stack Overflow, liste as Questões relacionadas aparece. A lista mostra os títulos de entradas relacionadas e o número agregado única da quantidade de respostas para cada título.
Eu tenho um problema semelhante, mas precisando de vários agregados. Eu gostaria de exibir uma lista de itens em qualquer um dos 3 formatos, dependendo das opções do usuário:
- O nome do meu item (15 totais, 13 possuído por mim)
- O nome do meu item (15 no total)
- O nome do meu item (13 possuído por mim)
Meu banco de dados é:
- itens : itemId, itemName, ownerId
- categorias : catId, catName
- Mapa : mapId, itemId, catId
A consulta abaixo recebe: nome da categoria, a contagem de IDs de itens por categoria
SELECT
categories.catName,
COUNT(map.itemId) AS item_count
FROM categories
LEFT JOIN map
ON categories.catId = map.catId
GROUP BY categories.catName
Esta se obtém: nome da categoria, a contagem de IDs de itens por categoria para este owner_id única
SELECT categories.catName,
COUNT(map.itemId) AS owner_item_count
FROM categories
LEFT JOIN map
ON categories.catId = map.catId
LEFT JOIN items
ON items.itemId = map.itemId
WHERE owner = @ownerId
GROUP BY categories.catId
Mas como faço para obtê-los, ao mesmo tempo em uma única consulta? I.e .: nome da categoria, a contagem de IDs de itens por categoria, a contagem de IDs de itens por categoria para este owner_id única
Bonus. Como posso opcionalmente recuperar somente onde catid contar! = 0 para qualquer um desses? Na tentativa "ONDE ITEM_COUNT <> 0" eu recebo:
MySQL said: Documentation
#1054 - Unknown column 'rid_count' in 'where clause'
Solução
Aqui está um truque: cálculo de um SUM()
de valores que são conhecidos por ser 1 ou 0 é equivalente a uma COUNT()
das linhas onde o valor é 1. E você sabe que uma comparação boolean retorna 1 ou 0 (ou nulo) .
SELECT c.catname, COUNT(m.catid) AS item_count,
SUM(i.ownerid = @ownerid) AS owner_item_count
FROM categories c
LEFT JOIN map m USING (catid)
LEFT JOIN items i USING (itemid)
GROUP BY c.catid;
Quanto à questão de bônus, você poderia simplesmente fazer uma junção interna em vez de uma associação externa, o que significaria apenas as categorias com pelo menos uma linha na map
seria devolvido.
SELECT c.catname, COUNT(m.catid) AS item_count,
SUM(i.ownerid = @ownerid) AS owner_item_count
FROM categories c
INNER JOIN map m USING (catid)
INNER JOIN items i USING (itemid)
GROUP BY c.catid;
Aqui está uma outra solução, que não é tão eficiente, mas eu vou mostrá-lo para explicar porque você tem o erro:
SELECT c.catname, COUNT(m.catid) AS item_count,
SUM(i.ownerid = @ownerid) AS owner_item_count
FROM categories c
LEFT JOIN map m USING (catid)
LEFT JOIN items i USING (itemid)
GROUP BY c.catid
HAVING item_count > 0;
Você não pode usar apelidos de coluna na cláusula WHERE
, porque as expressões na cláusula WHERE
são avaliadas antes das expressões na lista de seleção. Em outras palavras, os valores associados com select-list expressões ainda não estão disponíveis.
Você pode usar aliases de coluna nas cláusulas GROUP BY
, HAVING
e ORDER BY
. Estas cláusulas são executados depois de todas as expressões na lista de seleção foram avaliadas.
Outras dicas
Você pode esgueirar-se uma instrução CASE dentro de sua SUM ():
SELECT categories.catName,
COUNT(map.itemId) AS item_count,
SUM(CASE WHEN owner= @ownerid THEN 1 ELSE 0 END) AS owner_item_count
FROM categories
LEFT JOIN map ON categories.catId = map.catId
LEFT JOIN items ON items.itemId = map.itemId
GROUP BY categories.catId
HAVING COUNT(map.itemId) > 0
SELECT categories.catName,
COUNT(map.itemId) AS item_count,
COUNT(items.itemId) AS owner_item_count
FROM categories
INNER JOIN map
ON categories.catId = map.catId
LEFT JOIN items
ON items.itemId = map.itemId
AND items.owner = @ownerId
GROUP BY categories.catId
Note que você poderia usar uma cláusula HAVING
em owner_item_count
, mas a junção interna cuida de ITEM_COUNT para você.