cswork / libraryOpen practice workspace

Coding questions · Robinhood

Write SQL and Python for transaction analytics

This question evaluates data manipulation competency in SQL and Python, covering normalization of transaction direction, JOINs and CASE logic, window functions for ranking and aggregation, and designing single-pass counting algorithms for streaming-like inputs.

You are given user and transaction data. Part A (SQL): Use a join and window functions to answer the prompts below using the following schema and sample data.

Schema:

Users(user_id INT PRIMARY KEY, name TEXT, country TEXT)
Transactions(transaction_id INT PRIMARY KEY, from_user_id INT, to_user_id INT, amount INT, created_at TIMESTAMP)

Sample tables:

Users
+---------+-------+---------+
| user_id | name | country |
+---------+-------+---------+
| 1 | Alice | US |
| 2 | Bob | US |
| 3 | Chen | CN |
| 4 | Divya | IN |
| 5 | Emma | US |
+---------+-------+---------+
Transactions
+----------------+--------------+------------+--------+---------------------+
| transaction_id | from_user_id | to_user_id | amount | created_at |
+----------------+--------------+------------+--------+---------------------+
| 1001 | 1 | 2 | 50 | 2025-08-30 10:00:00 |
| 1002 | 2 | 1 | -20 | 2025-08-31 09:00:00 |
| 1003 | 3 | 1 | 10 | 2025-09-01 11:00:00 |
| 1004 | 1 | 3 | -5 | 2025-09-02 12:00:00 |
| 1005 | 4 | 5 | 15 | 2025-09-03 08:00:00 |
+----------------+--------------+------------+--------+---------------------+

Interpretation rule: If amount < 0, the actual sender is to_user_id and the actual receiver is from_user_id, and the normalized amount is ABS(amount).

Tasks:

  1. Write a single SQL query that returns, for every row in Transactions, the columns: sender_user_id, receiver_user_id, normalized_amount, created_at, sender_country, receiver_country. Use a join to Users and a CASE expression (or equivalent) to normalize direction based on the sign of amount.
  2. Using window functions, produce a result set with columns (country, user_id, sent_txn_count, country_rank_by_sent) where sent_txn_count is the number of transactions the user sent (after direction normalization) and country_rank_by_sent is the dense rank of the user within their country ordered by sent_txn_count desc, then user_id asc for tie-breaks. Return only the top 3 users per country.

Part B (Python): You receive an iterable of records formatted as (transaction_id: int, pair: str "from_user_id:to_user_id", amount: int). Apply the same direction rule as above (amount < 0 means the transaction direction is reversed). Implement an efficient function that returns two lists: top 5 sender user_ids and top 5 receiver user_ids ranked by their respective transaction counts (descending count, then ascending user_id as a tie-breaker). Aim for O(n) time and O(u) additional space where u is the number of unique users; do not materialize per-transaction expansions beyond what is necessary.

Normalize transaction direction and enrich with sender/receiver countries

You are given two tables: Users and Transactions.

Interpretation rule (direction normalization):

  • If amount >= 0, the sender is from_user_id and the receiver is to_user_id.
  • If amount < 0, the sender is to_user_id and the receiver is from_user_id.
  • normalized_amount is ABS(amount).

Write a single SQL query that returns, for every row in Transactions, exactly these columns:

  • sender_user_id
  • receiver_user_id
  • normalized_amount
  • created_at formatted as YYYY-MM-DD HH24:MI:SS
  • sender_country
  • receiver_country

You must join to Users to get sender_country and receiver_country, and use a CASE expression (or equivalent) to normalize direction based on the sign of amount.

Table Users

columntypeconstraints
user_idINTPRIMARY KEY, NOT NULL
nameVARCHAR(50)NOT NULL
countryVARCHAR(2)NOT NULL

Sample data:

countrynameuser_id
USAlice1
USBob2
CNChen3
INDivya4
USEmma5

Table Transactions

columntypeconstraints
transaction_idINTPRIMARY KEY, NOT NULL
from_user_idINTNOT NULL
to_user_idINTNOT NULL
amountINTNOT NULL
created_atTIMESTAMPNOT NULL

Sample data:

amountcreated_atfrom_user_idto_user_idtransaction_id
502025-08-30 10:00:00121001
-202025-08-31 09:00:00211002
102025-09-01 11:00:00311003
-52025-09-02 12:00:00131004
152025-09-03 08:00:00451005

Expected output

One row per transaction with normalized sender/receiver direction, absolute amount, and each side's country.

created_atnormalized_amountreceiver_countryreceiver_user_idsender_countrysender_user_id
2025-08-30 10:00:0050US2US1
2025-08-31 09:00:0020US2US1
2025-09-01 11:00:0010US1CN3
2025-09-02 12:00:005US1CN3
2025-09-03 08:00:0015US5IN4

Hints

  1. Use CASE WHEN amount < 0 to swap sender and receiver IDs for negative transactions.
  2. Join Users twice: once for the normalized sender and once for the normalized receiver.
  3. Use TO_CHAR(t.created_at, 'YYYY-MM-DD HH24:MI:SS') for the output timestamp.

Top senders per country using window functions after direction normalization

Using the same Users and Transactions tables and the same direction normalization rule:

  • If amount >= 0, sender = from_user_id
  • If amount < 0, sender = to_user_id

Produce a result set with columns:

  • country
  • user_id
  • sent_txn_count (number of transactions the user sent after normalization)
  • country_rank_by_sent

Where country_rank_by_sent is the DENSE_RANK of the user within their country ordered by:

  1. sent_txn_count DESC
  2. user_id ASC (tie-break)

Return only the top 3 users per country (i.e., country_rank_by_sent <= 3).

Table Users

columntypeconstraints
user_idINTPRIMARY KEY, NOT NULL
nameVARCHAR(50)NOT NULL
countryVARCHAR(2)NOT NULL

Sample data:

countrynameuser_id
USAlice1
USBob2
CNChen3
INDivya4
USEmma5

Table Transactions

columntypeconstraints
transaction_idINTPRIMARY KEY, NOT NULL
from_user_idINTNOT NULL
to_user_idINTNOT NULL
amountINTNOT NULL
created_atTIMESTAMPNOT NULL

Sample data:

amountcreated_atfrom_user_idto_user_idtransaction_id
502025-08-30 10:00:00121001
-202025-08-31 09:00:00211002
102025-09-01 11:00:00311003
-52025-09-02 12:00:00131004
152025-09-03 08:00:00451005

Expected output

Top 3 users per country ranked by number of sent transactions (after normalizing direction). Includes users with 0 sends if they fall in the top 3 for their country.

countrycountry_rank_by_sentsent_txn_countuser_id
CN123
IN114
US121
US202
US305

Hints

  1. First normalize the sender_user_id in a CTE, then aggregate counts per user.
  2. Use LEFT JOIN from Users to counts so users with 0 sent transactions can still appear.
  3. Apply DENSE_RANK() partitioned by country and filter to rank <= 3.