Harrison Osiezagha

AnalystLagos, Nigeria

Data analyst working across SQL, Excel, and Power BI to answer operational and financial questions for retail and related businesses.

Looking for: Seeking Data Analyst roles that allow me to apply data analysis, reporting, dashboard development, and business intelligence skills to improve performance, optimize processes, and deliver measurable business value.

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

Graded projects
23
Average best score
92/100
Career level
Analyst
Core tool stack
Excel (11) · SQL (7) · Power BI (5)

About

Working through retail and financial scenarios on a graded simulation platform, I have built SQL queries for revenue baselining, customer deduplication, and acquisition channel breakdowns, as well as Excel validation sheets that reconcile conflicting counts across business units. My submitted work spans beginner to intermediate difficulty, covering multi-condition aggregations, window functions, and COUNTIFS logic that translates raw exports into decision-ready summaries.

These projects have sharpened my habit of making queries self-documenting through clear aliasing and inline conclusions, and of structuring spreadsheet outputs so a non-technical stakeholder can read them without a handover call. Across 23 graded submissions, my average best score is 92 out of 100.

Skills

SQL

  • GROUP BY with COUNT(*) for frequency distribution
  • FILTER clause on COUNT for conditional aggregation
  • Window function SUM(COUNT(*)) OVER () for percentage of total
  • Date casting with ::DATE and MIN/MAX for range extraction
  • Cents-to-currency conversion with ROUND and division
  • Table schema qualification (schema.table syntax)

Excel

  • COUNTA for full row counts in Excel
  • COUNTIF with text-boolean criteria in Excel
  • COUNTIFS with multiple column conditions in Excel
  • SUMIFS with DATE() criteria for period revenue in Excel

Query Design

  • WHERE clause deduplication filtering
  • Self-documenting aliasing and inline result annotation

Retail · SQL

Resolving a three-way customer count conflict with a single self-documenting SQL query

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

Problem

Chidi Okafor at Meridian Retail (simulated retailer) was receiving three different customer totals from three internal teams and needed one defensible number for an upcoming investor meeting. The specific question was which of the figures, 2,331, 2,131, or 2,105, was correct, and why the others differed.

What I did

Using COUNT(*) with conditional FILTER clauses reproduces all three counting methodologies in a single pass over retail.customers, so the relationship between the figures is arithmetically transparent in one result row. The FILTER (WHERE is_duplicate = FALSE) isolates non-duplicate records for the operations count, and adding AND total_orders > 0 to that filter replicates finance's stricter rule. Embedding the recommendation as a string literal in a chidi_recommendation column makes the query self-documenting, the output tells Chidi what number to use and why without a separate write-up.

Finding

The three counts are 2,331 (all records), 2,131 (non-duplicates), and 2,105 (non-duplicates with at least one order placed), with 2,131 identified as the correct figure for the investor context.

Outcome

Chidi received a single query result containing all three counts, a clear label identifying the correct investor-facing figure, and the logical basis for why the other two differ.

SELECT
 COUNT(*) AS marketing_count,
 COUNT(*) FILTER (WHERE is_duplicate = FALSE) AS operations_count,
 COUNT(*) FILTER (
 WHERE is_duplicate = FALSE
 AND total_orders > 0
 ) AS finance_count,
 '2,131 operations_count represents unique, non-duplicate customers.' AS chidi_recommendation
FROM retail.customers;

The FILTER clauses apply row-level conditions to COUNT without affecting the FROM clause, so all three counts are produced from one table scan and remain directly comparable.

Next step

A follow-on query breaking the 26 non-duplicate zero-order customers down by acquisition source and signup date would help Chidi decide whether those customers are recent signups who may still convert or long-inactive accounts that should be removed from forecasts.

  • FILTER clause on COUNT for conditional aggregation
  • WHERE clause deduplication filtering
  • Self-documenting aliasing and inline result annotation
  • GROUP BY with COUNT(*) for frequency distribution

