cswork / libraryOpen practice workspace

Coding questions

Build SQL pivot with lookups and currency conversion

This question evaluates proficiency in data manipulation using SQL or SQL-like Python transforms, covering point-in-time currency conversion, temporal joins for FX lookups, joins to enrich product/customer metadata, aggregation and pivot-style reporting, ranking, and data-quality checks for missing FX.

You are given the following schema and sample data. Use SQL (or Python with SQL-like transforms) to answer the tasks below. Treat amounts as gross revenue. Use the most recent FX rate on or before txn_date to convert to USD. If no rate exists on or before txn_date, the transaction should be flagged as missing FX.

transactions
+--------+------------+-------------+------------+--------+----------+--------+
| txn_id | txn_date | customer_id | product_id | region | currency | amount |
+--------+------------+-------------+------------+--------+----------+--------+
| 1 | 2025-08-28 | C1 | P1 | NA | USD | 1200 |
| 2 | 2025-08-30 | C2 | P2 | EU | EUR | 900 |
| 3 | 2025-07-15 | C3 | P1 | APAC | JPY | 150000 |
| 4 | 2025-06-20 | C1 | P3 | NA | USD | 500 |
| 5 | 2025-08-05 | C3 | P2 | EU | EUR | 700 |
| 6 | 2025-07-31 | C2 | P3 | NA | USD | 300 |
+--------+------------+-------------+------------+--------+----------+--------+
fx_rates (rate_to_usd = USD per 1 unit of currency)
+----------+------------+-------------+
| currency | rate_date | rate_to_usd |
+----------+------------+-------------+
| EUR | 2025-08-29 | 1.10 |
| EUR | 2025-07-31 | 1.12 |
| JPY | 2025-07-10 | 0.0065 |
| JPY | 2025-06-30 | 0.0068 |
+----------+------------+-------------+
products
+------------+--------------+----------+
| product_id | product_name | category |
+------------+--------------+----------+
| P1 | Alpha | SaaS |
| P2 | Beta | Services |
| P3 | Gamma | Hardware |
+------------+--------------+----------+
customers
+-------------+---------+------------+
| customer_id | name | segment |
+-------------+---------+------------+
| C1 | Acme | Enterprise |
| C2 | Globex | Mid-Market |
| C3 | Initech | SMB |
+-------------+---------+------------+

Tasks: A) Write SQL to convert all transactions to USD using the latest rate on or before txn_date (point-in-time join). Output: txn_id, txn_date, customer_id, product_id, region, category, segment, amount_usd, fx_missing_flag. B) Produce a pivot-like monthly report for 2025-06 through 2025-08 with rows = category and columns = region (NA, EU, APAC) plus a Total column, summing amount_usd. Do not hardcode months; derive them from txn_date. C) For August 2025 only, return the top 1 customer by amount_usd within each region. Break ties by: (1) highest single-transaction amount_usd in that month, then (2) customer_id ascending. D) Data-quality check: list any transactions where no FX rate exists on or before txn_date within the prior 30 days for non-USD currencies (these should have fx_missing_flag = 1), and explain in one sentence how you would monitor this in production.

Convert transactions to USD using point-in-time FX rates

Using the tables below, write a SQL query that converts all transactions to USD using the most recent fx_rates record with the same currency whose rate_date is on or before txn_date (a point-in-time join). If no such FX rate exists for a non-USD currency, set amount_usd to NULL and fx_missing_flag to 1; otherwise fx_missing_flag should be 0. Treat USD transactions as having rate_to_usd = 1 and fx_missing_flag = 0. Output columns: txn_id, txn_date, customer_id, product_id, region, category, segment, amount_usd, fx_missing_flag.

Table transactions

columntypeconstraints
txn_idINTPRIMARY KEY, NOT NULL
txn_dateDATENOT NULL
customer_idVARCHAR(10)NOT NULL
product_idVARCHAR(10)NOT NULL
regionVARCHAR(10)NOT NULL
currencyVARCHAR(3)NOT NULL
amountDECIMAL(12,2)NOT NULL

Sample data:

amountcurrencycustomer_idproduct_idregiontxn_datetxn_id
1200USDC1P1NA2025-08-281
900EURC2P2EU2025-08-302
150000JPYC3P1APAC2025-07-153
500USDC1P3NA2025-06-204
700EURC3P2EU2025-08-055
300USDC2P3NA2025-07-316
400GBPC2P1EU2025-08-107

Table fx_rates

columntypeconstraints
currencyVARCHAR(3)NOT NULL
rate_dateDATENOT NULL
rate_to_usdDECIMAL(10,4)NOT NULL

Sample data:

currencyrate_daterate_to_usd
EUR2025-08-291.1
EUR2025-07-311.12
JPY2025-07-100.0065
JPY2025-06-300.0068

Table products

columntypeconstraints
product_idVARCHAR(10)PRIMARY KEY, NOT NULL
product_nameVARCHAR(50)NOT NULL
categoryVARCHAR(50)NOT NULL

