Vuong Minh Toan
This project analyzes ecommerce business performance using Google BigQuery and the AdventureWorks2019 dataset. The analysis is organized across three business domains: Sales, Production, and Purchasing. The objective is to answer business-driven analytical questions with SQL and transform transactional data into meaningful insights for decision-making.
- 📈 Sales trend analysis
- 📊 Year-over-Year (YoY) growth analysis
- 🌍 Territory ranking
- 💸 Seasonal discount cost evaluation
- 👥 Customer retention (cohort analysis)
- 📦 Inventory trend and Month-over-Month (MoM) tracking
- ⚖️ Stock-to-sales efficiency analysis
- 🛒 Pending purchase order monitoring
This project answers the following business questions:
- 📌 What are the quantity of items sold, total sales value, and order quantity by subcategory in the last 12 months?
- 📌 Which subcategories have the highest YoY growth rate, and what are the top 3 performers?
- 📌 Which TerritoryIDs rank in the top 3 by order quantity each year?
- 📌 What is the total discount cost generated by Seasonal Discount for each subcategory?
- 📌 What is the customer retention pattern in 2014 for successfully shipped orders using cohort analysis?
- 📌 What is the monthly stock trend and MoM percentage change for all products in 2011?
- 📌 What is the stock-to-sales ratio by product and month in 2011?
- 📌 How many purchase orders remained pending in 2014, and what was their total value?
This project uses the AdventureWorks2019 dataset, a sample business database containing sales, product, inventory, and purchasing data.
SalesProductionPurchasing
Sales.SalesOrderDetailSales.SalesOrderHeaderSales.SpecialOfferProduction.ProductProduction.ProductSubcategoryProduction.ProductInventoryProduction.WorkOrderPurchasing.PurchaseOrderHeader
ModifiedDate– for time filtering and period extractionOrderQty– quantity of items soldLineTotal– sales valueSalesOrderID– distinct order countCustomerID– customer retention analysisStatus– shipped / pending status filteringTerritoryID– territory ranking
ProductID– product join keyProductSubcategoryID– subcategory join keyName– product or subcategory nameDiscountPct– discount percentageType– promotion typeUnitPrice– used for discount cost calculation
StockedQty– stock quantityPurchaseOrderID– purchase order countTotalDue– purchase order value
The SQL file contains 8 analytical queries mapped to three domains:
- 📊 Queries 1–5: Sales analysis
- 📦 Queries 6–7: Production / inventory analysis
- 🛒 Query 8: Purchasing analysis
JOIN,LEFT JOIN,FULL JOIN- Common Table Expressions (
WITH) - Aggregation:
SUM,COUNT(DISTINCT ...),ROUND - Time functions:
EXTRACT,FORMAT_TIMESTAMP,FORMAT_DATETIME,DATE_SUB - Window functions:
LAG,ROW_NUMBER,DENSE_RANK
Because this repository currently contains the SQL logic rather than executed output files, the insights below describe the intended analytical value of each query:
- 📈 Sales concentration by subcategory helps identify which product groups contribute most to volume, revenue, and orders.
- 🚀 YoY growth analysis highlights the fastest-growing subcategories and emerging opportunities.
- 🌍 Territory ranking reveals the strongest geographic sales areas by yearly order quantity.
- 💸 Seasonal discount cost analysis measures promotional cost exposure by subcategory.
- 👥 Customer retention analysis shows repeat purchase behavior after the first successful shipment month.
- 📦 Stock trend analysis helps detect inventory volatility and unusual monthly changes.
- ⚖️ Stock-to-sales ratio helps evaluate inventory efficiency and detect overstock / understock conditions.
- 🛒 Pending purchase order analysis supports procurement monitoring and supply chain control.
Based on the query objectives, the following recommendations can be derived:
- 🎯 Prioritize high-performing product subcategories with strong sales and growth.
- 🚚 Monitor fast-growth categories to ensure supply readiness.
- 🌍 Review top-performing territories as benchmarks for other regions.
- 💰 Compare seasonal discount cost against sales uplift to assess promotion effectiveness.
- 🤝 Use cohort findings to design customer retention and loyalty programs.
- 📦 Review products with unstable MoM stock movement or abnormal stock-to-sales ratios.
- ⏳ Track pending purchase orders closely to reduce procurement bottlenecks.
This project demonstrates the following skills:
- 🧮 Writing analytical SQL in Google BigQuery
- 🔗 Multi-table data integration across schemas
- 🪟 Window functions and ranking logic
- ⏱️ Time-series analysis: YoY, MoM, and cohort analysis
- 📊 Sales, inventory, and purchasing KPI development
- 🧠 Translating business questions into SQL solutions
- 📌 Business-oriented data interpretation
Purpose: Calculate quantity sold, total sales value, and order count by subcategory in the most recent 12-month period.
select format_datetime('%b %Y', a.ModifiedDate) month
,c.Name
,sum(a.OrderQty) qty_item
,sum(a.LineTotal) total_sales
,count(distinct a.SalesOrderID) order_cnt
FROM `adventureworks2019.Sales.SalesOrderDetail` a
left join `adventureworks2019.Production.Product` b
on a.ProductID = b.ProductID
left join `adventureworks2019.Production.ProductSubcategory` c
on b.ProductSubcategoryID = cast(c.ProductSubcategoryID as string)
where date(a.ModifiedDate) >= (select date_sub(date(max(a.ModifiedDate)), INTERVAL 12 month)
from `adventureworks2019.Sales.SalesOrderDetail` )
group by 1,2
order by 2,1;Purpose: Identify the top 3 subcategories with the highest YoY growth rate based on item quantity.
WITH summary AS (
SELECT
EXTRACT(YEAR FROM a.ModifiedDate) AS year,
c.name,
SUM(a.OrderQty) AS item_count
FROM `adventureworks2019.Sales.SalesOrderDetail` a
LEFT JOIN `adventureworks2019.Production.Product` b
ON a.ProductID = b.ProductID
LEFT JOIN `adventureworks2019.Production.ProductSubcategory` c
ON CAST(b.ProductSubcategoryID AS INT64) = c.ProductSubcategoryID
GROUP BY year, c.name
),
raw_data AS (
SELECT
year,
name,
item_count AS Qty_item,
LAG(item_count) OVER (PARTITION BY name ORDER BY year) AS Prv_qty
FROM summary
),
diff_calc AS (
SELECT
year,
name,
Qty_item,
Prv_qty,
ROUND(Qty_item / Prv_qty - 1, 2) AS qty_diff
FROM raw_data
WHERE Prv_qty IS NOT NULL AND Prv_qty < Qty_item
),
ranked AS (
SELECT
year,
name,
Qty_item,
Prv_qty,
qty_diff,
DENSE_RANK() OVER (PARTITION BY year ORDER BY qty_diff DESC) AS rank
FROM diff_calc
)
SELECT
year,
name,
Qty_item,
Prv_qty,
qty_diff,
rank
FROM ranked
WHERE rank <= 3
ORDER BY year, rank;Purpose: Rank the top 3 territories by total item quantity for every year using dense ranking.
WITH summary AS (
SELECT
EXTRACT(YEAR FROM a.ModifiedDate) AS year,
b.TerritoryID,
SUM(a.OrderQty) AS item_count
FROM `adventureworks2019.Sales.SalesOrderDetail` a
LEFT JOIN `adventureworks2019.Sales.SalesOrderHeader` b
ON a.SalesOrderID = b.SalesOrderID
GROUP BY year, b.TerritoryID
),
ranked AS (
SELECT
year,
TerritoryID,
item_count,
DENSE_RANK() OVER (
PARTITION BY year
ORDER BY item_count DESC
) AS rnk
FROM summary
)
SELECT
year,
TerritoryID,
item_count,
rnk
FROM ranked
WHERE rnk <= 3
ORDER BY year, item_count DESC;Purpose: Calculate the total seasonal discount cost for each product subcategory.
select
FORMAT_TIMESTAMP("%Y", ModifiedDate)
, Name
, sum(disc_cost) as total_cost
from (
select distinct a.ModifiedDate
, c.Name
, d.DiscountPct, d.Type
, a.OrderQty * d.DiscountPct * UnitPrice as disc_cost
from `adventureworks2019.Sales.SalesOrderDetail` a
LEFT JOIN `adventureworks2019.Production.Product` b on a.ProductID = b.ProductID
LEFT JOIN `adventureworks2019.Production.ProductSubcategory` c on cast(b.ProductSubcategoryID as int) = c.ProductSubcategoryID
LEFT JOIN `adventureworks2019.Sales.SpecialOffer` d on a.SpecialOfferID = d.SpecialOfferID
WHERE lower(d.Type) like '%seasonal discount%'
)
group by 1,2;Purpose: Measure customer retention in 2014 based on successfully shipped orders.
with
info as (
select
extract(month from ModifiedDate) as month_no
, extract(year from ModifiedDate) as year_no
, CustomerID
, count(Distinct SalesOrderID) as order_cnt
from `adventureworks2019.Sales.SalesOrderHeader`
where FORMAT_TIMESTAMP("%Y", ModifiedDate) = '2014'
and Status = 5
group by 1,2,3
order by 3,1
),
row_num as (
select
month_no
,year_no
,CustomerID
,order_cnt
, row_number() over (partition by CustomerID order by month_no) as row_numb
from info
),
first_order as (
select
month_no
,year_no
,CustomerID
,order_cnt
from row_num
where row_numb = 1
),
month_gap as (
select
a.CustomerID
, b.month_no as month_join
, a.month_no as month_order
, a.order_cnt
, concat('M - ',a.month_no - b.month_no) as month_diff
from info a
left join first_order b
on a.CustomerID = b.CustomerID
order by 1,3
)
select month_join
, month_diff
, count(distinct CustomerID) as customer_cnt
from month_gap
group by 1,2
order by 1,2;Purpose: Track stock levels by product and calculate monthly percentage change in stock.
WITH stock_data AS (
SELECT
g.Name AS ProductName,
EXTRACT(YEAR FROM s.ModifiedDate) AS Year,
EXTRACT(MONTH FROM s.ModifiedDate) AS Month,
SUM(p.StockedQty) AS Stock_current
FROM adventureworks2019.Production.ProductInventory s
JOIN adventureworks2019.Production.WorkOrder p
ON s.ProductID = p.ProductID
JOIN adventureworks2019.Production.Product g
ON g.ProductID = p.ProductID
WHERE EXTRACT(YEAR FROM s.ModifiedDate) = 2011
GROUP BY g.Name, Year, Month
),
with_prev AS (
SELECT
ProductName,
Year,
Month,
Stock_current,
LAG(Stock_current) OVER (PARTITION BY ProductName ORDER BY Year, Month) AS Stock_prv
FROM stock_data
)
SELECT
ProductName AS `Product Name`,
Month,
Year,
Stock_current,
IFNULL(Stock_prv, 0) AS Stock_prv,
ROUND(
IFNULL((Stock_current - Stock_prv) / NULLIF(Stock_prv, 0) * 100, 0),
1
) AS `%diff`
FROM with_prev
ORDER BY ProductName ASC, Year, Month;Purpose: Compare stock quantity with sales quantity to evaluate inventory efficiency by product and month.
with
sale_info as (
select
extract(month from a.ModifiedDate) as mth
, extract(year from a.ModifiedDate) as yr
, a.ProductId
, b.Name
, sum(a.OrderQty) as sales
from `adventureworks2019.Sales.SalesOrderDetail` a
left join `adventureworks2019.Production.Product` b
on a.ProductID = b.ProductID
where FORMAT_TIMESTAMP("%Y", a.ModifiedDate) = '2011'
group by 1,2,3,4
),
stock_info as (
select
extract(month from ModifiedDate) as mth
, extract(year from ModifiedDate) as yr
, ProductId
, sum(StockedQty) as stock_cnt
from 'adventureworks2019.Production.WorkOrder'
where FORMAT_TIMESTAMP("%Y", ModifiedDate) = '2011'
group by 1,2,3
)
select
a.mth
, a.yr
, a.ProductId
, a.Name
, a.sales
, b.stock_cnt as stock
, round(coalesce(b.stock_cnt,0) / sales,2) as ratio
from sale_info a
full join stock_info b
on a.ProductId = b.ProductId
and a.mth = b.mth
and a.yr = b.yr
order by 1 desc, 7 desc;Purpose: Count pending purchase orders and calculate total order value in 2014.
SELECT
EXTRACT(YEAR FROM ModifiedDate) AS year,
Status,
count(distinct PurchaseOrderID) as order_Cnt,
sum(TotalDue) as value
FROM adventureworks2019.Purchasing.PurchaseOrderHeader
WHERE EXTRACT(YEAR FROM ModifiedDate) = 2014 AND Status = 1
GROUP BY Status, year;This project demonstrates how Google BigQuery SQL can be used to solve practical business problems across multiple operational domains. Together, the queries provide a strong foundation for business monitoring in revenue, customer retention, inventory movement, and purchasing workload.
- 📅 Run these queries regularly as part of a monthly business performance review.
- 📊 Build dashboards in Power BI, Looker Studio, or Tableau for stakeholder reporting.
- 💰 Compare discount cost with revenue uplift to evaluate promotion effectiveness.
- 🔁 Extend cohort analysis to more years for stronger customer lifecycle tracking.
- 📦 Combine stock and sales analysis with demand forecasting for better supply planning.
- AdventureWorks Dataset Dictionary: https://drive.google.com/file/d/1bwwsS3cRJYOg1cvNppc1K_8dQLELN16T/view