Ifiemi John-Lewis

Analyst

Junior SQL analyst solving retail data quality, revenue, and customer problems in PostgreSQL

Scores reflect independent, automated grading of simulated business tasks completed on DataSimHQ.

Graded projects
7
Average best score
98/100
Career level
Analyst
Core tool stack
SQL (7)

About

Working through retail scenarios for a fictional company called Meridian Retail, I have built queries that reconcile conflicting customer counts, isolate revenue gaps by order status, and produce clean baseline summaries from transactional tables. Each submission was graded independently on DataSimHQ against verified model answers.

The work has covered aggregation with GROUP BY and ROLLUP, conditional filtering with FILTER WHERE, cents-to-naira unit conversion, and deduplication logic applied before aggregation. Across five graded tasks, the focus has been on writing queries that give stakeholders one defensible number rather than several conflicting ones.

Skills

SQL Aggregation

  • GROUP BY aggregation
  • GROUP BY ROLLUP for subtotals and grand totals
  • COUNT with FILTER WHERE for conditional counts
  • SUM with FILTER WHERE for conditional aggregation
  • CASE WHEN inside SUM for revenue isolation

Query Documentation

  • Inline SQL comment documentation

SQL Data Preparation

  • MIN and MAX with date casting using ::date
  • cents-to-naira unit conversion with division and ROUND
  • Schema-qualified table references
  • Deduplication filtering with boolean flag columns
  • Safe date range filtering with >= and < patterns
  • ORDER BY with NULLS LAST

Retail · SQL

Mapping all order statuses to support a revenue discrepancy investigation

Independent grading: 100/100(verify, opens in a new tab)

Problem

Nura Ibrahim, CFO at Meridian Retail (simulated retailer), flagged a revenue discrepancy and needed a reliable picture of the orders table before any filtering began. The first question was straightforward: what order statuses exist, and how many orders carry each one?

What I did

Before any investigative filtering made sense, a clean status inventory was needed. I queried retail.orders with GROUP BY status and COUNT(*) to count every order against its status value. Sorting by order_count DESC made the output immediately readable from the most common status downward, which is the natural reading order for an executive scanning the result.

Finding

The orders table contains 7 distinct statuses, with delivered dominating the distribution at 14,061 orders.

Outcome

Nura received a complete, ranked status map that confirmed no statuses were missing and gave her a reliable starting point for the revenue investigation.

SELECT
 status,
 COUNT(*) AS order_count
FROM retail.orders
GROUP BY status
ORDER BY order_count DESC;

This query groups every row in retail.orders by its status value and counts them, returning all 7 statuses ranked from most to least common in a single scan.

Next step

A useful next step would be to cross-tabulate status against the order creation year to see whether the mix of cancelled or refunded orders has shifted over the four-year date range.

  • GROUP BY aggregation
  • Schema-qualified table references
  • ORDER BY with NULLS LAST

Retail · SQL

Building a baseline revenue summary across the full orders table

Independent grading: 100/100(verify, opens in a new tab)

Problem

Still supporting Nura Ibrahim's investigation at Meridian Retail, I was asked to produce a single-row baseline: total order count, the full date range of orders, and total revenue in naira. This summary would anchor all subsequent filtering and comparison.

What I did

Four metrics were needed in one statement. I used COUNT(*) for total orders, MIN and MAX on created_at cast to date with ::date to strip unnecessary time components, and SUM(total_cents) divided by 100.0 with ROUND to two decimal places to convert the stored cent values into a clean naira figure. Descriptive aliases were chosen to match the language of the business request exactly, so the output column names were self-explanatory without additional annotation.

Finding

The table holds 23,572 orders spanning 2021-01-12 to 2024-12-31, with total revenue of NGN 2,305,853,142.45.

Outcome

Nura had a verified, single-row baseline to anchor every subsequent filter and comparison in the investigation.

SELECT
 COUNT(*) AS total_orders,
 MIN(created_at)::date AS earliest_order,
 MAX(created_at)::date AS latest_order,
 ROUND(SUM(total_cents) / 100.0, 2) AS total_revenue_naira
FROM retail.orders;

The ::date cast removes the time component from the timestamp columns, and dividing total_cents by 100.0 before rounding converts the stored integer cent values into a readable naira figure.

Next step

Breaking this same summary down by calendar year would show whether revenue growth, order volume, or both are driving the overall total.

  • COUNT with FILTER WHERE for conditional counts
  • MIN and MAX with date casting using ::date
  • cents-to-naira unit conversion with division and ROUND
  • Schema-qualified table references

Retail · SQL

Ranking customer acquisition channels by deduplicated volume

Independent grading: 100/100(verify, opens in a new tab)

Problem

Chidi Okafor, a stakeholder at Meridian Retail (simulated retailer), needed a channel-by-channel customer count to answer expected investor questions about acquisition strategy. The requirement was that duplicate customer records be excluded so the counts reflected real acquired customers.

What I did

The deduplication step was the critical filter: without WHERE is_duplicate = FALSE, channel counts would be inflated by flagged duplicate records. After applying that filter, GROUP BY acquisition_source and COUNT(*) produced the per-channel totals. Sorting by customer_count DESC puts the highest-volume channels at the top, which is the natural order for an investor-facing breakdown. A comment block at the top of the query documents the deduplication logic so any reviewer can understand the intent without running the query.

Finding

Each acquisition channel returned a distinct, deduplicated customer count, ranked from highest to lowest volume, ready for investor presentation.

Outcome

