Жесткий T-SQL к левому соединению? - PullRequest
6 голосов
/ 27 июля 2011

У меня есть таблица ExchangeRates, в которой указан код страны и что-то подобное:

ExchangeRateID   Country   ToUSD      ExchangeRateDate
1                  Euro     .7400     2/14/2011
2                  JAP      80.1900   2/14/2011
3                  Euro     .7700      7/20/2011

Обратите внимание, что может существовать одна и та же страна с другим курсом, основанным на дате ... так, например, выше, евро было 2,74 на 2/14/2011, а сейчас составляет 0,77700 7/20/2011.

У меня есть еще одна таблица позиций для перечисления элементов в зависимости от страны. В этой таблице каждая позиция имеет дату, связанную с ней. Дата позиции должна использовать соответствующую дату и страну на основе обменного курса. Таким образом, используя вышеуказанные данные, если у меня была позиция со страной Евро от 16.02.2011, следует использовать значение евро для 14.02.2011, а не значение для 20.07.2011 из-за даты (условие er. ExchangeRateDate <= erli.LineItemDate). Это сработало бы, если бы у меня был только один элемент в таблице, но представьте, что у меня была дата элемента строки 01.08.2011, тогда это условие (er.ExchangeRateDate <= erliLineItemDate) вернуло бы несколько строк, следовательно, мой запрос не удался ... </p>

SELECT     
    er.ExchangeRateID, 
    er.CountryID AS Expr1, 
    er.ExchangeRateDate, 
    er.ToUSD, 
    erli.ExpenseReportLineItemID, 
    erli.ExpenseReportID, 
    erli.LineItemDate
FROM         
    dbo.ExpenseReportLineItem AS erli 
LEFT JOIN
    dbo.ExchangeRate AS er 
ON er.CountryID = erli.CountryID 
AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDate), 0) <= DATEADD(d, DATEDIFF(d, 0, 
                      erli.LineItemDate), 0)
WHERE     (erli.ExpenseReportID = 196)

Проблема с этим левым соединением ... заключается в том, что даты <= дата позиции, поэтому она возвращает много записей, я должен был бы как-то это сделать, но не знаю как. </p>

Таблицы LineItem имеют несколько записей, и каждая запись может иметь свой собственный CountryID:

Item            Country      ParentID    LineItemDate
Line Item 1      Euro           1           2/14/2011
Line Item 2      US             1           2/14/2011
Line Item3       Euro           1           2/15/2011

Итак, есть три записи для ParentID (ExpenseReportID) = 1. Итак, я беру эти записи и присоединяюсь к таблице ExchangeRate, где страна в моей таблице позиций = страна таблицы обменного курса (эта часть проста), НО Второе условие, которое я должен выполнить:

  AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDate), 0) <= DATEADD(d, DATEDIFF(d, 0, 
                          erli.LineItemDate), 0)

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

Ответы [ 7 ]

3 голосов
/ 27 июля 2011

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

SELECT *
FROM (
    SELECT     
        er.ExchangeRateID, 
        er.CountryID AS Expr1, 
        er.ExchangeRateDate, 
        er.ToUSD, 
        erli.ExpenseReportLineItemID, 
        erli.ExpenseReportID, 
        erli.LineItemDate,
        ROW_NUMBER() OVER (PARTITION BY ExpenseReportID, ExpenseReportLineItemID ORDER BY ExchangeRateDate DESC) AS ExchangeRateOrderID
    FROM dbo.ExpenseReportLineItem AS erli 
    LEFT JOIN dbo.ExchangeRate AS er 
        ON er.CountryID = erli.CountryID 
            AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDate), 0) 
                <= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
    WHERE (erli.ExpenseReportID = 196)
        --For reasonable performance, it would be VERY nice to put a filter
        -- on how far back the exchange rates can go here:
        --AND er.ExchangeRateDate > DateAdd(Day, -7, GetDate())
) As FullData
WHERE ExchangeRateOrderID = 1

Извините, если я неправильно понял, иначе надеюсь, что это поможет!

1 голос
/ 27 июля 2011

Я бы создал таблицу в памяти, создающую таблицу ExchangeRate с ExchangeRateDates From & To.
Все, что осталось сделать после этого, это присоединиться к этому CTE в вашем запросе вместо таблицы ExchangeRate и добавить условие, в котором указана датаbetween дата от / до.

Оператор SQL

;WITH er AS (
    SELECT  rn = ROW_NUMBER() OVER (PARTITION BY er1.ExchangeRateID ORDER BY er2.ExchangeRateDate DESC)
            , er1.ExchangeRateID
            , er1.Country
            , ExchangeRateDateFrom = ISNULL(DATEADD(d, 1, er2.ExchangeRateDate), 0)
            , ExchangeRateDateTo = er1.ExchangeRateDate
            , er1.ToUSD
    FROM    @ExchangeRate er1
            LEFT OUTER JOIN @ExchangeRate er2
                ON  er1.Country = er2.Country
                    AND er1.ExchangeRateDate >= er2.ExchangeRateDate
                    AND er1.ExchangeRateID > er2.ExchangeRateID     
)
SELECT  er.ExchangeRateID, 
        er.CountryID AS Expr1, 
        er.ExchangeRateDateTo, 
        er.ToUSD, 
        erli.ExpenseReportLineItemID, 
        erli.ExpenseReportID, 
        erli.LineItemDate
