Проблема SQL "GROUP BY"

Я проектирую корзину покупок. Чтобы обойти проблему старых счетов-фактур, показывающих неточные цены после изменения цены продукта, я переместил поле цены из таблицы Product в таблицу ProductPrice, которая состоит из трех полей, pid, даты и цены. 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), кажется невероятным, что для такого простого запроса звучания потребуется выполнить внутреннее соединение. Мой вопрос в том, является ли мое второе выражение самым простым способом выполнить то, что мне нужно сделать? Или я чего-то не хватает? Спасибо заранее!

Джеймс

Ответ 1

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

Вы не можете решить свою проблему с предложением 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.

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

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

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