Sample data:

categoryproduct_idproduct_name
SaaSP1Alpha
ServicesP2Beta
HardwareP3Gamma

Table customers

columntypeconstraints
customer_idVARCHAR(10)PRIMARY KEY, NOT NULL
nameVARCHAR(50)NOT NULL
segmentVARCHAR(50)NOT NULL

Sample data:

customer_idnamesegment
C1AcmeEnterprise
C2GlobexMid-Market
C3InitechSMB

Expected output

For each transaction, show its details with the product category and customer segment, the USD-equivalent amount using the latest FX rate on or before txn_date, and a flag indicating whether FX was missing (1) or available (0).

amount_usdcategorycustomer_idfx_missing_flagproduct_idregionsegmenttxn_datetxn_id
1200SaaSC10P1NAEnterprise2025-08-281
990ServicesC20P2EUMid-Market2025-08-302
975SaaSC30P1APACSMB2025-07-153
500HardwareC10P3NAEnterprise2025-06-204
784ServicesC30P2EUSMB2025-08-055
300HardwareC20P3NAMid-Market2025-07-316
NULLSaaSC21P1EUMid-Market2025-08-107

Hints

  1. Use a window function over fx_rates joined to transactions to pick the latest rate_date on or before each txn_date.
  2. Treat USD as a special case with an implicit rate_to_usd of 1 and no missing FX.

Monthly category-by-region USD revenue pivot

Using the same conversion logic as in Question 1, produce a monthly report for the period from 2025-06-01 through 2025-08-31 (inclusive). For each month (derived from txn_date) and product category, compute the sum of amount_usd by region and a grand total. The output should have one row per (month, category) and columns: month (formatted as 'YYYY-MM'), category, na_amount_usd, eu_amount_usd, apac_amount_usd, total_amount_usd. Do not hardcode the months; derive them from txn_date, but filter to the 2025-06 to 2025-08 range.

Table transactions

columntypeconstraints
txn_idINTPRIMARY KEY, NOT NULL
txn_dateDATENOT NULL
customer_idVARCHAR(10)NOT NULL
product_idVARCHAR(10)NOT NULL
regionVARCHAR(10)NOT NULL
currencyVARCHAR(3)NOT NULL
amountDECIMAL(12,2)NOT NULL

Sample data:

amountcurrencycustomer_idproduct_idregiontxn_datetxn_id
1200USDC1P1NA2025-08-281
900EURC2P2EU2025-08-302
150000JPYC3P1APAC2025-07-153
500USDC1P3NA2025-06-204
700EURC3P2EU2025-08-055
300USDC2P3NA2025-07-316
400GBPC2P1EU2025-08-107

Table fx_rates

columntypeconstraints
currencyVARCHAR(3)NOT NULL
rate_dateDATENOT NULL
rate_to_usdDECIMAL(10,4)NOT NULL

Sample data:

currencyrate_daterate_to_usd
EUR2025-08-291.1
EUR2025-07-311.12
JPY2025-07-100.0065
JPY2025-06-300.0068

Table products

columntypeconstraints
product_idVARCHAR(10)PRIMARY KEY, NOT NULL
product_nameVARCHAR(50)NOT NULL
categoryVARCHAR(50)NOT NULL

Sample data:

categoryproduct_idproduct_name
SaaSP1Alpha
ServicesP2Beta
HardwareP3Gamma

Table customers

columntypeconstraints
customer_idVARCHAR(10)PRIMARY KEY, NOT NULL
nameVARCHAR(50)NOT NULL
segmentVARCHAR(50)NOT NULL

Sample data:

customer_idnamesegment
C1AcmeEnterprise
C2GlobexMid-Market
C3InitechSMB

Expected output

For each month from 2025-06 to 2025-08 and each product category with activity, show the total USD revenue by region (NA, EU, APAC) and a grand total. Transactions with missing FX (amount_usd NULL) do not contribute to the sums.

apac_amount_usdcategoryeu_amount_usdmonthna_amount_usdtotal_amount_usd
0Hardware02025-06500500
0Hardware02025-07300300
975SaaS02025-070975
0SaaS02025-0812001200
0Services17742025-0801774

Hints

  1. Reuse the USD conversion logic in a CTE, then aggregate by DATE_TRUNC('month', txn_date) and category.
  2. Use conditional aggregation (SUM with CASE) to pivot regions into separate columns.

Top customer by USD revenue per region in August 2025

For August 2025 only (txn_date between 2025-08-01 and 2025-08-31 inclusive), using USD-converted amounts as in Question 1, return the top 1 customer by total amount_usd within each region. Break ties by: (1) highest single-transaction amount_usd in that month, then (2) customer_id ascending. Output columns: region, customer_id, customer_name, total_amount_usd.

Table transactions

