如何使用给定结构的现有汇率表有效地转换SELECT中的价格?

时间:2017-10-05 12:31:50

标签: sql sql-server join greatest-n-per-group

情况:我有汇率表,如下所示:

date_from   cur1 coef1          cur2 coef2
2017-01-01  CZK  27.000000000   EUR  1.000000000
2017-07-03  EUR  1.000000000    CZK  26.150000000
2017-07-03  JPY  100.000000000  CZK  19.500000000
2017-10-05  JPY  1000.0000000   EUR  7.54761885

请注意,有时可以为同一对切换cur1cur2。该表还包含其他货币对。两个系数的原因是手动填充表格(以便人类大脑更容易理解数字 - 请参阅JPY转换)。

然后我有另一张表格,其中包含发票行,其中价格以当地货币表示(即每行都有自己的货币单位靠近价格值)

我需要在发票行表格上执行一些SELECT,并将价格转换为以所选目标货币显示 (比如,欧元中的一切)。如何有效地做到这一点?

我的第一次尝试:我事先了解目标货币。这意味着构建一个易于连接的简化结构的临时表应该更好。让目标货币为欧元。然后,将仅使用上表的子集,切换一些对,并且将两个系数转换为一个速率。目标货币将是固定或隐含的。从上表中,JPY-CZK对不会成为表的一部分:

date_from   cur  rate
2017-01-01  CZK  27.000000000
2017-07-03  CZK  26.150000000
2017-10-05  JPY  0.00754761885

要将行与另一个表连接,我不仅需要date_from,还需要date_to。为了能够在连接条件中使用BETWEEN,我希望将date_to作为下一个时段之前的date_from date_to cur rate 2017-01-01 2017-07-02 CZK 27.000000000 。对于CZK,我需要有一个像:

这样的记录
date_to

请注意下一个date_from的{​​{1}}中的一天假。

但是,我还需要自动添加明确表示的时间间隔之前和之后的日期的一些边界值。我需要这样的东西:

date_from   date_to     cur  rate
1900-01-01  2016-12-31  CZK  27.000000000   <-- guessed rate from the next; fixed start at the year 1900
2017-01-01  2017-07-02  CZK  27.000000000
2017-07-03  3000-01-01  CZK  26.150000000   <-- explicit rate; boundary year to 3000

同样适用于同一临时表中的其他货币...

1900-01-01  2017-10-04  JPY  0.00754761885  <-- rate guessed from the next; fictional date_from
2017-10-05  3000-01-01  JPY  0.00754761885  <-- explicit rate; fictional date_to

如何有效地构建这样的临时表?

您对此问题有任何其他建议吗?

更新:我已将我的解决方案发布到代码审核https://codereview.stackexchange.com/q/177517/16189请查看相关漏洞。

2 个答案:

答案 0 :(得分:1)

我认为你不需要临时表。

首先,您需要获取每张发票中date_from值最高的费率。这只是date_from上的MAX,其中日期的限制小于发票的日期。对于示例,我使用CZK作为转换为的货币:

SELECT 
  invoices.id
  , invoices.cur
  , MAX(date_from) AS current
FROM invoices
JOIN rates
ON rates.cur1 = invoices.cur
AND invoices.date > rates.date_from
AND rates.cur2 = 'CZK'
GROUP BY invoices.id, invoices.cur, invoices.date

由于GROUP BY导致SELECT可用列的限制,我们现在必须再次加入这两个表,然后加入它以获取当前速率:

SELECT 
  invoices.id
  , invoices.cur
  , invoices.amount
  , 'CZK' AS otherCurrency
  , invoices.amount / rates.coef1 * rates.coef2 AS converted
FROM invoices
JOIN
  (SELECT 
    invoices.id
    , invoices.cur
    , MAX(date_from) AS current
  FROM invoices
  JOIN rates
  ON rates.cur1 = invoices.cur
  AND invoices.date > rates.date_from
  AND rates.cur2 = 'CZK'
  GROUP BY invoices.id, invoices.cur, invoices.date) AS current_rate
ON invoices.id = current_rate.id
JOIN rates
ON current_rate.current = rates.date_from
AND rates.cur1 = invoices.cur
AND rates.cur2 = 'CZK'

我准备了一个Tracking Issue for Material Chart Feature Parity来显示SQL的实际效果。

答案 1 :(得分:1)

假设汇率表如下,汇率为您的目标货币:

CREATE TABLE currency_rate (
    currency_id INT NOT NULL,
    update_date DATE NOT NULL,
    rate DECIMAL(18,6) NOT NULL,
    CONSTRAINT PK_currency_rate PRIMARY KEY(currency_id,update_date)
);

您可以使用相关子查询将发票链接到汇率:

SELECT
    i.*,
    cr.rate
FROM
    invoice AS i
    INNER JOIN currency_rate AS cr ON
        cr.currency_id=i.currency_id AND
        cr.update_date=(
            SELECT
                MAX(cr_i.update_date)
            FROM
                currency_rate AS cr_i
            WHERE
                cr_i.currency_id=i.currency_id AND
                cr_i.update_date<=i.invoice_date
        );

如果您确实有很多发票和大量费率,基于临时表的解决方案可能会提高性能。最好衡量哪一个获胜。基于相同的currency_rate表定义:

CREATE TABLE #cr (
    date_from DATETIME,
    date_to DATETIME,
    currency_id INT,
    rate DECIMAL(18,6)
);
CREATE CLUSTERED INDEX IX_tcr_curr_dt ON #cr(currency_id,date_from);

INSERT INTO #cr (
    date_from,
    date_to,
    currency_id,
    rate
)
SELECT
    date_from=ISNULL(DATEADD(DAY,1,LAG(update_date) OVER (PARTITION BY currency_id ORDER BY update_date)), '17530101'),
    date_to=CASE WHEN LEAD(update_date) OVER (PARTITION BY currency_id ORDER BY update_date) IS NULL THEN '99991231' ELSE update_date END,
    currency_id,
    rate
FROM
    currency_rate AS cr;

SELECT
    i.*,
    c.rate
FROM
    invoices AS i
    INNER JOIN #cr AS c ON 
        c.currency_id=i.currency_id AND 
        c.date_from<=i.invoice_date AND 
        c.date_to>=i.invoice_date;

DROP TABLE #cr;