cswork / libraryOpen practice workspace

Coding questions · Capital One

Merge seven tables into one clean DataFrame

This question evaluates proficiency with pandas-based data manipulation, including multi-table joins, aggregations, dtype enforcement, denormalization, and verification of row/column integrity within the Data Manipulation (SQL/Python) domain for a Data Scientist role.

Using pandas only (no loops over rows), write a function build_facts(customers, orders, order_items, products, payments, shipments, refunds) -> pd.DataFrame that returns a single denormalized table with exactly these columns in this order: ["order_item_id","order_id","customer_id","order_ts","paid_ts","shipped_ts","product_id","sku","category","qty","unit_price","line_amount","refund_amount","country","status","payment_method","carrier"]. Input tables (columns): customers(customer_id:int, signup_date:datetime, country:str); orders(order_id:int, customer_id:int, order_ts:datetime, status:str); order_items(order_item_id:int, order_id:int, product_id:int, qty:int, unit_price:float); products(product_id:int, sku:str, category:str); payments(payment_id:int, order_id:int, paid_ts:datetime, method:str, amount:float); shipments(shipment_id:int, order_id:int, shipped_ts:datetime, carrier:str, ship_cost:float); refunds(refund_id:int, order_item_id:int, refund_ts:datetime, refund_amount:float, reason:str). Rules: one row per order_item_id; line_amount = qty * unit_price; refund_amount = sum of refund_amount per order_item_id (0.0 if none); paid_ts = max(paid_ts) per order; payment_method = method associated with the largest payment amount for the order (break ties by lexicographically smallest method); shipped_ts = earliest shipped_ts per order (NaT if none); left‑join so every order_item_id in order_items appears exactly once; ensure datetime dtypes for *_ts, numeric dtypes for qty/unit_price/line_amount/refund_amount; output must contain no extra or missing columns and exact lower_snake_case names. Provide brief unit‑test style assertions for row count preservation, sorted column order, and absence of duplicate order_item_id.

Build a denormalized order item fact table

You are given seven PostgreSQL tables describing an e-commerce system: customers, orders, order_items, products, payments, shipments, and refunds. Write a single SQL query that returns a denormalized fact table with exactly these columns in this order:

order_item_id, order_id, customer_id, order_ts, paid_ts, shipped_ts, product_id, sku, category, qty, unit_price, line_amount, refund_amount, country, status, payment_method, carrier

Rules:

  • Return one row per order_item_id; every row from order_items must appear exactly once.
  • line_amount = qty * unit_price.
  • refund_amount = SUM(refund_amount) for that order_item_id; use 0.0 when there are no refunds.
  • paid_ts = the latest paid_ts per order_id; if an order has no payments, paid_ts is NULL.
  • payment_method = the method for the largest payment amount for that order_id; if multiple payments tie for largest amount, choose the lexicographically smallest method. If an order has no payments, payment_method is NULL.
  • shipped_ts = the earliest shipped_ts per order_id; if an order has no shipments, shipped_ts is NULL.
  • carrier = the carrier for that earliest shipment; if multiple shipments tie for earliest timestamp, choose the lexicographically smallest carrier. If an order has no shipments, carrier is NULL.
  • Use left joins for optional payments, shipments, and refunds so order_items are not dropped.
  • Format order_ts, paid_ts, and shipped_ts as YYYY-MM-DD HH24:MI:SS strings.

Return the result ordered by order_item_id.

Table customers

columntypeconstraints
customer_idINTPRIMARY KEY, NOT NULL
signup_dateTIMESTAMPNOT NULL
countryVARCHAR(2)NOT NULL

Sample data:

countrycustomer_idsignup_date
US12025-01-10 09:00:00
CA22025-02-15 10:30:00
UK32025-03-20 14:45:00

Table orders

columntypeconstraints
order_idINTPRIMARY KEY, NOT NULL
customer_idINTNOT NULL
order_tsTIMESTAMPNOT NULL
statusVARCHAR(20)NOT NULL

