MySQL подзапросы с математическими операциями - PullRequest
1 голос
/ 26 января 2012

У меня есть две таблицы.

Один содержит список продуктов, первичным ключом которого является идентификатор продукта.Давайте представим, что ~ 10 столбцов с информацией о продукте сведены в один.

В другом содержится список баллов, которые пользователи выставляют за продукты.Столбцы - это идентификатор продукта, идентификатор пользователя и рейтинг.В этой таблице может быть на порядок больше записей, чем в таблице продуктов.

Я хочу получить в одном запросе всю информацию о продукте, а также его среднюю оценку и число пользователей.рейтинги пользователей.

Кажется, что это правильный путь:

SELECT 
    p.p_id,
    p.product_info,
    ( SELECT AVG(score) FROM ratings AS r WHERE r.p_id = p.p_id ) avg_rating,
    ( SELECT COUNT(score) FROM ratings AS r WHERE r.p_id = p.p_id ) num_ratings
    FROM products AS p

Реальный вопрос: Как это выглядит с точки зрения производительности при увеличении моей базы данных?Можно ли использовать меньше подзапросов и, возможно, заменить их объединениями?

Дополнительный вопрос: У меня был план, в котором я бы кэшировал среднюю оценку и количество оценок для каждого продукта в продуктахтаблица и обновлять это всякий раз, когда новый счет или обновленный счет прибывает.Это делает запрос очень простым, но моя интуиция говорит мне, что это действительно наивно.Предполагая, что это таблица InnoDB, кто-то может объяснить более определенно, почему такого рода кэширование может быть, а может и не быть хорошей идеей?

Ответы [ 6 ]

1 голос
/ 26 января 2012

Если product_info - это довольно длинный VARCHAR, следующий запрос может быть быстрее (при условии, что у вас есть составной индекс (p_id, оценка) для ratings и p_id проиндексирован в products):

SELECT 
  p_id,
  product_info,
  avg_rating,
  num_ratings
FROM (
  SELECT p_id, AVG(score) as avg_rating, COUNT(score) as num_ratings
  FROM ratings
  GROUP BY p_id
) as aggr
JOIN products USING (p_id);

Порядок объединения отражает порядок, в котором MySQL предпочел бы выполнить запрос (поскольку результат подзапроса не индексируется).

Но запрос работает хорошо, когда ratings содержит хотя бы одну запись для каждого продукта, в противном случае вам нужно будет добавить UNION ALL с нулями для остальных продуктов (что может значительно замедлить работу).

Решение с предварительно рассчитанными агрегатами становится хорошей идеей, когда первый запрос недостаточно быстр.

1 голос
/ 26 января 2012

Попробуйте это:

SELECT 
    p.p_id,
    ,   p.product_info
    ,   AVG(r.score) avg_rating
    ,   COUNT(r.score) num_ratings
FROM 
    products AS p
    inner join ratings AS r on r.p_id = p.p_id
group by
    p.p_id,
    ,   p.product_info
1 голос
/ 26 января 2012
SELECT    products.p_id,
          products.product_info,
          AVG(ratings.score) AS AverageRating,
          COUNT(ratings.score) AS xRatings
FROM products LEFT JOIN ratings ON
          ratings.p_id = products.p_id
GROUP BY products.p_id
1 голос
/ 26 января 2012

Вы можете использовать одно соединение слева, что будет означать только одно сканирование таблицы. С различными выборами woulb подразумевает больше!

SELECT 
    p.p_id,
    p.product_info,
    AVG(r.score) AS avg_rating,
    COUNT(r.score) AS num_ratings
    FROM products AS p
    LEFT JOIN ratings AS r on r.p_id = p.p_id
    GROUP BY p.p_id
1 голос
/ 26 января 2012

Вы можете использовать JOIN вместо этих подзапросов:

SELECT 
    p.p_id,
    p.product_info,
    AVG(r.score) AS avg_rating,
    COUNT(r.p_id) AS num_ratings
FROM products AS p
    LEFT JOIN rating r
        ON r.p_id = p.p_id
GROUP BY p.p_id

или один групповой подзапрос и затем присоединитесь:

SELECT 
    p.p_id,
    p.product_info,
    gr.avg_rating,
    COALESCE(gr.num_ratings, 0) AS num_ratings
FROM products AS p
    LEFT JOIN 
        ( SELECT 
              p_id,
              AVG(score) AS avg_rating,
              COUNT(*) AS num_ratings
          FROM rating
          GROUP BY p_id
        ) AS gr
        ON gr.p_id = p.p_id
1 голос
/ 26 января 2012

Вы можете использовать join.

SELECT 
    p.p_id
    p.product_info
    AVG(s.score) as avg_rating,
    COUNT(s.score) as num_ratings
LEFT JOIN 
    ratings s
ON 
    p.p_id = s.p_id
GROUP BY 
    p.p_id
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...