columntypeconstraints
txn_idINTPRIMARY KEY, NOT NULL
txn_dateDATENOT NULL
customer_idVARCHAR(10)NOT NULL
product_idVARCHAR(10)NOT NULL
regionVARCHAR(10)NOT NULL
currencyVARCHAR(3)NOT NULL
amountDECIMAL(12,2)NOT NULL

Sample data:

amountcurrencycustomer_idproduct_idregiontxn_datetxn_id
1200USDC1P1NA2025-08-281
900EURC2P2EU2025-08-302
150000JPYC3P1APAC2025-07-153
500USDC1P3NA2025-06-204
700EURC3P2EU2025-08-055
300USDC2P3NA2025-07-316
400GBPC2P1EU2025-08-107

Table fx_rates

columntypeconstraints
currencyVARCHAR(3)NOT NULL
rate_dateDATENOT NULL
rate_to_usdDECIMAL(10,4)NOT NULL

Sample data:

currencyrate_daterate_to_usd
EUR2025-08-291.1
EUR2025-07-311.12
JPY2025-07-100.0065
JPY2025-06-300.0068

Table products

columntypeconstraints
product_idVARCHAR(10)PRIMARY KEY, NOT NULL
product_nameVARCHAR(50)NOT NULL
categoryVARCHAR(50)NOT NULL

Sample data:

categoryproduct_idproduct_name
SaaSP1Alpha
ServicesP2Beta
HardwareP3Gamma

Table customers

columntypeconstraints
customer_idVARCHAR(10)PRIMARY KEY, NOT NULL
nameVARCHAR(50)NOT NULL
segmentVARCHAR(50)NOT NULL

Sample data:

customer_idnamesegment
C1AcmeEnterprise
C2GlobexMid-Market
C3InitechSMB

Expected output

For August 2025, the top customer by USD revenue for NA and EU regions, using tie-breaking rules (total revenue, then largest single transaction, then customer_id). APAC has no August transactions, so it does not appear.

customer_idcustomer_nameregiontotal_amount_usd
C2GlobexEU990
C1AcmeNA1200

Hints

  1. First aggregate August 2025 transactions to get total and max single-transaction USD amounts per (region, customer).
  2. Use ROW_NUMBER() partitioned by region with the specified ORDER BY to enforce the tie-breaking rules and pick the top row per region.

Detect transactions missing recent FX rates

Write a SQL query that lists any transactions where, for a non-USD currency, there is no fx_rates record for the same currency with rate_date between txn_date - 30 days and txn_date (inclusive). These transactions should correspond to rows that would have fx_missing_flag = 1 in Question 1. Output columns: txn_id, txn_date, currency, amount, fx_missing_flag. Also, in one sentence (you may include it as a SQL comment), explain how you would monitor this issue in production.

Table transactions

columntypeconstraints
txn_idINTPRIMARY KEY, NOT NULL
txn_dateDATENOT NULL
customer_idVARCHAR(10)NOT NULL
product_idVARCHAR(10)NOT NULL
regionVARCHAR(10)NOT NULL
currencyVARCHAR(3)NOT NULL
amountDECIMAL(12,2)NOT NULL

Sample data:

amountcurrencycustomer_idproduct_idregiontxn_datetxn_id
1200USDC1P1NA2025-08-281
900EURC2P2EU2025-08-302
150000JPYC3P1APAC2025-07-153
500USDC1P3NA2025-06-204
700EURC3P2EU2025-08-055
300USDC2P3NA2025-07-316
400GBPC2P1EU2025-08-107

Table fx_rates

columntypeconstraints
currencyVARCHAR(3)NOT NULL
rate_dateDATENOT NULL
rate_to_usdDECIMAL(10,4)NOT NULL

Sample data:

currencyrate_daterate_to_usd
EUR2025-08-291.1
EUR2025-07-311.12
JPY2025-07-100.0065
JPY2025-06-300.0068

Table products

columntypeconstraints
product_idVARCHAR(10)PRIMARY KEY, NOT NULL
product_nameVARCHAR(50)NOT NULL
categoryVARCHAR(50)NOT NULL

Sample data:

categoryproduct_idproduct_name
SaaSP1Alpha
ServicesP2Beta
HardwareP3Gamma

Table customers

columntypeconstraints
customer_idVARCHAR(10)PRIMARY KEY, NOT NULL
nameVARCHAR(50)NOT NULL
segmentVARCHAR(50)NOT NULL

Sample data:

customer_idnamesegment
C1AcmeEnterprise
C2GlobexMid-Market
C3InitechSMB

Expected output

All non-USD transactions where there is no FX rate for the same currency with rate_date in the 30 days leading up to and including txn_date. In the sample data, only the GBP transaction on 2025-08-10 is missing any corresponding FX rate.

amountcurrencyfx_missing_flagtxn_datetxn_id
400GBP12025-08-107

Hints

  1. Use a NOT EXISTS or LEFT JOIN/IS NULL pattern from transactions to fx_rates filtered to the 30-day lookback window.
  2. Remember to exclude USD transactions since they do not rely on fx_rates.