艰难的T-SQL加入左边?

时间:2011-07-26 20:10:07

标签: sql-server-2005 tsql

我有一张ExchangeRates表,其中包含countryid和exchangerate,这是有效的:

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

请注意,根据日期,可能会有相同的国家/地区具有不同的费率...例如,2011年2月14日欧元的汇率为.7400,现在是.7700 7/20/2011。

我有另一个订单项表,可根据国家/地区列出项目。在此表格中,每个订单项都有与之关联的日期。订单项日期应根据汇率使用相应的日期和国家/地区。因此,如果我在2011年2月16日有一个国家/地区欧元的订单项,则使用上述数据时,应使用2011年2月14日的欧元值而不是7/20/2011的值,因为日期(条件er。 ExchangeRateDate< = erli.LineItemDate)。如果我在表中只有一个项目,但是假设我有一个项目日期为8/1/2011那么这个条件(er.ExchangeRateDate< = erliLineItemDate)将返回多行,因此我的查询将失败。

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)

这个左连接的问题是因为日期是&lt; =行项目日期所以它返回许多记录,我不得不以某种方式这样做但不知道如何。

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 个答案:

答案 0 :(得分:3)

我可能在这里遗漏了一些东西,但据我所知,你的问题的“愚蠢”解决方案是使用A 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 :(得分:1)

如果你可以在你的ExchangeRates表中添加一个名为(类似的东西)的附加列,这会让你的生活变得更轻松

ExchangeRateToDate

单独的流程可以在添加新条目时更新上一个条目。

然后,你可以查询LineItemDate&gt; = ExhangeRateDate和&lt; = ExchangeRateToDate

(处理最后一个,可能是一个null的ExchangeRateToDate,作为特例)。

答案 2 :(得分:1)

我会创建一个内存表,用ExchangeRateDates From&amp;创建一个ExchangeRate表。要。
在此之后剩下要做的就是在您的查询中加入此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

答案 3 :(得分:0)

也许您可以尝试使用table expression进入TOP 1,然后加入表格表达式。那有意义吗?希望这可以帮助。

答案 4 :(得分:0)

这可以通过使用一个或多个CTE来解决。这个早期的SO问题应该有所需的构建块: How can you use SQL to return values for a specified date or closest date < specified date?

请注意,您必须将此修改为您自己的架构,并过滤掉更接近但未来的结果。 我希望这会有所帮助,但如果还不够,那么我相信我可以发布更详细的答案。

答案 5 :(得分:0)

您可以将此作为相关子查询使用,该子查询将为您提供一个表格,其中包含给定日期的最新交换值(在评论中指明):

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)

答案 6 :(得分:0)

如果我不误解你想做什么,你可以使用外部申请来获得最新的汇率。

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