Ich habe zwei Tabellen in einer MySQL 5.7.22-Datenbank: posts
und reasons
. Jede Beitragszeile hat und gehört zu vielen Grundzeilen. Jeder Grund hat ein Gewicht mit ihm verbunden, und jeder Beitrag hat daher ein Gesamt aggregierte Gewicht mit ihm verbunden ist .
Für jedes Inkrement von 10 Gewichtspunkten (dh für 0, 10, 20, 30 usw.) möchte ich eine Anzahl von Posts erhalten, deren Gesamtgewicht kleiner oder gleich diesem Inkrement ist. Ich würde erwarten, dass die Ergebnisse dafür ungefähr so aussehen:
weight | post_count
--------+------------
0 | 0
10 | 5
20 | 12
30 | 18
... | ...
280 | 20918
290 | 21102
... | ...
1250 | 118005
1260 | 118039
1270 | 118040
Die Gesamtgewichte sind ungefähr normal verteilt, mit einigen sehr niedrigen Werten und einigen sehr hohen Werten (Maximum ist derzeit 1277), aber die Mehrheit in der Mitte. Es gibt knapp 120.000 Zeilen in posts
und rund 120 in reasons
. Jeder Beitrag hat durchschnittlich 5 oder 6 Gründe.
Die relevanten Teile der Tabellen sehen folgendermaßen aus:
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)
);
Bisher habe ich versucht , die Post - ID und fallen Gesamtgewicht in eine Ansicht, dann verbindet diese Ansicht selbst eine aggregierte Zählung zu erhalten:
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;
Das ist jedoch ungewöhnlich langsam - ich lasse es 15 Minuten lang laufen, ohne es zu beenden, was ich in der Produktion nicht tun kann.
Gibt es einen effizienteren Weg, dies zu tun?
Wenn Sie den gesamten Datensatz testen möchten, können Sie ihn hier herunterladen . Die Datei ist ungefähr 60 MB groß und wird auf ungefähr 250 MB erweitert. Alternativ gibt es 12.000 Zeilen in einem GitHub Kern hier .
w.weight
- stimmt das? Ich bin auf der Suche Beiträge mit einem zählen Gesamtgewicht (Summe der Gewichte ihrer zugehörigen Grunde Reihen) von ltew.weight
.post_weights
Ansicht auswählen , die ich bereits erstellt habe, anstattreasons
.In MySQL können Variablen in Abfragen verwendet werden, die sowohl aus Werten in Spalten berechnet als auch als Ausdruck für neue, berechnete Spalten verwendet werden. In diesem Fall führt die Verwendung einer Variablen zu einer effizienten Abfrage:
Die
d
abgeleitete Tabelle ist eigentlich Ihrepost_weights
Ansicht. Wenn Sie die Ansicht beibehalten möchten, können Sie sie daher anstelle der abgeleiteten Tabelle verwenden:Eine Demo dieser Lösung, die eine übersichtliche Version der reduzierten Version Ihres Setups verwendet, kann bei SQL Fiddle gefunden und gespielt werden .
quelle
ERROR 1055 (42000): 'd.reason_weight' isn't in GROUP BY
ob sieONLY_FULL_GROUP_BY
sich in @@ sql_mode befindet. Deaktivieren Ich habe festgestellt, dass Ihre Abfrage beim ersten Ausführen langsamer ist als meine (~ 11 Sek.). Sobald die Daten zwischengespeichert sind, sind sie schneller (~ 1 Sek.). Meine Abfrage wird jedes Mal in ca. 4 Sekunden ausgeführt.GROUP BY FLOOR(reason_weight / 10)
aber akzeptiertGROUP BY reason_weight
. Was die Leistung angeht, bin ich sicherlich auch kein Experte, wenn es um MySQL geht, es war nur eine Beobachtung auf meiner beschissenen Maschine. Da ich meine Abfrage zuerst ausgeführt habe, sollten alle Daten bereits zwischengespeichert sein, sodass ich nicht weiß, warum sie beim ersten Ausführen langsamer waren.