SQL "GROUP BY" выпуск - PullRequest
       35

SQL "GROUP BY" выпуск

11 голосов
/ 19 ноября 2009

Я разрабатываю корзину для покупок. Чтобы обойти проблему старых счетов, показывающих неточные цены после изменения цены продукта, я переместил поле цены из таблицы Product в таблицу ProductPrice, которая состоит из 3 полей: pid, date и price. pid и date образуют первичный ключ для таблицы. Вот пример того, как выглядит таблица:

pid    date     price
1      1/1/09   50
1      2/1/09   55
1      3/1/09   54

Используя SELECT и GROUP BY, чтобы найти последнюю цену каждого продукта, я придумал:

SELECT pid, price, max(date) FROM ProductPrice GROUP BY pid

Дата и возвращенный pid были точными. Я получил ровно 1 запись для каждого уникального pid, и дата, которая сопровождала его, была самой последней датой для этого pid. Тем не менее, то, что стало неожиданностью, было возвращение цены. Он возвратил цену первого ряда, соответствующего pid, которая в данном случае была 50.

Переработав свое утверждение, я придумал следующее:

SELECT pp.pid, pp.price, pp.date FROM ProductPrice AS pp
INNER JOIN (
    SELECT pid AS lastPid, max(date) AS lastDate FROM ProductPrice GROUP BY pid
) AS m
ON pp.pid = lastPid AND pp.date = lastDate

Хотя переработанный оператор теперь дает правильную цену (54), кажется невероятным, что для такого простого звучащего запроса для выполнения потребуется внутреннее соединение. Мой вопрос заключается в том, является ли мое второе утверждение самым простым способом сделать то, что мне нужно сделать? Или я что-то здесь упускаю? Заранее спасибо!

Джеймс

Ответы [ 7 ]

9 голосов
/ 19 ноября 2009

Причина, по которой вы получаете произвольную цену, заключается в том, что mysql не может знать, какие столбцы выбрать, если вы GROUP BY что-то. Он знает, что ему нужна цена a и дата a за пид, и может получить самую последнюю дату, как вы запросили с помощью max(date), но решает вернуть цену, наиболее эффективную для его извлечения. - вы не предоставили статистическую функцию для этого столбца (на самом деле ваш первый запрос не является допустимым SQL).

Ваш второй запрос выглядит нормально, но есть более короткая альтернатива:

SELECT pid, price, date
FROM ProductPrice p
WHERE date = (SELECT MAX(date) FROM ProductPrice tmp WHERE tmp.pid = p.pid)

Но если вы часто обращаетесь к последней цене (что, я думаю, вы делаете), я бы порекомендовал добавить старый столбец обратно в исходную таблицу, чтобы сохранить самое новое значение, если у вас есть возможность изменить структуру базы данных снова.

3 голосов
/ 19 ноября 2009

Я думаю, что вы нарушили схему базы данных.

Чтобы обойти проблему старых счетов, показывающих неточные цены после изменения цены продукта, я переместил поле цены из таблицы Product в таблицу ProductPrice, которая состоит из 3 полей: pid, date и price. pid и date образуют первичный ключ для таблицы.

Как вы указали, вам нужно вести историю изменений цен. Но вы можете сохранить текущую цену в таблице продуктов в дополнение к этой новой таблице. Это сделало бы вашу жизнь намного проще (и ваши запросы быстрее).

1 голос
/ 19 ноября 2009

Вы не можете решить вашу проблему с помощью предложения GROUP BY, потому что для каждой группы pid MySQL просто извлечет первый pid, максимальную дату и первую найденную цену (что не то, что вам нужно).

Вы можете использовать подзапрос (который может быть неэффективным):

SELECT pid, date, price
FROM   ProductPrice p1
WHERE  date = ( SELECT MAX(p2.date)
                FROM ProductPrice p2
                WHERE p1.pid = p2.pid)

или вы можете просто присоединиться к столу с самим собой:

SELECT    p1.pid, p1.date, p1.price
FROM      ProductPrice p1
LEFT JOIN ProductPrice p2 ON p1.pid = p2.pid
          AND p1.date < p2.date
WHERE     p2.pid IS NULL

Взгляните на этот раздел документации MySQL.

1 голос
/ 19 ноября 2009

Вы можете попробовать это:

SELECT pid, price, date FROM ProductPrice GROUP BY pid ORDER BY date DESC

Группа имеет некоторые неясные функции, я тоже всегда не уверен, правильное ли это поле ... но оно должно быть первым в наборе результатов.

0 голосов
/ 19 ноября 2009

Я столкнулся с той же проблемой в одном из моих проектов. Я использовал подзапрос, чтобы получить дату, а затем сравнить ее, но это замедляет работу системы при увеличении данных. поэтому лучше хранить последнюю цену в таблице «Продукты» в дополнение к новой таблице, которую вы создали, чтобы вести историю изменений цен.

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

0 голосов
/ 19 ноября 2009

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

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

Кроме того, если у вас есть система выставления счетов, вам действительно следует хранить цену на продукт (и налоговые ставки, а также «коды») вместе со счетом, т.е. таблицы счетов должны содержать все необходимые финансовые данные. информация для воспроизведения счета. В общем, вы не не хотите полагаться на возможность поиска цены (или налоговой ставки) в изменчивой таблице, даже если учесть систему, представленную выше. Независимо от этого у ценовой истории есть свои достоинства.

0 голосов
/ 19 ноября 2009

Вот еще один, возможно, неэффективный:

SELECT pid, substring_index( group_concat( price order by date desc ), ',', 1 ) , max(date)
  FROM ProductPrice
GROUP BY pid
...