SQL-запрос, чтобы получить последнюю цену - PullRequest
13 голосов
/ 08 сентября 2008

У меня есть таблица, содержащая цены на множество различных «вещей» в таблице MS SQL 2005. Сотни записей на одну вещь в день, и разные вещи получают обновления цен в разное время.

ID uniqueidentifier not null,
ThingID int NOT NULL,
PriceDateTime datetime NOT NULL,
Price decimal(18,4) NOT NULL

Мне нужно узнать последние последние цены на группу вещей. Приведенный ниже запрос работает, но я получаю сотни строк назад, и мне приходится их циклически проходить и извлекать только последнюю для каждого ThingID. Как я могу (например, через GROUP BY) сказать, что я хочу получить последнюю версию для каждого ThingID? Или мне придется использовать подзапросы?

SELECT * 
FROM Thing
WHERE ThingID IN (1,2,3,4,5,6)
  AND PriceDate > cast( convert(varchar(20), getdate(), 106) as DateTime) 

ОБНОВЛЕНИЕ: В попытке скрыть сложность я поместил столбец ID в int. В реальной жизни это GUID (а не последовательный вид). Я обновил таблицу def выше, чтобы использовать uniqueidentifier.

Ответы [ 10 ]

20 голосов
/ 08 сентября 2008

Я думаю, что единственное решение с вашей структурой таблицы - это работать с подзапросом:

SELECT *
   FROM Thing
   WHERE ID IN (SELECT max(ID) FROM Thing 
                   WHERE ThingID IN (1,2,3,4)
                   GROUP BY ThingID)

(с учетом самого высокого идентификатора также означает новейшую цену)

Однако я предлагаю вам добавить столбец «IsCurrent», который равен 0, если это не самая последняя цена, или 1, если это самая последняя. Это добавит возможный риск несогласованности данных, но значительно ускорит весь процесс, когда таблица станет больше (если она находится в индексе). Тогда все, что вам нужно сделать, это ...

SELECT *
   FROM Thing
   WHERE ThingID IN (1,2,3,4)
     AND IsCurrent = 1

UPDATE

Хорошо, Маркус обновил вопрос, чтобы показать, что ID - это uniqueid, а не int. Это делает написание запроса еще более сложным.

SELECT T.* 
   FROM Thing T
   JOIN (SELECT ThingID, max(PriceDateTime)
            WHERE ThingID IN (1,2,3,4)
            GROUP BY ThingID) X ON X.ThingID = T.ThingID 
                                AND X.PriceDateTime = T.PriceDateTime
   WHERE ThingID IN (1,2,3,4)

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

(Я знаю, что ThingID в нижней части является избыточным. Просто попробуйте, если он быстрее с или без "WHERE". Не уверен, какая версия будет быстрее после того, как оптимизатор заработал.)

2 голосов
/ 08 сентября 2008

Поскольку вы используете SQL Server 2005, вы можете использовать новое предложение (CROSS | OUTTER) APPLY. Предложение APPLY позволяет объединить таблицу с табличной функцией.

Чтобы решить эту проблему, сначала определите табличную функцию для извлечения первых n строк из Thing для определенного идентификатора, упорядоченной даты:

CREATE FUNCTION dbo.fn_GetTopThings(@ThingID AS GUID, @n AS INT)
  RETURNS TABLE
AS
RETURN
  SELECT TOP(@n) *
  FROM Things
  WHERE ThingID= @ThingID
  ORDER BY PriceDateTime DESC
GO

, а затем используйте функцию для получения первых 1 записей в запросе:

SELECT *
   FROM Thing t
CROSS APPLY dbo.fn_GetTopThings(t.ThingID, 1)
WHERE t.ThingID IN (1,2,3,4,5,6)

Волшебство здесь выполняется предложением APPLY, которое применяет функцию к каждой строке в левом наборе результатов , затем соединяется с набором результатов, возвращаемым функцией, и затем повторно устанавливает окончательный набор результатов. (Примечание: чтобы сделать левое соединение, например, применить, используйте OUTTER APPLY, который возвращает все строки с левой стороны, в то время как CROSS APPLY возвращает только строки, которые имеют совпадение с правой стороны)

Бламу: Поскольку я пока не могу оставлять комментарии (из-за низкого количества повторений), даже не свои собственные ответы ^^, я отвечу в теле сообщения: - предложение APPLY, даже если оно использует функции с табличными значениями, оно оптимизируется внутри SQL Server таким образом, что он не вызывает функцию для каждой строки в левом наборе результатов, а вместо этого получает внутренний sql из функции и преобразует его в предложение соединения с остальной частью запроса, так что производительность эквивалентна или даже выше (если план выбран правильным сервером SQL Server и возможна дальнейшая оптимизация), чем производительность запроса с использованием подзапросов), и в мой личный опыт У APPLY нет проблем с производительностью, когда база данных правильно проиндексирована и статистика актуальна (как обычный запрос с подзапросами ведет себя в таких условиях)

