Предположим, у вас есть расширенная таблица обменных курсов, которая содержит:
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
Мы можем обсудить детали того, должны ли первые две строки быть объединены, но общая идея состоит в том, что найти обменный курс на данную дату тривиально. Эта структура работает с оператором SQL BETWEEN, который включает в себя концы диапазонов. Часто лучшим форматом для диапазонов является «открытый-закрытый»; первая указанная дата включена, а вторая исключена. Обратите внимание, что существует ограничение на строки данных: (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;
Сложная задача - создать таблицу ExtendedExchangeRate из заданной таблицы ExchangeRate на лету.
Если это вариант, то хорошей идеей будет пересмотреть структуру базовой таблицы ExchangeRate, чтобы она соответствовала таблице ExtendedExchangeRate; Вы решаете беспорядок, когда данные вводятся (один раз в месяц), а не каждый раз, когда необходимо определить обменный курс (много раз в день).
Как создать расширенную таблицу курсов валют? Если ваша система поддерживает добавление или вычитание 1 из значения даты для получения следующего или предыдущего дня (и имеет одну таблицу строк с именем «Dual»), то вариант
на этом будет работать (без использования каких-либо функций 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 является (непересекающийся) 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;
В тестовой СУБД (IBM Informix Dynamic Server 11.50.FC6 в MacOS X 10.6.2) мне удалось преобразовать запрос в представление, но мне пришлось прекратить обманывать типы данных - путем приведения строк в даты :
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;