Chidi received a clean, ranked channel breakdown built on deduplicated records, giving him a defensible acquisition story for the investor meeting.

SELECT
 acquisition_source,
 COUNT(*) AS customer_count
FROM retail.customers
WHERE is_duplicate = FALSE
GROUP BY acquisition_source
ORDER BY customer_count DESC;

The WHERE clause removes duplicate-flagged rows before aggregation, so each channel count reflects only distinct acquired customers rather than raw record volume.

Next step

Adding average order value or total revenue per acquisition channel would let Chidi show not just which channels bring the most customers but which bring the most valuable ones.

  • Deduplication filtering with boolean flag columns
  • GROUP BY aggregation
  • Inline SQL comment documentation
  • Schema-qualified table references

Retail · SQL

Decomposing the October revenue gap by order status using rollup aggregation

Independent grading: 97/100(verify, opens in a new tab)

Problem

Nura Ibrahim at Meridian Retail needed to understand the source of a flagged revenue discrepancy for October 2023. The question was precise: show gross revenue across all statuses, net revenue excluding cancelled, returned, and refunded orders, and the gap between them, all in a single result with per-status and grand-total rows.

What I did

GROUP BY ROLLUP(status) produces both the per-status rows and a grand-total row in one pass, so Nura could see both levels of the summary without a UNION ALL. For net revenue, I used the FILTER WHERE aggregate syntax to exclude the three problem statuses directly inside SUM, which is cleaner than wrapping a CASE inside the sum. The revenue gap was isolated with CASE WHEN inside SUM, and the date range used >= and < rather than BETWEEN to avoid cutting off late-night orders on 31 October. Sorting by gross_revenue DESC NULLS LAST keeps the grand-total row from floating to the top.

Finding

Gross revenue for October 2023 was NGN 56,876,792.48, net revenue after excluding cancelled, returned, and refunded orders was NGN 38,450,382.35, and the gap was NGN 18,426,410.13, landing precisely on Nura's flagged figure.

Outcome

Nura received a single query result showing both per-status revenue and the overall rollup, confirming the exact source and size of the discrepancy she had flagged.

SELECT
 SUM(total_cents) / 100.0 AS gross_revenue,
 COALESCE(SUM(total_cents) FILTER (
 WHERE status NOT IN ('cancelled', 'returned', 'refunded')
 ) / 100.0) AS net_revenue,
 SUM(CASE WHEN status IN ('cancelled', 'returned', 'refunded')
 THEN total_cents ELSE 0 END) / 100.0 AS revenue_gap
FROM retail.orders
WHERE created_at >= '2023-10-01'
 AND created_at < '2023-11-01'
GROUP BY ROLLUP(status)
ORDER BY gross_revenue DESC NULLS LAST;

The FILTER WHERE clause computes net revenue conditionally inside the SUM without a subquery, and ROLLUP(status) generates the grand-total row alongside per-status rows in a single aggregation pass.

Next step

Comparing this October breakdown against the same status split for September and November would show whether the gap is seasonal or specific to that month.

  • GROUP BY ROLLUP for subtotals and grand totals
  • SUM with FILTER WHERE for conditional aggregation
  • CASE WHEN inside SUM for revenue isolation
  • Safe date range filtering with >= and < patterns
  • cents-to-naira unit conversion with division and ROUND
  • ORDER BY with NULLS LAST

Retail · SQL

Reconciling three conflicting customer counts to produce a single defensible investor figure

Independent grading: 97/100(verify, opens in a new tab)

Problem

Chidi Okafor at Meridian Retail (simulated retailer) was walking into an investor meeting with three different customer counts in circulation: 2,331, 2,131, and 2,200, and needed one number he could defend. The task was to explain where each count came from and identify which was correct.

What I did

Rather than writing three separate queries, I used COUNT(*) FILTER WHERE to compute all five headline figures in a single table scan: total rows, non-duplicate customers, non-duplicates with a confirmed email, non-duplicates with at least one order, and non-duplicates with zero orders. A detailed comment block above the SQL narrates the business logic behind each figure so Chidi could read the explanation before the numbers. For the unresolvable 2,200 figure, the honest response was to flag it as outside the available filters and recommend Chidi go back to the originating team rather than speculate.

Finding

The defensible investor count is 2,131 deduplicated customers, 2,331 includes 200 duplicate records, and 2,105 is the active subset with at least one order, leaving 26 non-duplicate customers with no orders.

Outcome

Chidi received a single query with a plain-language explanation of all three counts and a clear recommendation on which figure to use, along with a flagged action item to resolve the unexplained 2,200.

SELECT
 COUNT(*) AS total_rows,
 COUNT(*) FILTER (
 WHERE is_duplicate = FALSE
 ) AS non_duplicate_customers,
 COUNT(*) FILTER (
 WHERE is_duplicate = FALSE
 AND total_orders > 0
 ) AS active_non_duplicates,
 COUNT(*) FILTER (
 WHERE is_duplicate = FALSE
 AND total_orders = 0
 ) AS non_duplicates_with_zero_orders
FROM retail.customers;

Multiple FILTER WHERE clauses on a single COUNT allow all five customer segments to be computed in one scan, so each figure shares the same source row set and the differences between them are directly comparable.

Next step

Pulling the registration dates for the 26 zero-order non-duplicate customers would show whether they are genuinely inactive or simply very recent acquisitions who have not yet purchased.

  • COUNT with FILTER WHERE for conditional counts
  • Deduplication filtering with boolean flag columns
  • Inline SQL comment documentation
  • Schema-qualified table references