Data warehousing and business-driven SQL analysis, from raw, messy tables to answers a team can act on
A data warehouse built in SQL Server on the Olist Brazilian e-commerce dataset (~100k orders, 9 raw source tables) using a Bronze/Silver/Gold medallion architecture. Raw, inconsistent data is cleaned, validated, and modeled into a star schema that powers a 6-page Power BI dashboard. Every non-trivial cleaning decision is documented, not just the final code.
The Olist dataset arrives as relational CSVs with real data-quality problems: a hidden customer duplication issue, impossible order states, timestamps that contradict each other, missing product metadata, and heavily duplicated geolocation records. This project builds a repeatable pipeline that turns that raw data into a clean, query-ready warehouse.
Bronze layer: raw source tables loaded as-is from the CSVs, with no transformation.
Silver layer: cleaned and validated data, including:
customer_id was unique, but ~3,345 repeated customer_unique_id values meant the same person had several records. Kept one row per real customer using ROW_NUMBER() partitioned on customer_unique_idorder_status before treating any as an error, since a canceled order is legitimately never approved. Removed 14 "delivered" orders still missing approval, carrier, and delivery timestamps, which is logically impossibleorder_approved_at on 1,359 orders where carrier pickup preceded approval. Time gaps started at 70+ seconds and climbed, which ruled out a simple logging quirk, so the less reliable field was nulled instead of dropping real orders. Applied the same logic to 23 rows of order_delivered_customer_date'unknown' instead of dropping them, because they carry real sales in order_items and dropping them would silently delete revenueDISTINCT, and deliberately left the multi-city ambiguity for the Gold layerfreight_value = 0 after confirming they were legitimate free-shipping casesGold layer: a star schema exposed as views — 4 dimensions
(dim_customers, dim_sellers, dim_products,
dim_geolocation) and 3 facts (fact_orders,
fact_order_items, fact_payments). The zip-prefix grain problem
is resolved here by picking the most frequent city/state per prefix and averaging
lat/lng, since that is a business decision rather than a cleaning one. Fact joins use
LEFT JOIN so valid rows are not dropped, and fact_orders resolves duplicate
reviews with a ROW_NUMBER() CTE.
The Silver load runs through one stored procedure, silver.load_silver,
with TRY/CATCH error handling and per-table load timing. Gold objects are
views rather than physical tables, so Gold always reflects the latest Silver load with
no separate refresh step. dim_date is intentionally left out of the SQL
layer because Power BI generates its own date table with DAX's CALENDAR().
The repo is organized as seven numbered scripts: database and schemas, bronze import, bronze validation, silver schema, silver load procedure, gold layer, and gold validation (row counts, duplicate checks, and revenue reconciliation), plus an architecture diagram for each layer.
order_purchase_timestamp needing to live on every fact table for date filtering, before building the Power BI model instead of discovering the need halfway throughA SQL analysis of HR data across four related tables, built to simulate real HR analytics scenarios: attrition analysis, employee performance tracking, training effectiveness, and recruitment insights, including data cleaning and standardization along the way.
The project uses four tables: Employees (role, department, start and exit dates, status, performance score, termination type), EmployeeEngagement (engagement, satisfaction, and work-life balance scores), EmployeeTraining (training type, date, and cost), and Recruitment (applicant job title, experience, desired salary, status).
Queries combine the four tables with INNER and LEFT JOINs, then use aggregates
(COUNT, AVG, SUM), GROUP BY /
HAVING, window functions (RANK, ROW_NUMBER),
CTEs, and subqueries to answer each business question.
Data challenges handled:
TerminationType held inconsistent values (for example "Voluntary" vs "Resignation"), which I standardized with CASE statementsA SQL analysis of how academic, behavioral, and social factors relate to students' final grades, moving from basic descriptive queries to risk scoring, segmentation, and ranking.
The data comes from two tables, Students_Math and
Students_Portuguese, with academic features (G1, G2, G3, study time,
failures), behavioral features (absences, going out, alcohol consumption), and support
features (family and school support). The analysis builds in three tiers:
CASE, ranking with RANK and PERCENT_RANK, and running aggregatesFrom these, the project identifies high-risk students and "hidden talent" (low study time but high performance), and evaluates how attendance and lifestyle relate to grades.
These are observations from SQL aggregations and a scoring model, not a statistical or predictive test, which is why a Python predictive model and a Power BI dashboard are the planned next steps.
A SQL Server project that builds a retail sales database, cleans it, explores it, and answers ten business questions on sales, customers, and categories. It was built to strengthen SQL fundamentals: aggregation, filtering, grouping, and window functions.
Data cleaning: checked for NULLs in key columns, removed records where key fields were NULL, verified the total record count and unique customers, and explored the available categories.
Business questions answered:
Built in SQL Server in a dedicated database. Queries use GROUP BY with
aggregates, date filtering, CASE for shift classification, a CTE for the
shift counts, and window functions to rank months within each year.