SQL技巧:在行间范围内查找值

时间:2012-04-20 09:35:21

标签: sql range self-reference

我使用的汇率数据库的价值仅在特定日期有效。例如,如果我想将美元兑换成欧元,我必须使用特定日期内的特定汇率。汇率随着时间的推移而变化,最终会被另一个更新的汇率所取代。以下是我的汇率数据库样本:

Exchange_Rate_History
Valid-From  Exchange-rate  From-Currency  To-Currency
2012-04-16  0.8            USD            EUR
2012-04-18  0.82           USD            EUR
2012-04-20  0.81           USD            EUR  

现在,如果您注意到,我只有一个'Valid-From'日期,但我没有'Valid-To'日期。

现在我有另一个需要加入Exchange_Rate_History表的表。此表包含购物交易

Purchases
Transaction-ID  Transaction-Date  Amount-In-USD
1               2012-04-16        100
2               2012-04-17        100

对于上述两笔交易,我们有两个不同的日期,即2012年4月16日和17日。但是对于这两个日期,我们需要使用标记为有效的汇率 - 来自2012-04-16。由于我只有一个约会,我不能使用BETWEEN..AND来执行连接。因此,无法进行以下连接

SELECT 
    * 
FROM
    Exchange_Rate_History
INNER JOIN Purchases ON (Purchases.Transaction-Date BETWEEN Exchange_Rate_History.Valid-From AND Exchange_Rate_History.???)

我正在考虑对Exchange_Rate_History表进行自连接(递归/自引用关系),这样我就会得到两个彼此相邻的Valid-From列。第一个Valid-From将是原始的,而第二个将是垂直移位的。结果表如下所示:

Exchange_Rate_History
Valid-From  Exchange-rate  From-Currency  To-Currency Valid-From-1 (aliased to Valid-To)  
2012-04-16  0.8            USD            EUR         2012-04-18
2012-04-18  0.82           USD            EUR         2012-04-20
2012-04-20  0.81           USD            EUR  

我想使用Valid-From-1字段,就像它是Valid-To字段一样,这样我就可以执行上面的SQL语句了。现在请注意,Valid-From字段的日期为4月16日,而Valid-To的日期为4月18日。但是在这个阶段我不知道如何做垂直“移动”记录的递归关系!

请帮忙吗?这不是一件容易的事!

4 个答案:

答案 0 :(得分:1)

假设每个汇率从valid-from开始有效,到下一个汇率开始时结束, 您想要选择早于交易日期的最新valid-from。那将是当时最新的那一行。

您可以直接获取具有子查询的交易的汇率,如下所示。假设变量@currencyFrom@currencyTo@transactionDate

select top 1 
  exchange-rate 
from Exchange_Rate_History erh
where erh.currency-from = @currencyFrom
  and erh.currency-to = @currencyTo
  and erh.valid-from < @transactionDate
order by erh.valid-from desc

您可以将其放在子查询中,以通过将外部字段名称替换为变量来获取事务的速率。

例如:

Select p.Transaction-ID, p.Transaction-Date, p.Amount-in-USD,
(select top 1 
  exchange-rate 
from Exchange_Rate_History erh
where erh.currency-from = 'USD'
  and erh.currency-to = 'CHF'
  and erh.valid-from < p.Transaction-Date
order by erh.valid-from desc) as exchange-rate,
(select top 1 
  exchange-rate 
from Exchange_Rate_History erh
where erh.currency-from = 'USD'
  and erh.currency-to = 'CHF'
  and erh.valid-from < p.Transaction-Date
order by erh.valid-from desc) * p.Amount-In-USD as Amount-In-CHF


from Purchases p

即使您正在进行两个子查询,您可能会发现它的表现也很好。你可以重写它以避免这种情况,但它可能不值得努力。

无法避免循环连接,但您的聚集索引应该在currency-fromcurrency-tovalid-from上,然后它应该执行正常。如果您无法更改clusttered索引,请在这些字段上创建idex并在索引中包含exchange-rate - 这也将提供良好的性能。

答案 1 :(得分:1)

