У меня есть две таблицы в базе данных MySQL 5.7.22: posts
и reasons
. Каждая строка сообщения имеет и принадлежит многим рядам причин. У каждой причины есть вес, связанный с ней, и поэтому у каждого сообщения есть общий агрегированный вес, связанный с ним.
Для каждого увеличения веса на 10 пунктов (т. Е. Для 0, 10, 20, 30 и т. Д.) Я хочу получить количество сообщений, общий вес которых меньше или равен этому приращению. Я ожидаю, что результаты для этого будут выглядеть примерно так:
weight | post_count
--------+------------
0 | 0
10 | 5
20 | 12
30 | 18
... | ...
280 | 20918
290 | 21102
... | ...
1250 | 118005
1260 | 118039
1270 | 118040
Полные веса примерно нормально распределены, с несколькими очень низкими значениями и несколькими очень высокими значениями (максимальный в настоящее время 1277), но большинство в середине. Есть чуть менее 120000 строк posts
и около 120 дюймов reasons
. Каждый пост имеет в среднем 5 или 6 причин.
Соответствующие части таблиц выглядят так:
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)
);
До сих пор я пытался сбросить идентификатор сообщения и общий вес в представление, а затем соединить это представление с самим собой, чтобы получить агрегированное число:
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;
Это, однако, необычайно медленно - я позволил ему работать в течение 15 минут без остановки, что я не могу сделать в производстве.
Есть ли более эффективный способ сделать это?
Если вы заинтересованы в тестировании всего набора данных, его можно загрузить здесь . Размер файла составляет около 60 МБ, он увеличивается до 250 МБ. С другой стороны , есть 12000 строк в сущности GitHub здесь .
w.weight
- это правильно? Я рассчитываю подсчитывать сообщения с общим весом (суммой весов связанных с ними строк причин) ltew.weight
.post_weights
представления, которое я уже создал вместоreasons
.В MySQL переменные могут использоваться в запросах как для вычисления по значениям в столбцах, так и для использования в выражении для новых вычисляемых столбцов. В этом случае использование переменной приводит к эффективному запросу:
Производная
d
таблица на самом деле вашеpost_weights
мнение. Поэтому, если вы планируете сохранить представление, вы можете использовать его вместо производной таблицы:Демонстрацию этого решения, в которой используется сжатая версия сокращенной версии вашей установки, можно найти и поиграть на SQL Fiddle .
источник
ERROR 1055 (42000): 'd.reason_weight' isn't in GROUP BY
еслиONLY_FULL_GROUP_BY
находится в @@ sql_mode. Отключив его, я заметил, что ваш запрос выполняется медленнее, чем мой в первый раз (~ 11 секунд). Как только данные кэшируются, это происходит быстрее (~ 1 сек). Мой запрос выполняется около 4 секунд каждый раз.GROUP BY FLOOR(reason_weight / 10)
но принимаетGROUP BY reason_weight
. Что касается производительности, то я, конечно, не эксперт в том, что касается MySQL, это было просто наблюдение на моей дрянной машине. Поскольку я сначала запустил свой запрос, все данные уже должны были быть кэшированы, поэтому я не знаю, почему он работал медленнее при первом запуске.