J'ai deux tables dans une base de données MySQL 5.7.22: posts
et reasons
. Chaque ligne de publication a et appartient à de nombreuses lignes de raison. Chaque raison a un poids qui lui est associé, et chaque poste a donc un poids total agrégé qui lui est associé.
Pour chaque incrément de 10 points de poids (c'est-à-dire pour 0, 10, 20, 30, etc.), je veux obtenir un nombre de messages dont le poids total est inférieur ou égal à cet incrément. Je m'attendrais à ce que les résultats ressemblent à ceci:
weight | post_count
--------+------------
0 | 0
10 | 5
20 | 12
30 | 18
... | ...
280 | 20918
290 | 21102
... | ...
1250 | 118005
1260 | 118039
1270 | 118040
Les poids totaux sont approximativement normalement distribués, avec quelques valeurs très faibles et quelques valeurs très élevées (le maximum est actuellement 1277), mais la majorité au milieu. Il y a un peu moins de 120 000 rangées posts
et environ 120 pouces reasons
. Chaque message a en moyenne 5 ou 6 raisons.
Les parties pertinentes des tableaux ressemblent à ceci:
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)
);
Jusqu'à présent, j'ai essayé de supprimer l'ID du message et le poids total dans une vue, puis de joindre cette vue à elle-même pour obtenir un nombre agrégé:
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;
C'est cependant inhabituellement lent - je l'ai laissé fonctionner pendant 15 minutes sans terminer, ce que je ne peux pas faire en production.
Existe-t-il un moyen plus efficace de procéder?
Si vous souhaitez tester l'ensemble de données, il est téléchargeable ici . Le fichier fait environ 60 Mo, il s'étend à environ 250 Mo. Alternativement, il y a 12 000 lignes dans un résumé GitHub ici .
w.weight
- est-ce vrai? Je cherche à compter les messages avec un poids total (somme des poids de leurs lignes de raison associées) de ltew.weight
.post_weights
vue existante que j'ai déjà créée à la placereasons
.Dans MySQL, les variables peuvent être utilisées dans les requêtes à la fois pour être calculées à partir des valeurs des colonnes et pour être utilisées dans l'expression pour les nouvelles colonnes calculées. Dans ce cas, l'utilisation d'une variable entraîne une requête efficace:
La
d
table dérivée est en fait votrepost_weights
vue. Par conséquent, si vous prévoyez de conserver la vue, vous pouvez l'utiliser à la place de la table dérivée:Une démonstration de cette solution, qui utilise une édition concise de la version réduite de votre configuration, peut être trouvée et jouée avec à SQL Fiddle .
la source
ERROR 1055 (42000): 'd.reason_weight' isn't in GROUP BY
siONLY_FULL_GROUP_BY
est dans @@ sql_mode. En le désactivant, j'ai remarqué que votre requête est plus lente que la mienne la première fois (~ 11 sec). Une fois les données mises en cache, elles sont plus rapides (~ 1 sec). Ma requête s'exécute en environ 4 secondes à chaque fois.GROUP BY FLOOR(reason_weight / 10)
mais accepteGROUP BY reason_weight
. En ce qui concerne les performances, je ne suis certainement pas un expert non plus en ce qui concerne MySQL, c'était juste une observation sur ma machine de merde. Depuis que j'ai exécuté ma requête en premier, toutes les données devraient déjà avoir été mises en cache, donc je ne sais pas pourquoi elles ont été plus lentes lors de leur première exécution.