Design & creation
A normalized schema for a music store: artists, albums, tracks, customers, orders, and reviews, with foreign keys enforcing referential integrity.
-- Create tables
CREATE TABLE Artists (
ArtistID INT PRIMARY KEY,
ArtistName VARCHAR(100) NOT NULL,
Genre VARCHAR(50)
);
CREATE TABLE Albums (
AlbumID INT PRIMARY KEY,
AlbumTitle VARCHAR(100) NOT NULL,
ReleaseYear INT,
ArtistID INT,
CONSTRAINT fk_artist FOREIGN KEY (ArtistID) REFERENCES Artists(ArtistID)
);
CREATE TABLE Tracks (
TrackID INT PRIMARY KEY,
TrackTitle VARCHAR(100) NOT NULL,
Duration TIME,
AlbumID INT,
TrackNumber INT,
Lyrics TEXT,
CONSTRAINT fk_album FOREIGN KEY (AlbumID) REFERENCES Albums(AlbumID)
);
CREATE TABLE Customers (
CustomerID INT PRIMARY KEY,
FirstName VARCHAR(50) NOT NULL,
LastName VARCHAR(50) NOT NULL,
Email VARCHAR(100) UNIQUE,
Birthdate DATE
);
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
CustomerID INT,
OrderDate DATE DEFAULT CURRENT_DATE,
TotalAmount DECIMAL(10, 2),
CONSTRAINT fk_customer FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);
CREATE TABLE OrderItems (
OrderItemID INT PRIMARY KEY,
OrderID INT,
TrackID INT,
Quantity INT,
UnitPrice DECIMAL(6, 2),
CONSTRAINT fk_order FOREIGN KEY (OrderID) REFERENCES Orders(OrderID),
CONSTRAINT fk_track FOREIGN KEY (TrackID) REFERENCES Tracks(TrackID)
);
CREATE TABLE Reviews (
ReviewID INT PRIMARY KEY,
TrackID INT,
CustomerID INT,
Rating INT CHECK (Rating >= 1 AND Rating <= 5),
ReviewText TEXT,
CONSTRAINT fk_review_track FOREIGN KEY (TrackID) REFERENCES Tracks(TrackID),
CONSTRAINT fk_review_customer FOREIGN KEY (CustomerID) REFERENCES Customers(CustomerID)
);
Data manipulation
Inserting records and retrieving data across joins, the everyday operations the schema needs to support cleanly.
-- Insert a new artist
INSERT INTO Artists (ArtistID, ArtistName, Genre)
VALUES (1, 'The Exclusive', 'indie-pop');
-- Insert a new customer
INSERT INTO Customers (CustomerID, FirstName, LastName, Email, Birthdate)
VALUES (1, 'Shane', 'Draper', 'shane@example.com', '1990-05-15');
-- Retrieve all tracks from a specific album
SELECT TrackTitle, Duration
FROM Tracks
WHERE AlbumID = 1;
-- Retrieve customers who placed orders
SELECT FirstName, LastName
FROM Customers
WHERE CustomerID IN (SELECT DISTINCT CustomerID FROM Orders);
-- Calculate the total order amount for a specific customer
SELECT CustomerID, SUM(TotalAmount) AS TotalSpent
FROM Orders
WHERE CustomerID = 1
GROUP BY CustomerID;
-- Retrieve customer info with the track and album they ordered
SELECT c.FirstName, c.LastName, t.TrackTitle, a.AlbumTitle
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID
JOIN OrderItems oi ON o.OrderID = oi.OrderID
JOIN Tracks t ON oi.TrackID = t.TrackID
JOIN Albums a ON t.AlbumID = a.AlbumID
WHERE c.CustomerID = 1;
Complex queries
Multi-table joins and aggregation to answer questions the business would actually ask.
-- Albums and their average ratings
SELECT a.AlbumTitle, AVG(r.Rating) AS AvgRating
FROM Albums a
LEFT JOIN Tracks t ON a.AlbumID = t.AlbumID
LEFT JOIN Reviews r ON t.TrackID = r.TrackID
GROUP BY a.AlbumID, a.AlbumTitle
ORDER BY AvgRating DESC;
-- Customers who purchased albums from a specific genre
SELECT DISTINCT c.FirstName, c.LastName
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID
JOIN OrderItems oi ON o.OrderID = oi.OrderID
JOIN Tracks t ON oi.TrackID = t.TrackID
JOIN Albums a ON t.AlbumID = a.AlbumID
JOIN Artists ar ON a.ArtistID = ar.ArtistID
WHERE ar.Genre = 'Rock';
-- Most popular tracks by total number of orders
SELECT t.TrackTitle, COUNT(oi.OrderID) AS NumOrders
FROM Tracks t
LEFT JOIN OrderItems oi ON t.TrackID = oi.TrackID
GROUP BY t.TrackID, t.TrackTitle
ORDER BY NumOrders DESC
LIMIT 5;
-- Customers who haven't made any orders
SELECT c.FirstName, c.LastName
FROM Customers c
LEFT JOIN Orders o ON c.CustomerID = o.CustomerID
WHERE o.OrderID IS NULL;
Performance optimization
Indexing hot lookup paths, normalizing genre into its own table, and partitioning orders by year so the system stays fast as data grows.
-- Index the Albums table
CREATE INDEX idx_album_artist ON Albums (ArtistID);
CREATE INDEX idx_album_title ON Albums (AlbumTitle);
-- Index the Tracks table
CREATE INDEX idx_track_album ON Tracks (AlbumID);
CREATE INDEX idx_track_title ON Tracks (TrackTitle);
-- Normalize genre into a separate table
CREATE TABLE Genres (
GenreID INT PRIMARY KEY,
GenreName VARCHAR(50) NOT NULL
);
CREATE TABLE Artists (
ArtistID INT PRIMARY KEY,
ArtistName VARCHAR(100) NOT NULL,
GenreID INT,
FOREIGN KEY (GenreID) REFERENCES Genres(GenreID)
);
-- Partition the Orders table by year
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
CustomerID INT,
OrderDate DATE,
TotalAmount DECIMAL(10, 2)
)
PARTITION BY RANGE (YEAR(OrderDate)) (
PARTITION p_2010 VALUES LESS THAN (2011),
PARTITION p_2011 VALUES LESS THAN (2012),
PARTITION p_2012 VALUES LESS THAN (2013)
);
Reporting queries
Aggregations that turn transactional data into the numbers leadership cares about, revenue by genre and order trends over time.
-- Total sales revenue by genre
SELECT ar.Genre, SUM(oi.Quantity * oi.UnitPrice) AS TotalRevenue
FROM Artists ar
JOIN Albums a ON ar.ArtistID = a.ArtistID
JOIN Tracks t ON a.AlbumID = t.AlbumID
JOIN OrderItems oi ON t.TrackID = oi.TrackID
GROUP BY ar.Genre
ORDER BY TotalRevenue DESC;
-- Average order amount and number of orders per year
SELECT YEAR(o.OrderDate) AS Year,
AVG(o.TotalAmount) AS AvgOrderAmount,
COUNT(o.OrderID) AS NumOrders
FROM Orders o
GROUP BY Year
ORDER BY Year;
Subqueries & aggregation
Nested queries for higher-order questions: top spenders, customer lifetime value, and artists who span multiple genres.
-- Customer with the highest total amount spent
SELECT c.FirstName, c.LastName, MAX(TotalAmountSpent) AS MaxAmountSpent
FROM (
SELECT c.CustomerID, SUM(o.TotalAmount) AS TotalAmountSpent
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID
GROUP BY c.CustomerID
) AS CustomerTotalAmount
JOIN Customers c ON CustomerTotalAmount.CustomerID = c.CustomerID;
-- Customer lifetime value based on average order amount
SELECT c.FirstName, c.LastName,
AVG(o.TotalAmount) * COUNT(o.OrderID) AS CLV
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID
GROUP BY c.CustomerID, c.FirstName, c.LastName;
-- Artists with albums in different genres
SELECT ar.ArtistName, GROUP_CONCAT(DISTINCT a.Genre) AS DifferentGenres
FROM Artists ar
JOIN Albums a ON ar.ArtistID = a.ArtistID
GROUP BY ar.ArtistName
HAVING COUNT(DISTINCT a.Genre) > 1;
Stored procedures
Encapsulating frequently used logic into procedures and functions for maintainability and reuse.
-- Calculate total revenue for an artist
DELIMITER //
CREATE PROCEDURE CalculateArtistRevenue(IN artistID INT)
BEGIN
DECLARE totalRevenue DECIMAL(10, 2);
SELECT SUM(oi.Quantity * oi.UnitPrice) INTO totalRevenue
FROM OrderItems oi
JOIN Tracks t ON oi.TrackID = t.TrackID
JOIN Albums a ON t.AlbumID = a.AlbumID
WHERE a.ArtistID = artistID;
SELECT totalRevenue;
END //
DELIMITER ;
CALL CalculateArtistRevenue(1);
-- Update track prices (increase by 10%)
DELIMITER //
CREATE PROCEDURE UpdateTrackPrices()
BEGIN
UPDATE Tracks
SET UnitPrice = UnitPrice * 1.1;
END //
DELIMITER ;
-- Function: number of reviews for a customer
DELIMITER //
CREATE FUNCTION GetNumReviewsForCustomer(customerID INT) RETURNS INT
BEGIN
DECLARE numReviews INT;
SELECT COUNT(*) INTO numReviews
FROM Reviews
WHERE CustomerID = customerID;
RETURN numReviews;
END //
DELIMITER ;
SELECT FirstName, LastName, GetNumReviewsForCustomer(CustomerID) AS NumReviews
FROM Customers;
Security & access control
Roles, granular permissions, row-level security, and password policy, because a database is only as trustworthy as its access model.
-- Roles for different types of users
CREATE ROLE admin;
CREATE ROLE employee;
CREATE ROLE customer;
-- Grant permissions to roles
GRANT SELECT, INSERT, UPDATE, DELETE ON Artists TO admin;
GRANT SELECT, INSERT, UPDATE, DELETE ON Albums TO admin;
GRANT SELECT ON Tracks TO admin, employee, customer;
GRANT SELECT ON Customers TO admin, employee, customer;
GRANT SELECT ON Orders TO admin, employee, customer;
GRANT SELECT ON OrderItems TO admin, employee, customer;
-- Create users and assign roles
CREATE USER 'admin_user' IDENTIFIED BY 'admin_password';
GRANT admin TO 'admin_user';
CREATE USER 'employee_user' IDENTIFIED BY 'employee_password';
GRANT employee TO 'employee_user';
-- Row-level access control for customers
CREATE POLICY customer_order_policy
ON Orders
USING (CustomerID = current_setting('app.current_customer_id')::INT);
ALTER TABLE Orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE Orders FORCE ROW LEVEL SECURITY;
-- Enforce strong password policy
ALTER USER 'admin_user' PASSWORD EXPIRE;