cswork / libraryOpen practice workspace

Coding questions · EY

Map sources to functional dataset with SQL

This question evaluates competency in SQL-based data manipulation and data engineering concepts, including handling late-arriving data and deduplication to the latest ingested record, joining and filtering for enrichment and KYC status, and computing exposure metrics such as gross/net notional, limit utilization, and breach flags.

You must produce a functional, consumption‑ready dataset for daily exposure monitoring. Assume “today” = 2025‑09‑01. Use the last 7 calendar days by trade_dt (inclusive), handle late‑arriving data, and deduplicate to the latest ingested record per trade_id.

Sample source tables (ASCII):

Customers
+---------+-----------+------------+---------+
| cust_id | cust_name | kyc_status | country |
+---------+-----------+------------+---------+
| 101 | Alpha LLC | PASS | US |
| 102 | Beta SA | PASS | FR |
| 103 | Gamma AG | REVIEW | DE |
+---------+-----------+------------+---------+
Accounts
+---------+---------+---------+---------------------+
| acct_id | cust_id | product | opened_at |
+---------+---------+---------+---------------------+
| 5001 | 101 | MARGIN | 2023-05-10 09:00:00 |
| 5002 | 102 | CASH | 2024-11-01 10:00:00 |
+---------+---------+---------+---------------------+
Trades
+----------+---------+------------+-------------+---------------+------+----------+---------------------+
| trade_id | acct_id | trade_dt | asset_class | notional_usd | side | status | ingested_at |
+----------+---------+------------+-------------+---------------+------+----------+---------------------+
| T1 | 5001 | 2025-08-26 | EQ | 1,000,000 | BUY | BOOKED | 2025-08-26 12:00:00 |
| T1 | 5001 | 2025-08-26 | EQ | 1,000,000 | BUY | CANCELED | 2025-08-27 08:00:00 |
| T2 | 5001 | 2025-08-28 | FI | 2,500,000 | SELL | BOOKED | 2025-08-28 11:30:00 |
| T3 | 5002 | 2025-08-30 | EQ | 750,000 | BUY | BOOKED | 2025-09-01 02:00:00 |
+----------+---------+------------+-------------+---------------+------+----------+---------------------+
RiskLimits
+---------+----------------+
| acct_id | daily_limit_usd|
+---------+----------------+
| 5001 | 2,000,000 |
| 5002 | 1,000,000 |
+---------+----------------+

Task: Write SQL to build a DailyExposure fact at grain (acct_id, trade_dt) over [2025‑08‑25, 2025‑09‑01]. Requirements:

  • Deduplicate to the latest ingested row per (trade_id) before aggregation.
  • Compute gross_notional (sum abs(notional_usd)), net_notional (BUY positive, SELL negative), limit_utilization = gross_notional / daily_limit_usd, and breach_flag (limit_utilization > 1.0).
  • Exclude trades with status = 'CANCELED'.
  • Include only accounts with Customers.kyc_status = 'PASS'.
  • Make the query idempotent for daily backfills (no double counting on re‑runs). Provide the final SELECT and explain one edge case your SQL intentionally ignores.

Build Daily Exposure Fact with Deduplication and Limit Utilization

You must produce a functional, consumption-ready dataset for daily exposure monitoring. Assume "today" = 2025-06-01. Use the last 7 calendar days by trade_dt (inclusive), i.e. from 2025-05-26 to 2025-06-01.

Task: Write SQL to build a DailyExposure fact at grain (acct_id, trade_dt) over [2025-05-26, 2025-06-01]. Requirements:

  • Deduplicate to the latest ingested row per trade_id before aggregation (use ingested_at to pick the most recent record).
  • Compute gross_notional (sum of ABS(notional_usd)), net_notional (BUY positive, SELL negative), limit_utilization = gross_notional / daily_limit_usd, and breach_flag (1 if limit_utilization > 1.0, else 0).
  • Exclude trades with status = 'CANCELED'.
  • Include only accounts whose customer has Customers.kyc_status = 'PASS'.
  • Make the query idempotent for daily backfills (re-running for the same date range must not double count trades).
  • Base your logic only on the source tables below.

Provide the final SELECT statement and briefly describe one edge case your SQL intentionally ignores (for example, how you treat accounts that do not have a risk limit).

Table Customers

columntypeconstraints
cust_idINTPRIMARY KEY, NOT NULL
cust_nameVARCHAR(100)NOT NULL
kyc_statusVARCHAR(20)NOT NULL
countryVARCHAR(2)NOT NULL

Sample data:

countrycust_idcust_namekyc_status
US101Alpha LLCPASS
FR102Beta SAPASS
DE103Gamma AGREVIEW

Table Accounts

columntypeconstraints
acct_idINTPRIMARY KEY, NOT NULL
cust_idINTNOT NULL
productVARCHAR(20)NOT NULL
opened_atTIMESTAMPNOT NULL

Sample data:

acct_idcust_idopened_atproduct
50011012023-05-10 09:00:00MARGIN
50021022024-11-01 10:00:00CASH
50031032025-01-15 14:30:00MARGIN

Table Trades

columntypeconstraints
trade_idVARCHAR(20)NOT NULL
acct_idINTNOT NULL
trade_dtDATENOT NULL
asset_classVARCHAR(10)NOT NULL
notional_usdDECIMAL(18,2)NOT NULL
sideVARCHAR(4)NOT NULL
statusVARCHAR(20)NOT NULL
ingested_atTIMESTAMPNOT NULL

Sample data:

acct_idasset_classingested_atnotional_usdsidestatustrade_dttrade_id
5001EQ2025-05-26 12:00:001000000BUYBOOKED2025-05-26T1
5001EQ2025-05-27 08:00:001000000BUYCANCELED2025-05-26T1
5001FI2025-05-28 11:30:002500000SELLBOOKED2025-05-28T2
5002EQ2025-06-01 02:00:00750000BUYBOOKED2025-05-30T3
5003EQ2025-05-31 09:00:00600000BUYBOOKED2025-05-31T4
5001EQ2025-05-25 10:00:00500000BUYBOOKED2025-05-25T5
5002EQ2025-06-01 15:00:00300000SELLBOOKED2025-06-01T6

Table RiskLimits

columntypeconstraints
acct_idINTPRIMARY KEY, NOT NULL
daily_limit_usdDECIMAL(18,2)NOT NULL

Sample data:

acct_iddaily_limit_usd
50012000000
50021000000
50031500000

Expected output

DailyExposure fact table at grain (acct_id, trade_dt) for trade_dt between 2025-05-26 and 2025-06-01. Trades are first deduplicated to the latest ingested row per trade_id, then filtered to non-CANCELED trades on accounts whose customers have KYC status PASS, joined to RiskLimits, and aggregated. Metrics: gross_notional (sum of absolute notional_usd), net_notional (BUY positive, SELL negative), limit_utilization (gross_notional / daily_limit_usd), and breach_flag (1 if limit_utilization > 1.0 else 0).

acct_idbreach_flagdaily_limit_usdgross_notionallimit_utilizationnet_notionaltrade_dt
50011200000025000001.25-25000002025-05-28
5002010000007500000.757500002025-05-30
5002010000003000000.3-3000002025-06-01

Hints

  1. First deduplicate Trades using a window function over trade_id ordered by ingested_at DESC, then filter to rn = 1.
  2. After deduplication, join Trades to Accounts, Customers, and RiskLimits, filter out non-PASS KYC and CANCELED trades, then aggregate by acct_id and trade_dt.