以下を含む拡張為替レート テーブルがあるとします。
Start Date End Date Rate
========== ========== =======
0001-01-01 2009-01-31 40.1
2009-02-01 2009-02-28 40.1
2009-03-01 2009-03-31 41.0
2009-04-01 2009-04-30 38.5
2009-05-01 9999-12-31 42.7
最初の 2 つの行を結合するかどうかの詳細については議論できますが、一般的な考え方は、特定の日付の為替レートを見つけるのは簡単だということです。この構造は、範囲の終わりを含む SQL 'BETWEEN' 演算子で機能します。多くの場合、範囲のより適切な形式は「open-closed」です。リストの最初の日付が含まれ、2 番目の日付は除外されます。データ行には制約があることに注意してください。(a) 日付範囲の範囲にギャップがなく、(b) 範囲に重複がありません。これらの制約を強制することは、完全に自明ではありません (丁寧な控えめな表現 - 減数分裂)。
これで、基本的なクエリは自明になり、ケース B はもはや特別なケースではなくなりました。
SELECT T.Date, T.Amount, X.Rate
FROM Transactions AS T JOIN ExtendedExchangeRates AS X
ON T.Date BETWEEN X.StartDate AND X.EndDate;
注意が必要な部分は、指定された ExchangeRate テーブルからその場で ExtendedExchangeRate テーブルを作成することです。それがオプションである場合は、ExtendedExchangeRate テーブルに一致するように基本的な ExchangeRate テーブルの構造を変更することをお勧めします。為替レートを決定する必要があるたびに (1 日に何度も) ではなく、データが入力されたときに (月に 1 回) 面倒なことを解決します。
拡張為替レート テーブルの作成方法 システムが日付値から 1 を加算または減算して翌日または前日を取得することをサポートしている場合 (および「デュアル」と呼ばれる単一行テーブルがある場合)、これのバリエーションが機能します (OLAP 関数を使用しません)。
CREATE TABLE ExchangeRate
(
Date DATE NOT NULL,
Rate DECIMAL(10,5) NOT NULL
);
INSERT INTO ExchangeRate VALUES('2009-02-01', 40.1);
INSERT INTO ExchangeRate VALUES('2009-03-01', 41.0);
INSERT INTO ExchangeRate VALUES('2009-04-01', 38.5);
INSERT INTO ExchangeRate VALUES('2009-05-01', 42.7);
最初の行:
SELECT '0001-01-01' AS StartDate,
(SELECT MIN(Date) - 1 FROM ExchangeRate) AS EndDate,
(SELECT Rate FROM ExchangeRate
WHERE Date = (SELECT MIN(Date) FROM ExchangeRate)) AS Rate
FROM Dual;
結果:
0001-01-01 2009-01-31 40.10000
最後の行:
SELECT (SELECT MAX(Date) FROM ExchangeRate) AS StartDate,
'9999-12-31' AS EndDate,
(SELECT Rate FROM ExchangeRate
WHERE Date = (SELECT MAX(Date) FROM ExchangeRate)) AS Rate
FROM Dual;
結果:
2009-05-01 9999-12-31 42.70000
中段:
SELECT X1.Date AS StartDate,
X2.Date - 1 AS EndDate,
X1.Rate AS Rate
FROM ExchangeRate AS X1 JOIN ExchangeRate AS X2
ON X1.Date < X2.Date
WHERE NOT EXISTS
(SELECT *
FROM ExchangeRate AS X3
WHERE X3.Date > X1.Date AND X3.Date < X2.Date
);
結果:
2009-02-01 2009-02-28 40.10000
2009-03-01 2009-03-31 41.00000
2009-04-01 2009-04-30 38.50000
NOT EXISTS サブクエリはかなり重要であることに注意してください。それがなければ、「中間行」の結果は次のようになります。
2009-02-01 2009-02-28 40.10000
2009-02-01 2009-03-31 40.10000 # Unwanted
2009-02-01 2009-04-30 40.10000 # Unwanted
2009-03-01 2009-03-31 41.00000
2009-03-01 2009-04-30 41.00000 # Unwanted
2009-04-01 2009-04-30 38.50000
テーブルのサイズが大きくなるにつれて、不要な行の数が劇的に増加します (N > 2 行の場合、(N-2) * (N - 3) / 2 個の不要な行があると思います)。
ExtendedExchangeRate の結果は、3 つのクエリの (互いに素な) UNION です。
SELECT DATE '0001-01-01' AS StartDate,
(SELECT MIN(Date) - 1 FROM ExchangeRate) AS EndDate,
(SELECT Rate FROM ExchangeRate
WHERE Date = (SELECT MIN(Date) FROM ExchangeRate)) AS Rate
FROM Dual
UNION
SELECT X1.Date AS StartDate,
X2.Date - 1 AS EndDate,
X1.Rate AS Rate
FROM ExchangeRate AS X1 JOIN ExchangeRate AS X2
ON X1.Date < X2.Date
WHERE NOT EXISTS
(SELECT *
FROM ExchangeRate AS X3
WHERE X3.Date > X1.Date AND X3.Date < X2.Date
)
UNION
SELECT (SELECT MAX(Date) FROM ExchangeRate) AS StartDate,
DATE '9999-12-31' AS EndDate,
(SELECT Rate FROM ExchangeRate
WHERE Date = (SELECT MAX(Date) FROM ExchangeRate)) AS Rate
FROM Dual;
テスト用の DBMS (MacOS X 10.6.2 上の IBM Informix Dynamic Server 11.50.FC6) では、クエリをビューに変換できましたが、文字列を強制的に日付にすることで、データ型の不正行為をやめなければなりませんでした。
CREATE VIEW ExtendedExchangeRate(StartDate, EndDate, Rate) AS
SELECT DATE('0001-01-01') AS StartDate,
(SELECT MIN(Date) - 1 FROM ExchangeRate) AS EndDate,
(SELECT Rate FROM ExchangeRate WHERE Date = (SELECT MIN(Date) FROM ExchangeRate)) AS Rate
FROM Dual
UNION
SELECT X1.Date AS StartDate,
X2.Date - 1 AS EndDate,
X1.Rate AS Rate
FROM ExchangeRate AS X1 JOIN ExchangeRate AS X2
ON X1.Date < X2.Date
WHERE NOT EXISTS
(SELECT *
FROM ExchangeRate AS X3
WHERE X3.Date > X1.Date AND X3.Date < X2.Date
)
UNION
SELECT (SELECT MAX(Date) FROM ExchangeRate) AS StartDate,
DATE('9999-12-31') AS EndDate,
(SELECT Rate FROM ExchangeRate WHERE Date = (SELECT MAX(Date) FROM ExchangeRate)) AS Rate
FROM Dual;