Eu tenho duas tabelas em um banco de dados MySQL 5.7.22: posts
e reasons
. Cada linha de postagem possui e pertence a muitas linhas de motivo. Cada razão tem um peso associado a ela e, portanto, cada postagem possui um peso agregado total associado a ela.
Para cada incremento de 10 pontos de peso (ou seja, para 0, 10, 20, 30, etc.), quero obter uma contagem de postagens que tenham um peso total menor ou igual a esse incremento. Eu esperaria que os resultados parecessem algo assim:
weight | post_count
--------+------------
0 | 0
10 | 5
20 | 12
30 | 18
... | ...
280 | 20918
290 | 21102
... | ...
1250 | 118005
1260 | 118039
1270 | 118040
Os pesos totais são aproximadamente distribuídos normalmente, com alguns valores muito baixos e alguns valores muito altos (o máximo é atualmente 1277), mas a maioria no meio. Existem pouco menos de 120.000 linhas posts
e cerca de 120 polegadas reasons
. Cada post tem em média 5 ou 6 razões.
As partes relevantes das tabelas são assim:
CREATE TABLE `posts` (
id BIGINT PRIMARY KEY
);
CREATE TABLE `reasons` (
id BIGINT PRIMARY KEY,
weight INT(11) NOT NULL
);
CREATE TABLE `posts_reasons` (
post_id BIGINT NOT NULL,
reason_id BIGINT NOT NULL,
CONSTRAINT fk_posts_reasons_posts (post_id) REFERENCES posts(id),
CONSTRAINT fk_posts_reasons_reasons (reason_id) REFERENCES reasons(id)
);
Até agora, tentei soltar o ID da postagem e o peso total em uma visualização e associá-la a ela mesma para obter uma contagem agregada:
CREATE VIEW `post_weights` AS (
SELECT
posts.id,
SUM(reasons.weight) AS reason_weight
FROM posts
INNER JOIN posts_reasons ON posts.id = posts_reasons.post_id
INNER JOIN reasons ON posts_reasons.reason_id = reasons.id
GROUP BY posts.id
);
SELECT
FLOOR(p1.reason_weight / 10) AS weight,
COUNT(DISTINCT p2.id) AS cumulative
FROM post_weights AS p1
INNER JOIN post_weights AS p2 ON FLOOR(p2.reason_weight / 10) <= FLOOR(p1.reason_weight / 10)
GROUP BY FLOOR(p1.reason_weight / 10)
ORDER BY FLOOR(p1.reason_weight / 10) ASC;
Isso é, no entanto, inusitavelmente lento - deixei que funcionasse por 15 minutos sem terminar, o que não posso fazer na produção.
Existe uma maneira mais eficiente de fazer isso?
Caso você esteja interessado em testar o conjunto de dados inteiro, ele pode ser baixado aqui . O arquivo tem cerca de 60 MB e se expande para cerca de 250 MB. Como alternativa, existem 12.000 linhas em uma essência do GitHub aqui .
w.weight
- está certo? Estou procurando contar postagens com um peso total (soma dos pesos das linhas de razão associadas) de ltew.weight
.post_weights
exibição existente que eu já criei em vez dereasons
.No MySQL, variáveis podem ser usadas em consultas para serem calculadas a partir de valores em colunas e para expressão em colunas novas e calculadas. Nesse caso, o uso de uma variável resulta em uma consulta eficiente:
A
d
tabela derivada é realmente suapost_weights
visão. Portanto, se você planeja manter a visualização, poderá usá-la em vez da tabela derivada:Uma demonstração desta solução, que usa uma edição concisa da versão reduzida da sua instalação, pode ser encontrada e reproduzida no SQL Fiddle .
fonte
ERROR 1055 (42000): 'd.reason_weight' isn't in GROUP BY
seONLY_FULL_GROUP_BY
está em @@ sql_mode. Desativando, notei que sua consulta é mais lenta que a minha na primeira vez em que é executada (~ 11 segundos). Depois que os dados são armazenados em cache, fica mais rápido (~ 1 segundo). Minha consulta é executada em cerca de 4 segundos todas as vezes.GROUP BY FLOOR(reason_weight / 10)
mas aceitaGROUP BY reason_weight
. Quanto ao desempenho, certamente não sou especialista também no MySQL, foi apenas uma observação na minha máquina de baixa qualidade. Como eu executei minha consulta primeiro, todos os dados já deveriam ter sido armazenados em cache, então não sei por que foi mais lento na primeira vez em que foi executado.