Skip to content

Cleaning a PostgreSQL Database

In this project, you will work with data from a hypothetical Super Store to challenge and enhance your SQL skills in data cleaning. This project will engage you in identifying top categories based on the highest profit margins and detecting missing values, utilizing your comprehensive knowledge of SQL concepts.

Data Dictionary:

orders:

ColumnDefinitionData typeComments
row_idUnique Record IDINTEGER
order_idIdentifier for each order in tableTEXTConnects to order_id in returned_orders table
order_dateDate when order was placedTIMESTAMP
ship_dateDate when order was shippedTIMESTAMP
ship_modeShipping method used for the orderTEXT
customer_idUnique identifier for each customerTEXT
customer_nameName of the customerTEXT
segmentCustomer segmentTEXT
cityCity of the customerTEXT
stateState or province of the customerTEXT
countryCountry of the customerTEXT
postal_codePostal code of the customer's addressTEXT
marketMarket order belongs toTEXT
regionRegion Customer belongs toTEXTConnects to region in people table
product_idIdentifier of Product boughtTEXTConnects to product_id in products table
salesTotal Sales Amount for the Line ItemDOUBLE PRECISION
quantityTotal Quantity for the Line ItemINTEGER
discountDiscount applied for the Line ItemDOUBLE PRECISION
profitTotal Profit earned on the Line ItemDOUBLE PRECISION
shipping_costCost of shipping the orderDOUBLE PRECISION
order_priorityPriority level of the orderTEXT

returned_orders:

ColumnDefinitionData type
returnedYes values for Order / Line Item ReturnedTEXT
order_idIdentifier for each order in tableTEXT
marketMarket order_id belongs toTEXT

people:

ColumnDefinitionData type
personName of Salesperson credited with OrderTEXT
regionRegion Salesperson in operating inTEXT

products:

ColumnDefinitionData type
product_idUnique Identifier for the ProductTEXT
categoryCategory Product belongs toTEXT
sub_categorySub Category Product belongs toTEXT
product_nameDetailed Name of the ProductTEXT

As you can see in the Data Dictionary above, date fields have been written to the orders table as TEXT and numeric fields like sales, profit, etc. have been written to the orders table as Double Precision. You will need to take care of these types in some of the queries. This project is an excellent opportunity to apply your SQL skills in a practical setting and gain valuable experience in data cleaning and analysis. Good luck, and happy querying!

Spinner
DataFrameas
top_five_products_each_category
variable
WITH top_five_products_each_category AS (
    SELECT 
        p.category AS category,
        p.product_name AS product_name,
        ROUND(SUM(o.sales)::numeric, 2) AS product_total_sales,
        ROUND(SUM(o.profit)::numeric, 2) AS product_total_profit,
        RANK() OVER (PARTITION BY p.category ORDER BY SUM(o.sales) DESC) AS product_rank
    FROM orders o 
    JOIN products p ON p.product_id = o.product_id
    GROUP BY p.category, p.product_name
)
SELECT 
    category,
    product_name,
    product_total_sales,
    product_total_profit,
    product_rank
FROM top_five_products_each_category
WHERE product_rank <= 5
ORDER BY category ASC, product_total_sales DESC;
Spinner
DataFrameas
salesperson_market_sales_details
variable
SELECT ppl.person,
	ord.market,
	CASE WHEN ord.sales >= 0 AND ord.sales < 100	THEN '0-100'
		WHEN ord.sales >= 100 AND ord.sales < 500 THEN '100-500'
		WHEN ord.sales >= 500 THEN '500+'
		END AS sales_bin,
		COUNT(DISTINCT ord.order_id) AS order_counts,
		COUNT(ret.returned) AS orders_returned,
		SUM(ord.sales) AS total_sales,
		SUM(CASE WHEN ret.returned IS NULL THEN 0 ELSE ord.sales END) AS returned_sales
FROM orders as ord
INNER JOIN people AS ppl
	ON ord.region = ppl.region
LEFT JOIN returned_orders AS ret
	ON ord.order_id = ret.order_id AND ord.market = ret.market
GROUP BY sales_bin, ord.market, ppl.person
ORDER BY ppl.person, ord.market, sales_bin;
Spinner
DataFrameas
impute_missing_values
variable
-- impute_missing_values
WITH missing AS (
	SELECT product_id,
		discount, 
		market,
		region,
		sales,
		quantity
	FROM orders 
	WHERE quantity IS NULL
), 

unit_prices AS (
	SELECT o.product_id,
	(o.sales / o.quantity)::NUMERIC AS unit_price
FROM orders o
RIGHT JOIN missing AS m 
	ON o.product_id = m.product_id
	AND o.discount = m.discount
WHERE o.quantity IS NOT NULL
)

SELECT DISTINCT m.*,
	ROUND(CAST(m.sales AS NUMERIC) / up.unit_price,0) AS calculated_quantity
FROM missing AS m
INNER JOIN unit_prices AS up
	ON m.product_id = up.product_id;