Retail · Excel

Reconciling three conflicting customer counts in Excel for an investor-facing summary

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

Problem

Chidi Okafor at Meridian Retail (simulated retailer) was heading into an investor meeting where three teams were each reporting a different customer total. Marketing, operations, and finance each used a different counting rule, and Chidi needed a single sheet that explained why the numbers differed and which figure to use in which context.

What I did

Each of the three formulas targets a different filtering layer: COUNTA on the full customer column captures every signup regardless of quality; COUNTIF on is_duplicate filters that down to non-duplicated records; COUNTIFS combines the deduplication flag with a total_orders greater than zero condition to match finance's stricter definition. Subtracting 1 from the COUNTA result removes the column header from the row count cleanly. The labels in column D were written to explain not just what each number is but why it is smaller than the one above it, so Chidi could walk an investor through the logic without a separate briefing note.

Finding

The three counts are 2,331 (all signups), 2,131 (deduplicated), and 2,105 (deduplicated customers with at least one order), and the gaps between them are explained by 200 duplicate records and 26 non-duplicate customers who have not yet placed an order.

Outcome

Chidi received a formatted validation sheet with all three counts, their formulas, and plain-language labels explaining the methodology behind each figure.

Next step

Breaking down the 2,131 deduplicated customers by acquisition source would let Chidi show the investor not just how many customers exist but where the highest-quality ones came from.

  • COUNTA for full row counts in Excel
  • COUNTIF with text-boolean criteria in Excel
  • COUNTIFS with multiple column conditions in Excel
  • Self-documenting aliasing and inline result annotation

Retail · SQL

Profiling order status distribution to support a revenue discrepancy investigation

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

Problem

The CFO at Meridian Retail (simulated retailer), Nura Ibrahim, flagged a revenue discrepancy and needed a starting point before any filtering began. The immediate question was: what order statuses exist in the orders table, and how many orders carry each one? This frequency profile would shape every subsequent step of the investigation.

What I did

A simple GROUP BY on the status column paired with COUNT(*) is the cleanest way to answer a frequency question without touching any other table. I aliased the count as order_count to keep the result readable, and added ORDER BY order_count DESC so the dominant status appears first, removing the need for a stakeholder to scan the rows themselves. Qualifying the table as retail.orders makes the query portable and unambiguous regardless of the session search path.

Finding

The table contains 7 distinct statuses: delivered, shipped, cancelled, confirmed, returned, pending, and refunded. The delivered status is the most common, with 14,061 orders.

Outcome

Nura received a complete, sorted status inventory that confirmed no statuses were missing and identified the largest status bucket, giving the investigation a concrete baseline to filter from.

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 how many rows share each one, sorted so the highest-volume status appears first.

Next step

A useful follow-on would be to break each status down by month to see whether the share of cancelled or returned orders has shifted over time, which might point to when the discrepancy started.

  • GROUP BY with COUNT(*) for frequency distribution
  • Table schema qualification (schema.table syntax)
  • Self-documenting aliasing and inline result annotation

Retail · SQL

Building a four-metric baseline summary of the orders table before filtering

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

Problem

Still supporting Nura Ibrahim's revenue investigation at Meridian Retail (simulated retailer), the next requirement was a single-query baseline: total order count, the earliest and latest order dates, and total gross revenue in naira. This summary would anchor all subsequent filtered analyses.

What I did

Combining COUNT(*), MIN, MAX, and SUM into one SELECT means the baseline is produced in a single pass over the table, which also ensures all four metrics are always consistent with each other. Casting the date columns to ::DATE strips the time component and makes the output immediately readable in a report. Revenue was stored in the database as integer cents, so dividing by 100.0 and wrapping the result in ROUND with two decimal places converts it accurately to naira without floating-point drift. Each alias was written to be self-explanatory so the result set needs no column key.

Finding

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

Outcome

Nura received a single clean row of four baseline metrics that could be used as the reference point for every filtered or segmented query in the investigation.

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