Sample data:

customer_idorder_idorder_tsstatus
11012025-05-01 10:00:00completed
21022025-05-02 12:00:00pending
11032025-05-03 09:30:00completed
31042025-05-04 15:45:00cancelled

Table order_items

columntypeconstraints
order_item_idINTPRIMARY KEY, NOT NULL
order_idINTNOT NULL
product_idINTNOT NULL
qtyINTNOT NULL
unit_priceDECIMAL(10,2)NOT NULL

Sample data:

order_idorder_item_idproduct_idqtyunit_price
1011001501210
1011002502120
1021003502320
1031004503115
1041005501210

Table products

columntypeconstraints
product_idINTPRIMARY KEY, NOT NULL
skuVARCHAR(50)NOT NULL
categoryVARCHAR(50)NOT NULL

Sample data:

categoryproduct_idsku
electronics501SKU-501
books502SKU-502
toys503SKU-503

Table payments

columntypeconstraints
payment_idINTPRIMARY KEY, NOT NULL
order_idINTNOT NULL
paid_tsTIMESTAMPNOT NULL
methodVARCHAR(30)NOT NULL
amountDECIMAL(10,2)NOT NULL

Sample data:

amountmethodorder_idpaid_tspayment_id
15card1012025-05-01 10:05:009001
15paypal1012025-05-01 10:10:009002
60bank_transfer1022025-05-02 12:30:009003
10card1032025-05-03 09:40:009004
5paypal1032025-05-03 09:45:009005

Table shipments

columntypeconstraints
shipment_idINTPRIMARY KEY, NOT NULL
order_idINTNOT NULL
shipped_tsTIMESTAMPNOT NULL
carrierVARCHAR(30)NOT NULL
ship_costDECIMAL(10,2)NOT NULL

Sample data:

carrierorder_idship_costshipment_idshipped_ts
DHL101580012025-05-01 11:00:00
UPS101380022025-05-02 09:00:00
FedEx102780032025-05-03 14:00:00
UPS104480042025-05-05 10:00:00

Table refunds

columntypeconstraints
refund_idINTPRIMARY KEY, NOT NULL
order_item_idINTNOT NULL
refund_tsTIMESTAMPNOT NULL
refund_amountDECIMAL(10,2)NOT NULL
reasonVARCHAR(100)NOT NULL

Sample data:

order_item_idreasonrefund_amountrefund_idrefund_ts
1001damaged270012025-05-10 13:00:00
1001customer_return170022025-05-11 09:15:00
1003late_delivery570032025-05-12 16:30:00

Expected output

A denormalized fact table with one row per order_item_id combining customer, order, product, payment, shipment, and refund information. For the sample data, there are 5 order items, so the result has 5 rows with the specified columns in the given order.

carriercategorycountrycustomer_idline_amountorder_idorder_item_idorder_tspaid_tspayment_methodproduct_idqtyrefund_amountshipped_tsskustatusunit_price
DHLelectronicsUS12010110012025-05-01 10:00:002025-05-01 10:10:00card501232025-05-01 11:00:00SKU-501completed10
DHLbooksUS12010110022025-05-01 10:00:002025-05-01 10:10:00card502102025-05-01 11:00:00SKU-502completed20
FedExbooksCA26010210032025-05-02 12:00:002025-05-02 12:30:00bank_transfer502352025-05-03 14:00:00SKU-502pending20
NULLtoysUS11510310042025-05-03 09:30:002025-05-03 09:45:00card50310NULLSKU-503completed15
UPSelectronicsUK32010410052025-05-04 15:45:00NULLNULL501202025-05-05 10:00:00SKU-501cancelled10

Hints

  1. Use ROW_NUMBER() to pick the largest payment amount per order with a method tiebreaker, and a window MAX() to retain the latest paid_ts.
  2. Use another ROW_NUMBER() to choose the earliest shipment per order with a carrier tiebreaker.
  3. Start from order_items and left join optional aggregates so missing payments, shipments, or refunds do not remove rows.