Эффективный способ получить максимальную дату до указанной даты - PullRequest
7 голосов
/ 23 мая 2011

Предположим, у меня есть таблица Transaction и другая таблица Price. Цена содержит цены на данные средства на разные даты. Каждый фонд будет иметь цены, добавленные на разные даты, но они не будут иметь цены на все возможные даты. Так, для фонда XYZ у меня могут быть цены на 1 мая, 7 мая и 13 мая, а для фонда ABC могут быть цены на 3 мая, 9 мая и 11 мая.

Так что теперь я смотрю на цену, которая была преобладающей для фонда на дату транзакции. Сделка была для фонда XYZ 10 мая. То, что я хочу, это последняя известная цена в этот день, которая будет ценой 7 мая.

Вот код:

select d.TransactionID, d.FundCode, d.TransactionDate, v.OfferPrice
from Transaction d
    inner join Price v
        on v.FundCode = d.FundCode
        and v.PriceDate = (
            select max(PriceDate)
            from Price
            where FundCode = v.FundCode
            /* */ and PriceDate < d.TransactionDate 
        )

Это работает, но очень медленно (несколько минут в реальном мире). Если я удаляю строку с ведущим комментарием, запрос выполняется очень быстро (примерно 2 секунды), но затем он использует последнюю цену за фонд, что неверно.

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

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

Ответы [ 3 ]

5 голосов
/ 23 мая 2011

Существует метод для нахождения строк с максимальными или минимальными значениями, который включает в себя СЛЕДУЮЩЕЕ СОЕДИНЕНИЕ к себе, а не более интуитивно понятное, но, возможно, и более дорогое, ВНУТРЕННЕЕ СОЕДИНЕНИЕ к составному списку, производному от себя.

В основном, метод использует этот шаблон:

SELECT t.*
FROM t
  LEFT JOIN t AS t2 ON t.key = t2.key
    AND t2.Value > t.Value  /* ">" is when getting maximums; "<" is for minimums */
WHERE t2.key IS NULL

или его аналог НЕ СУЩЕСТВУЕТ:

SELECT *
FROM t
WHERE NOT EXISTS (
  SELECT *
  FROM t AS t2
  WHERE t.key = t2.key
    AND t2.Value > t.Value  /* same as above applies to ">" here as well */
)

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

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

Итак, вот как может выглядеть результирующий запрос при применении версии метода LEFT JOIN:

SELECT
  d.TransactionID,
  d.FundCode,
  d.TransactionDate,
  v.OfferPrice
FROM Transaction d
  INNER JOIN Price v ON v.FundCode = d.FundCode
  LEFT JOIN Price v2 ON v2.FundCode = v.FundCode  /* this and */
    AND v2.PriceDate > v.PriceDate                /* this are where we are applying
                                                       the above method; */
    AND v2.PriceDate < d.TransactionDate          /* and this is where we are limiting
                                                       the maximum value */
WHERE v2.FundCode IS NULL

А вот аналогичное решение с NOT EXISTS:

SELECT
  d.TransactionID,
  d.FundCode,
  d.TransactionDate,
  v.OfferPrice
FROM Transaction d
  INNER JOIN Price v ON v.FundCode = d.FundCode
  WHERE NOT EXISTS (
    SELECT *
    FROM Price v2
    WHERE v2.FundCode = v.FundCode           /* this and */
      AND v2.PriceDate > v.PriceDate         /* this are where we are applying
                                                the above method; */
      AND v2.PriceDate < d.TransactionDate   /* and this is where we are limiting
                                                the maximum value */
  )
4 голосов
/ 23 мая 2011

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

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

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

;WITH set_gen (TransactionID, OfferPrice, Match_val)
AS
(
    SELECT d.TransactionID, v.OfferPrice, ROW_NUMBER() OVER(PARTITION BY d.TransactionID ORDER BY v.PriceDate ASC) AS Match_val
    FROM Transaction d
        INNER JOIN Price v
            ON v.FundCode = d.FundCode
    WHERE v.PriceDate <= d.TransactionDate
)
SELECT sg.TransactionID, d.FundCode, d.TransactionDate, sg.OfferPrice
FROM Transaction d
    INNER JOIN set_gen sg ON d.TransactionID = sg.TransactionID
WHERE sg.Match_val = 1
0 голосов
/ 23 мая 2011

Индексируются ли pricedate и transactiondate?Если нет, вы выполняете сканирование таблицы, что, вероятно, является причиной узкого места производительности.

...