The cents-to-naira division uses 100.0 rather than 100 to force a decimal result before ROUND is applied, which avoids integer truncation on the SUM.

Next step

Splitting this same summary by order status would immediately show how much of the NGN 2,305,853,142.45 gross figure sits in delivered versus cancelled or returned orders, which is the next logical step in explaining Nura's discrepancy.

  • Date casting with ::DATE and MIN/MAX for range extraction
  • Cents-to-currency conversion with ROUND and division
  • GROUP BY with COUNT(*) for frequency distribution
  • Self-documenting aliasing and inline result annotation

Retail · SQL

Ranking customer acquisition channels by volume with percentage share for an investor briefing

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

Problem

Chidi Okafor at Meridian Retail (simulated retailer) needed a breakdown of customer volume by acquisition source ahead of an investor meeting focused on acquisition strategy. The output had to exclude duplicate records and be sorted so the highest-volume channels appeared first.

What I did

Filtering with WHERE is_duplicate = FALSE before grouping ensures duplicates are excluded from every count, not just the total. The window function SUM(COUNT(*)) OVER () computes the grand total across all groups within the same query pass, which means the percentage column is calculated without a subquery or a second table reference. Wrapping that ratio in ROUND at two decimal places keeps the output tidy for a presentation context. All 10 acquisition sources appear in the result, and the percentages summing to 100 percent confirms the window logic is complete.

Finding

All 10 acquisition sources were present and accounted for across the 2,131 non-duplicate customers, with each channel's share of total acquisition visible in a single result set.

Outcome

Chidi received an investor-ready channel breakdown that ranked sources by volume and showed each one's percentage contribution, without requiring any manual calculation outside the query.

SELECT
 acquisition_source,
 COUNT(*) AS customer_count,
 ROUND(
 100.0 * COUNT(*) / SUM(COUNT(*)) OVER (),
 2
 ) AS percentage_of_total
FROM retail.customers
WHERE is_duplicate = FALSE
GROUP BY acquisition_source
ORDER BY customer_count DESC;

SUM(COUNT(*)) OVER () is a window function that sums the per-group counts across all rows in the result, giving a denominator for the percentage without a correlated subquery.

Next step

Adding average total_orders or average lifetime spend per acquisition source would show Chidi not just which channels bring the most customers but which bring the most valuable ones.

  • Window function SUM(COUNT(*)) OVER () for percentage of total
  • WHERE clause deduplication filtering
  • GROUP BY with COUNT(*) for frequency distribution
  • Self-documenting aliasing and inline result annotation

Additional projects

Customer Analytics

  • Monthly customer acquisition pivotchart · Excel · 95/100
  • 24 month new customer acquisition trend · SQL · 95/100
  • Acquisition channel performance summary · Excel · 97/100

Dashboard Development

  • October revenue by order status visual · Power BI · 72/100

Data Analysis

  • Meridian data file structure and schema review · Excel · 95/100

Data Modeling

  • Power BI connection to Meridian database · Power BI · 100/100

Data Quality

  • Flagging invalid cash on delivery shipments · Excel · 95/100
  • Identifying impossible shipment timing records · Excel · 92/100
  • Orders table currency conversion in Power Query · Power BI · 100/100
  • Customer dimension deduplication in Power Query · Power BI · 95/100
  • Order items pricing cleanup in Power Query · Power BI · 100/100

Financial Reporting

  • Cash on delivery exposure report for audit committee · Excel · 91/100
  • Q4 2023 revenue breakdown by order status · Excel · 62/100
  • Gross vs net revenue by order status · SQL · 95/100
  • October 2023 revenue reconciliation by status · SQL · 62/100
  • October gross vs net revenue reconciliation · Excel · 97/100

Operations Analytics

  • Hub risk ranking by complaint and delivery rate · Excel · 95/100
  • Complaints breakdown by destination hub · Excel · 95/100