Historical SCD Type 2 Point-in-Time Currency Conversion
1. Analytical Significance & What It Means
Scenario & Frontier TrapJoins financial transactions to Slowly Changing Dimension (SCD Type 2) exchange rate tables matching effective date ranges, correctly handling open-ended NULL upper bounds.
Frontier models routinely fail SCD Type 2 point-in-time join logic in two ways: (1) omitting the open-ended current record condition `OR valid_to IS NULL`, which causes current active records to be silently dropped during INNER JOINs, and (2) using `<=` instead of `<` on the upper boundary, which causes duplicate rows when a transaction occurs exactly on the boundary timestamp of an SCD revision.
Convert each financial transaction in `transactions` to USD using the historically effective rate from `exchange_rates` (SCD Type 2 table). An exchange rate record is effective for a transaction if: 1. The transaction currency matches the rate currency, 2. The transaction timestamp is greater than or equal to `valid_from`, AND 3. The transaction timestamp is strictly less than `valid_to`, OR `valid_to` is NULL (indicating the current, ongoing rate). Required output columns: - txn_id (INT) - currency (VARCHAR) - amount (DECIMAL) - rate_to_usd (DECIMAL) - amount_usd (DECIMAL): Calculated as ROUND(amount * rate_to_usd, 2) Sort the results by txn_id ASC.
2. Underlying Tables, Row Counts & Data Volume
2 Tables (1,000,003 rows • 45.8 MiB)Production-scale fixtures loaded into the isolated DuckDB sandbox with at least 1,000,000 records per table:
rate_id (INTEGER) currency (VARCHAR) rate_to_usd (DECIMAL(10,4)) valid_from (TIMESTAMP) valid_to (TIMESTAMP)| # | rate_id | currency | rate_to_usd | valid_from | valid_to |
|---|---|---|---|---|---|
| 1 | 10 | EUR | 1.05 | 2025-01-01 00:00:00 | 2025-01-15 00:00:00 |
| 2 | 11 | EUR | 1.08 | 2025-01-15 00:00:00 | NULL |
| 3 | 12 | GBP | 1.25 | 2025-01-01 00:00:00 | NULL |
txn_id (INTEGER) currency (VARCHAR) amount (DECIMAL(10,2)) txn_time (TIMESTAMP)| # | txn_id | currency | amount | txn_time |
|---|---|---|---|---|
| 1 | 1 | EUR | 100 | 2025-01-05 12:00:00 |
| 2 | 2 | EUR | 200 | 2025-01-20 15:30:00 |
| 3 | 3 | GBP | 50 | 2025-01-10 09:00:00 |
| 4 | 3 | USD | 55.11 | 2026-03-01 09:30:00.000006 |
| 5 | 4 | EUR | 129.46 | 2026-03-01 09:30:00.000021 |
| 6 | 5 | GBP | 128.53 | 2026-03-01 09:30:00.000041 |
| 7 | 6 | JPY | 176.25 | 2026-03-01 09:30:00.000066 |
| 8 | 7 | CAD | 10.24 | 2026-03-01 09:30:00.000091 |
| 9 | 8 | AUD | 0.01 | 2026-03-01 09:30:00.000116 |
| 10 | 9 | USD | 208.45 | 2026-03-01 09:30:00.000130 |
| 11 | 10 | EUR | 78.61 | 2026-03-01 09:30:00.000144 |
| 12 | 11 | GBP | 0.01 | 2026-03-01 09:30:00.000173 |
| 13 | 12 | JPY | 232.53 | 2026-03-01 09:30:00.000192 |
| 14 | 13 | CAD | 124.88 | 2026-03-01 09:30:00.000201 |
| 15 | 14 | AUD | 72.53 | 2026-03-01 09:30:00.000225 |
3. Graphical Query Execution Plan & Cost Bottlenecks
Physical Operator Tree (DuckDB 2.0 Profiler)Visual flowchart of the physical execution pipeline. Operators with high relative costs (≥ 20% of query runtime or heavy latency) are visually highlighted:
| Operator | Cost % | Latency | Processed Rows | Data Volume | Details / Table | Status |
|---|---|---|---|---|---|---|
| ORDER_BY | 38.7% | 27.24 ms | 333,335 | 10.17 MB | - | 🔥 BOTTLENECK |
| HASH_JOIN | 27.8% | 19.60 ms | 333,335 | 11.44 MB | - | 🔥 BOTTLENECK |
| TABLE_SCAN | 26.0% | 18.30 ms | 333,335 | 11.44 MB | memory.main.transactions | 🔥 BOTTLENECK |
| PROJECTION | 4.6% | 3.21 ms | 333,335 | 13.99 MB | Projections: ['txn_id', 'currency', 'amount', 'r | Normal |
| PROJECTION | 2.4% | 1.71 ms | 333,335 | 10.17 MB | Projections: ['#0', '__internal_compress_string_ | Normal |
| PROJECTION | 0.5% | 0.35 ms | 333,335 | 13.99 MB | Projections: ['#0', '__internal_decompress_strin | Normal |
| TABLE_SCAN | 0.0% | 0.03 ms | 3 | 0.00 MB | memory.main.exchange_rates | Normal |
4. Queries Executed (Candidate vs Golden Ground Truth)
Beautified & Syntax HighlightedSELECT t.txn_id,
t.currency,
t.amount,
r.rate_to_usd,
round(t.amount * r.rate_to_usd, 2) AS amount_usd
FROM transactions AS t
JOIN exchange_rates AS r ON t.currency = r.currency
AND t.txn_time >= r.valid_from
AND (t.txn_time < r.valid_to
OR r.valid_to IS NULL)
ORDER BY t.txn_id ASC;
SELECT t.txn_id,
t.currency,
t.amount,
r.rate_to_usd,
round(t.amount * r.rate_to_usd, 2) AS amount_usd
FROM transactions t
JOIN exchange_rates r ON t.currency = r.currency
AND t.txn_time >= r.valid_from
AND (t.txn_time < r.valid_to
OR r.valid_to IS NULL)
ORDER BY t.txn_id ASC;
CREATE TABLE transactions (txn_id INT, currency VARCHAR, amount decimal(10, 2),
txn_time TIMESTAMP);
CREATE TABLE exchange_rates (rate_id INT, currency VARCHAR, rate_to_usd decimal(10, 4),
valid_from TIMESTAMP, valid_to TIMESTAMP);
INSERT INTO transactions
VALUES (1, 'EUR', 100.00, '2025-01-05 12:00:00'),
(2, 'EUR', 200.00, '2025-01-20 15:30:00'),
(3, 'GBP', 50.00, '2025-01-10 09:00:00');
-- SCD2 rates: EUR changed on Jan 15; GBP has no end date (current ongoing rate)
INSERT INTO exchange_rates
VALUES (10, 'EUR', 1.0500, '2025-01-01 00:00:00', '2025-01-15 00:00:00'),
(11, 'EUR', 1.0800, '2025-01-15 00:00:00', NULL),
(12, 'GBP', 1.2500, '2025-01-01 00:00:00', NULL);
5. Query Results & Expected Ground Truth
Tabular Comparison| # | txn_id | currency | amount | rate_to_usd | amount_usd |
|---|---|---|---|---|---|
| 1 | 1 | EUR | 100 | 1.05 | 105 |
| 2 | 2 | EUR | 200 | 1.08 | 216 |
| 3 | 3 | GBP | 50 | 1.25 | 62.5 |
| 4 | 4 | EUR | 129.46 | 1.08 | 139.82 |
| 5 | 5 | GBP | 128.53 | 1.25 | 160.66 |
| 6 | 10 | EUR | 78.61 | 1.08 | 84.9 |
| 7 | 11 | GBP | 0.01 | 1.25 | 0.01 |
| 8 | 16 | EUR | 19.19 | 1.08 | 20.73 |
| 9 | 17 | GBP | 266.2 | 1.25 | 332.75 |
| 10 | 22 | EUR | 96.4 | 1.08 | 104.11 |
| 11 | 23 | GBP | 187.36 | 1.25 | 234.2 |
| 12 | 28 | EUR | 74.92 | 1.08 | 80.91 |
| 13 | 29 | GBP | 266.55 | 1.25 | 333.19 |
| 14 | 34 | EUR | 117.25 | 1.08 | 126.63 |
| 15 | 35 | GBP | 75.64 | 1.25 | 94.55 |
| 16 | 40 | EUR | 0.01 | 1.08 | 0.01 |
| 17 | 41 | GBP | 5.45 | 1.25 | 6.81 |
| 18 | 46 | EUR | 0.01 | 1.08 | 0.01 |
| 19 | 47 | GBP | 233.62 | 1.25 | 292.03 |
| 20 | 52 | EUR | 101.52 | 1.08 | 109.64 |
| 21 | 53 | GBP | 165.94 | 1.25 | 207.43 |
| 22 | 58 | EUR | 77.66 | 1.08 | 83.87 |
| 23 | 59 | GBP | 157.56 | 1.25 | 196.95 |
| 24 | 64 | EUR | 0.01 | 1.08 | 0.01 |
| 25 | 65 | GBP | 116.7 | 1.25 | 145.88 |
| # | txn_id | currency | amount | rate_to_usd | amount_usd |
|---|---|---|---|---|---|
| 1 | 1 | EUR | 100 | 1.05 | 105 |
| 2 | 2 | EUR | 200 | 1.08 | 216 |
| 3 | 3 | GBP | 50 | 1.25 | 62.5 |
| 4 | 4 | EUR | 129.46 | 1.08 | 139.82 |
| 5 | 5 | GBP | 128.53 | 1.25 | 160.66 |
| 6 | 10 | EUR | 78.61 | 1.08 | 84.9 |
| 7 | 11 | GBP | 0.01 | 1.25 | 0.01 |
| 8 | 16 | EUR | 19.19 | 1.08 | 20.73 |
| 9 | 17 | GBP | 266.2 | 1.25 | 332.75 |
| 10 | 22 | EUR | 96.4 | 1.08 | 104.11 |
| 11 | 23 | GBP | 187.36 | 1.25 | 234.2 |
| 12 | 28 | EUR | 74.92 | 1.08 | 80.91 |
| 13 | 29 | GBP | 266.55 | 1.25 | 333.19 |
| 14 | 34 | EUR | 117.25 | 1.08 | 126.63 |
| 15 | 35 | GBP | 75.64 | 1.25 | 94.55 |
| 16 | 40 | EUR | 0.01 | 1.08 | 0.01 |
| 17 | 41 | GBP | 5.45 | 1.25 | 6.81 |
| 18 | 46 | EUR | 0.01 | 1.08 | 0.01 |
| 19 | 47 | GBP | 233.62 | 1.25 | 292.03 |
| 20 | 52 | EUR | 101.52 | 1.08 | 109.64 |
| 21 | 53 | GBP | 165.94 | 1.25 | 207.43 |
| 22 | 58 | EUR | 77.66 | 1.08 | 83.87 |
| 23 | 59 | GBP | 157.56 | 1.25 | 196.95 |
| 24 | 64 | EUR | 0.01 | 1.08 | 0.01 |
| 25 | 65 | GBP | 116.7 | 1.25 | 145.88 |