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
:
orders
:Column | Definition | Data type | Comments |
---|---|---|---|
row_id | Unique Record ID | INTEGER | |
order_id | Identifier for each order in table | TEXT | Connects to order_id in returned_orders table |
order_date | Date when order was placed | TEXT | |
market | Market order_id belongs to | TEXT | |
region | Region Customer belongs to | TEXT | Connects to region in people table |
product_id | Identifier of Product bought | TEXT | Connects to product_id in products table |
sales | Total Sales Amount for the Line Item | DOUBLE PRECISION | |
quantity | Total Quantity for the Line Item | DOUBLE PRECISION | |
discount | Discount applied for the Line Item | DOUBLE PRECISION | |
profit | Total Profit earned on the Line Item | DOUBLE PRECISION |
returned_orders
:
returned_orders
:Column | Definition | Data type |
---|---|---|
returned | Yes values for Order / Line Item Returned | TEXT |
order_id | Identifier for each order in table | TEXT |
market | Market order_id belongs to | TEXT |
people
:
people
:Column | Definition | Data type |
---|---|---|
person | Name of Salesperson credited with Order | TEXT |
region | Region Salesperson in operating in | TEXT |
products
:
products
:Column | Definition | Data type |
---|---|---|
product_id | Unique Identifier for the Product | TEXT |
category | Category Product belongs to | TEXT |
sub_category | Sub Category Product belongs to | TEXT |
product_name | Detailed Name of the Product | TEXT |
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!
-- top_five_products_each_category
SELECT *
-- Create a subquery in the FROM statement that forms a table depicting the top 5 products per category based on highest total sales
FROM (
SELECT
pr.category,
pr.product_name,
-- The orders table has the sales field
ROUND(SUM(o.sales)::numeric, 2) AS product_total_sales,
-- The orders table has the profit field
ROUND(SUM(o.profit)::numeric, 2) AS product_total_profit,
-- Output should be sorted by category in ascending order and by sales in descending order within each category
RANK() OVER(PARTITION BY pr.category ORDER BY ROUND(SUM(o.sales)::numeric, 2) DESC) AS product_rank
FROM products AS pr
INNER JOIN orders AS o
-- The unique identifier to join tables pr and o
USING(product_id)
GROUP BY pr.category, pr.product_name
-- Aliasing the subquery as ranked_products
) AS ranked_products
-- To find the top 5 per category
WHERE product_rank < 6;
-- impute_missing_values
-- First CTE will generate all rows in the orders table with missing values for the quantity field
WITH missing AS (
SELECT
product_id,
discount,
market,
region,
sales,
quantity
FROM orders
WHERE quantity is NULL
),
-- Second CTE will generate the unit prices for a single quantity of each product
unit_prices AS (
SELECT
orders.product_id,
(orders.sales/orders.quantity)::numeric AS unit_price
FROM orders
RIGHT JOIN missing --
ON orders.product_id = missing.product_id
AND orders.discount = missing.discount
WHERE orders.quantity IS NOT NULL
)
-- Estimate the missing quantity values
SELECT
DISTINCT missing.*,
ROUND((missing.sales/unit_prices.unit_price)::numeric, 0) AS calculated_quantity
FROM missing
INNER JOIN unit_prices
ON missing.product_id = unit_prices.product_id;