FROM    dbo.ExpenseReportLineItem AS erli 
        LEFT JOIN er ON er.CountryID = erli.CountryID 
                        AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDateTo), 0) <= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
                        AND DATEADD(d, DATEDIFF(d, 0, er.ExchangeRateDateFrom), 0) >= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
WHERE   (erli.ExpenseReportID = 196)
        and er.rn = 1

Тестовый скрипт

DECLARE @ExchangeRate TABLE (
    ExchangeRateID INTEGER
    , Country VARCHAR(32)
    , ToUSD FLOAT
    , ExchangeRateDate DATETIME
)   

INSERT INTO @ExchangeRate 
VALUES  (1, 'Euro', 0.7400, '02/14/2011')
        , (2, 'JAP', 80.1900, '02/14/2011')
        , (3, 'Euro', 0.7700, '07/20/2011')     
        , (4, 'Euro', 0.7800, '07/25/2011')     

;WITH er AS (
    SELECT  rn = ROW_NUMBER() OVER (PARTITION BY er1.ExchangeRateID ORDER BY er2.ExchangeRateDate DESC)
            , er1.ExchangeRateID
            , er1.Country
            , ExchangeRateDateFrom = ISNULL(DATEADD(d, 1, er2.ExchangeRateDate), 0)
            , ExchangeRateDateTo = er1.ExchangeRateDate
            , ToUSD = er1.ToUSD
    FROM    @ExchangeRate er1
            LEFT OUTER JOIN @ExchangeRate er2
                ON  er1.Country = er2.Country
                    AND er1.ExchangeRateDate >= er2.ExchangeRateDate
                    AND er1.ExchangeRateID > er2.ExchangeRateID     
)
SELECT  *
FROM    er
WHERE   rn = 1
1 голос
/ 27 июля 2011

Было бы намного проще, если бы вы могли добавить в свою таблицу ExchangeRates дополнительный столбец с именем (что-то вроде)

ExchangeRateToDate

Отдельный процесс мог бы обновить предыдущую запись, когда была добавлена ​​новая.

Затем вы можете просто запросить LineItemDate> = ExhangeRateDate и <= ExchangeRateToDate </p>

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

0 голосов
/ 27 июля 2011

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

select *
from ExpenseReportLineItem erli
  outer apply (select top 1 *
               from ExchangeRates as er1
               where er1.Country = erli.Country and
                     er1.ExchangeRateDate <= erli.LineItemDate 
               order by er1.ExchangeRateDate desc) as er
0 голосов
/ 27 июля 2011

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

SELECT * 
FROM er
    INNER JOIN
    (
        SELECT CountryID, MAX(ExchangeRateDate) AS ExchangeRateDate
        FROM er
        WHERE ExchangeRateDate <= '9/1/2011' 
            -- the above is the date you will need to correlate with the main query...
        GROUP BY Country
    ) iq
    ON iq.Country = er.Country AND er.ExchangeRateDate = iq.ExchangeRateDate

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

SELECT     
    iq2.ExchangeRateID, 
    iq2.CountryID AS Expr1, 
    iq2.ExchangeRateDate, 
    iq2.ToUSD, 
    erli.ExpenseReportLineItemID, 
    erli.ExpenseReportID, 
    erli.LineItemDate
FROM dbo.ExpenseReportLineItem AS erli 
    LEFT JOIN
    (
        SELECT * 
        FROM ExchangeRate er
            INNER JOIN
            (
                SELECT CountryID, MAX(ExchangeRateDate) AS ExchangeRateDate
                FROM ExchangeRate er
                WHERE ExchangeRateDate <= erli.LineItemDate 
                -- the above is where the correlation occurs...
                GROUP BY Country
            ) iq
            ON iq.Country = er.Country AND er.ExchangeRateDate = iq.ExchangeRateDate
    ) iq2
    ON er.CountryID = erli.CountryID 
        AND DATEADD(d, DATEDIFF(d, 0, iq2.ExchangeRateDate), 0) <= DATEADD(d, DATEDIFF(d, 0, erli.LineItemDate), 0)
    WHERE     (erli.ExpenseReportID = 196)
0 голосов
/ 27 июля 2011

Это можно решить с помощью одного или нескольких CTE. Этот ранее SO вопрос должен иметь необходимые строительные блоки: Как использовать SQL для возврата значений за указанную дату или ближайшую дату <указанная дата? </a>

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

0 голосов
/ 27 июля 2011

Возможно, вы можете попытаться использовать табличное выражение , чтобы попасть в TOP 1, а затем присоединиться к табличному выражению.Имеет ли это смысл?Надеюсь это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...