2 голосов
/ 08 сентября 2008

Если маршрут подзапроса был слишком медленным, я бы рассмотрел обработку ваших обновлений цен как журнал аудита и ведение таблицы ThingPrice - возможно, как триггер в таблице обновлений цен:

ThingID int not null,
UpdateID int not null,
PriceDateTime datetime not null,
Price decimal(18,4) not null

Первичным ключом будет просто ThingID, а «UpdateID» - это «ID» в исходной таблице.

2 голосов
/ 08 сентября 2008

Я хотел бы попробовать что-то вроде следующего подзапроса и забыть об изменении ваших структур данных.

SELECT
 *
FROM
 Thing
WHERE 
 (ThingID, PriceDateTime) IN 
 (SELECT 
   ThingID, 
   max(PriceDateTime ) 
  FROM 
   Thing 
  WHERE 
   ThingID IN (1,2,3,4)
  GROUP BY 
   ThingID
 )

Редактировать Выше приведен ANSI SQL, и теперь я предполагаю, что наличие более одного столбца в подзапросе не работает для T SQL. Мариус, я не могу проверить следующее, но попробую;

SELECT
 p.*
FROM
 Thing p,
 (SELECT ThingID, max(PriceDateTime ) FROM Thing WHERE ThingID IN (1,2,3,4) GROUP BY ThingID) m
WHERE 
 p.ThingId = m.ThingId
 and p.PriceDateTime = m.PriceDateTime

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

1 голос
/ 08 сентября 2008

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

Этот запрос получит все элементы в списке, если они были оценены сегодня. Если вы удалите пункт where для PriceDate, вы получите самую последнюю цену независимо от даты.

SELECT * 
FROM Thing thi
WHERE thi.ThingID IN (1,2,3,4,5,6)
  AND thi.PriceDateTime =
     (SELECT MAX(maxThi.PriceDateTime)
      FROM Thing maxThi
      WHERE maxThi.PriceDateTime >= CAST( CONVERT(varchar(20), GETDATE(), 106) AS DateTime)
        AND maxThi.ThingID = thi.ThingID)

Обратите внимание, что я изменил ">" на "> =", так как вы могли иметь цену прямо в начале дня

1 голос
/ 08 сентября 2008

Я конвертирую уникальный идентификатор в двоичный файл, чтобы получить его МАКС. Это должно гарантировать, что вы не получите дубликаты из нескольких записей с одинаковыми ThingID и PriceDateTimes:

SELECT * FROM Thing WHERE CONVERT(BINARY(16),Thing.ID) IN
(
 SELECT MAX(CONVERT(BINARY(16),Thing.ID))
  FROM Thing
  INNER JOIN
   (SELECT ThingID, MAX(PriceDateTime) LatestPriceDateTime FROM Thing
    WHERE PriceDateTime >= CAST(FLOOR(CAST(GETDATE() AS FLOAT)) AS DATETIME)
    GROUP BY ThingID) LatestPrices
  ON Thing.ThingID = LatestPrices.ThingID
   AND Thing.PriceDateTime = LatestPrices.LatestPriceDateTime
 GROUP BY Thing.ThingID, Thing.PriceDateTime
) AND Thing.ThingID IN (1,2,3,4,5,6)
1 голос
/ 08 сентября 2008

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

Как я уже сказал, в зависимости от вашей модели доступа, это может быть вариант.

0 голосов
/ 24 июня 2013

Он должен работать без использования глобального столбца PK (например, для сложных первичных ключей):

SELECT t1.*, t2.PriceDateTime AS bigger FROM Prices t1 
LEFT JOIN Prices t2 ON t1.ThingID = t2.ThingID AND t1.PriceDateTime < t2.PriceDateTime 
HAVING t2.PriceDateTime IS NULL
0 голосов
/ 12 июня 2013

возможно я не так понял, но как насчет:

SELECT ID, ThingID, max(PriceDateTime), Price FROM Thing GROUP BY ThingID

0 голосов
/ 08 сентября 2008

Попробуйте это (при условии, что вам нужна только последняя цена , а не идентификатор или дата-время этой цены)

SELECT ThingID, (SELECT TOP 1 Price FROM Thing WHERE ThingID = T.ThingID ORDER BY PriceDateTime DESC) Price
FROM Thing T
WHERE ThingID IN (1,2,3,4) AND DATEDIFF(D, PriceDateTime, GETDATE()) = 0
GROUP BY ThingID
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...