2008-09-08 2 views
12

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

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) 

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

+0

@BlaM: К сожалению, идентификатор является идентификатором GUID, а не Int. (Который вы не могли знать в то время). Сожалею. – Marius 2008-09-08 10:41:10

ответ

20

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

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

(Учитывая высокий ID означает также новейшую цену)

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

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

UPDATE

Хорошо, Маркус обновил вопрос, чтобы показать, что ID является UniqueID, не инт. Это делает запись запроса еще более сложной.

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 на дне является излишним. Просто попробуйте, если это быстрее и без этого «где». Не уверен, какая версия будет быстрее после того, как оптимизатор сделал свою работу.)

+0

Запрос «join» намного, намного быстрее, чем тот, в котором есть «select», в котором («join» делает один дополнительный выбор, «где» - один для каждой записи!). Не могли бы вы изменить ваш андерс, чтобы указать это? – skolima 2008-09-10 09:24:25

+0

@skolima: Как бы вы предложили заменить подзапрос на соединение. Я не думаю, что это возможно, потому что мне нужна сводная функция «посередине». – BlaM 2008-09-10 11:35:54

+0

Ну, я не знаю, как полностью избавиться от подзапроса. Однако в вашем «комплексном отклике обновления» подзапрос запускается один раз. В первом запросе подзапрос вызывается столько раз, сколько есть в `Thing`. По крайней мере, на MySQL. – skolima 2008-09-10 18:46:07

2

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

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

Edit вышеперечисленное 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

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

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

2

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

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

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

1

Я конвертирую uniqueidentifier в двоичный файл, чтобы получить MAX. Это должно убедиться, что вы не будете получать дубликаты из нескольких записей с одинаковым ThingIDs и 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

Поскольку ID не последовательный, я предполагаю, что у вас есть уникальный индекс 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) 

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

2

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

Для того, чтобы решить эту проблему, сначала определить таблицу функцию для извлечения Top 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) 

магия здесь делается на СОХРАНИТЬ положение, которое применяет функцию к каждой строке в левом наборе результатов затем присоединяется с результирующим набором, возвращаемой функцией, то Retuns конечного набора результатов ,(Примечание: для выполнения левого соединения, такого как apply, используйте OUTTER APPLY, который возвращает все строки с левой стороны, а CROSS APPLY возвращает только строки, имеющие совпадение в правой части)

BlaM: Поскольку я могу ' t еще комментариев (из-за низких точек повтора) даже не до моих собственных ответов ^^, я отвечу в теле сообщения: - предложение APPLY даже, если оно использует функции с табличными значениями, оно оптимизируется внутри SQL Сервер таким образом, что он не вызывает функцию для каждой строки в левом наборе результатов, а вместо этого берет внутренний sql из функции и преобразует ее в предложение соединения с остальной частью запроса, поэтому производительность эквивалентна или даже лучше (если план выбран непосредственно сервером sql, и дальнейшая оптимизация может быть выполнена), чем производительность запроса с использованием подзапросов), и в моем личном опыте ENCE ОТНОСИТЬСЯ не имеют проблем с производительностью, когда база данных проиндексированы надлежащим образом и статистика до настоящего времени (так же, как обычный запрос с подзапросами ведет себя в таких условиях)

0

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

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 
-1

может быть, я missunderstood в Такс, но что около:

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

0

Он должен работать остроумие hout, используя глобальный столбец 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