CREATE FUNCTION GetExchangeRate
(
  @TransactionDate  DATETIME,
  @FromCurrency VARCHAR(3),
  @ToCurrency VARCHAR(3)
 )
RETURNS TABLE
AS
RETURN
    SELECT TOP 1
        Rate
    FROM
        ExchangeRates
    WHERE
        FromCurrency = @fromCurrency
    AND ToCurrency = @toCurrency
    AND ValidFrom <= @transactionDate
    ORDER BY
        ValidFrom DESC

然后:

SELECT
    Purchases.ID,
    Purchases.Transaction-Date,
    Purchases.Amount-In-USD,
    ExchangeRate.Rate,
    Purchases.Amount-In-USD * ExchangeRate.Rate As ConvertedAmount
FROM
    Purchases
    CROSS APPLY dbo.GetExchangeRate(Purchases.Transaction-Date, 'USD', 'EUR') ExchangeRate

答案 2 :(得分:0)

以下情况如何?

SELECT *
    , (SELECT TOP 1 Valid-From
        FROM Exchange_Rate_History ex2
        WHERE ex2.From-Currency = ex1.From-Currency
            AND ex2.To-Currency = ex1.To-Currency
            AND ex2.Valid-From > ex1.Valid-From
        ORDER BY ex2.Valid-From ASC) Valid-To
FROM Exchange_Rate_History ex1

关于如何处理它可能还有很多其他选项,但这是一个简单的子查询方法。

方法2:

使用交叉应用可能会提高效率。

SELECT *
FROM Exchange_Rate_History ex1
    CROSS APPLY (SELECT TOP 1 Valid-From
        FROM Exchange_Rate_History ex2
        WHERE ex2.From-Currency = ex1.From-Currency
            AND ex2.To-Currency = ex1.To-Currency
            AND ex2.Valid-From > ex1.Valid-From
        ORDER BY ex2.Valid-From ASC) Valid-To

答案 3 :(得分:0)

窗口函数可以使用lag()和lead()函数:

DROP SCHEMA tmp CASCADE;

CREATE SCHEMA tmp ;
SET search_path='tmp';

CREATE TABLE exchange
        ( valid_from DATE NOT NULL
        , cur_from CHAR(3)
        , cur_to CHAR(3)
        , rate FLOAT
        ,PRIMARY KEY(cur_from,cur_to,valid_from)
        );
INSERT INTO exchange(valid_from,rate,cur_from,cur_to) VALUES
  ('2012-04-16',  0.8,        'USD', 'EUR' )
, ('2012-04-18',  0.82,       'USD', 'EUR' )
, ('2012-04-20',  0.81,       'USD', 'EUR' )
, ('2012-04-16',  800,        'USD', 'YEN' )
, ('2012-04-18',  820,       'USD', 'YEN' )
, ('2012-04-20',  810,       'USD', 'YEN' )
        ;

WITH next AS (
        SELECT valid_from
        , lead (valid_from) OVER nxt AS valid_to
        , cur_from
        , cur_to
        , rate
        FROM exchange
        WINDOW nxt AS (
           PARTITION BY cur_from,cur_to
           ORDER BY cur_from,cur_to,valid_from)
           )
SELECT * from next
        ;

结果:

NOTICE:  drop cascades to table tmp.exchange
DROP SCHEMA
CREATE SCHEMA
SET
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "exchange_pkey" for table "exchange"
CREATE TABLE
INSERT 0 6
 valid_from |  valid_to  | cur_from | cur_to | rate 
------------+------------+----------+--------+------
 2012-04-16 | 2012-04-18 | USD      | EUR    |  0.8
 2012-04-18 | 2012-04-20 | USD      | EUR    | 0.82
 2012-04-20 |            | USD      | EUR    | 0.81
 2012-04-16 | 2012-04-18 | USD      | YEN    |  800
 2012-04-18 | 2012-04-20 | USD      | YEN    |  820
 2012-04-20 |            | USD      | YEN    |  810
(6 rows)

(更新:添加日元和PARTITION BY)