Mam dwie tabele w bazie danych MySQL 5.7.22: posts
i reasons
. Każdy wiersz postu ma i należy do wielu wierszy przyczyny. Każdy powód ma przypisaną wagę, a zatem każdy post ma z nim łączną łączną wagę.
Dla każdego przyrostu 10 punktów wagi (tj. Dla 0, 10, 20, 30 itd.) Chcę uzyskać liczbę postów, których łączna waga jest mniejsza lub równa temu przyrostowi. Spodziewałbym się, że wyniki będą wyglądać mniej więcej tak:
weight | post_count
--------+------------
0 | 0
10 | 5
20 | 12
30 | 18
... | ...
280 | 20918
290 | 21102
... | ...
1250 | 118005
1260 | 118039
1270 | 118040
Całkowite masy są w przybliżeniu normalnie rozłożone, z kilkoma bardzo niskimi wartościami i kilkoma bardzo wysokimi wartościami (maksimum wynosi obecnie 1277), ale większość pośrodku. Istnieje nieco mniej niż 120 000 wierszy posts
i około 120 cali reasons
. Każdy post ma średnio 5 lub 6 powodów.
Odpowiednie części tabel wyglądają tak:
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)
);
Do tej pory próbowałem upuścić identyfikator posta i całkowitą wagę do widoku, a następnie połączyć się z tym widokiem, aby uzyskać zagregowaną liczbę:
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;
Jest to jednak niezwykle wolne - pozwalam mu działać przez 15 minut bez przerywania, czego nie mogę zrobić w produkcji.
Czy istnieje bardziej skuteczny sposób to zrobić?
Jeśli chcesz przetestować cały zestaw danych, możesz go pobrać tutaj . Plik ma około 60 MB, rozwija się do około 250 MB. Alternatywnie istnieje 12000 wierszy w GitHub GIST tutaj .
w.weight
- prawda? Chcę policzyć posty o łącznej wadze (suma wag powiązanych z nimi wierszy przyczyny) ltew.weight
.post_weights
Musiałem tylko wybrać z istniejącego widoku, który już utworzyłemreasons
.W MySQL zmienne mogą być używane w zapytaniach zarówno do obliczenia na podstawie wartości w kolumnach, jak i do wyrażenia nowych, obliczonych kolumn. W takim przypadku użycie zmiennej powoduje wydajne zapytanie:
d
Tabela pochodzi to rzeczywiście twójpost_weights
widok. Dlatego jeśli planujesz zachować widok, możesz go użyć zamiast tabeli pochodnej:Demo tego rozwiązania, które wykorzystuje zwięzłą edycję zredukowanej wersji twojej instalacji, można znaleźć i zagrać w SQL Fiddle .
źródło
ERROR 1055 (42000): 'd.reason_weight' isn't in GROUP BY
jeśliONLY_FULL_GROUP_BY
jest w trybie @@ sql_mode. Wyłączanie Zauważyłem, że twoje zapytanie jest wolniejsze niż moje przy pierwszym uruchomieniu (~ 11 sekund). Po buforowaniu danych jest szybsze (~ 1 sekunda). Moje zapytanie jest uruchamiane za każdym razem około 4 sekund.GROUP BY FLOOR(reason_weight / 10)
ale akceptujeGROUP BY reason_weight
. Jeśli chodzi o wydajność, z pewnością nie jestem ekspertem, jeśli chodzi o MySQL, to była tylko obserwacja na mojej gównianej maszynie. Ponieważ najpierw uruchomiłem kwerendę, wszystkie dane powinny już zostać buforowane, więc nie wiem, dlaczego było wolniejsze przy pierwszym uruchomieniu.