// case studies · real world impact

Real World SQL

Explore how companies solve business‑critical problems with SQL – full schema, solution, and insight.

Personalized Product Recommendations via Collaborative Filtering

🏢 AmazonAdvanced

Generate personalized product recommendations for each user using collaborative filtering based on similar users' purchase and rating patterns.

Business Problem

Amazon's personalization team is prototyping a 'customers like you also bought' feature and needs the underlying logic worked out in SQL before handing it to the ML team for productionization. For a given target user, find other users with similar purchase and rating behavior, then surface products those similar users rated highly that the target user has not purchased yet — ranked by a combination of how similar the other users are and how highly they rated the product — so the top rows can be shown directly on the user's homepage.

Dataset & Schema

User data with user_id; product ratings with user_id, product_id, rating, review_date; purchase history with user_id, product_id, purchase_date; product catalog with product_id, category, price.

CREATE TABLE users (
  user_id INTEGER PRIMARY KEY,
  user_name TEXT,
  signup_date DATE
);
CREATE TABLE ratings (
  rating_id INTEGER PRIMARY KEY,
  user_id INTEGER,
  product_id INTEGER,
  rating DECIMAL(2,1),
  review_date DATE,
  FOREIGN KEY(user_id) REFERENCES users(user_id)
);
CREATE TABLE purchases (
  purchase_id INTEGER PRIMARY KEY,
  user_id INTEGER,
  product_id INTEGER,
  purchase_date DATE,
  FOREIGN KEY(user_id) REFERENCES users(user_id)
);
CREATE TABLE products (
  product_id INTEGER PRIMARY KEY,
  product_name TEXT,
  category TEXT,
  price DECIMAL(10,2)
);

SQL Solution

WITH user_rating_vectors AS (
  SELECT 
    user_id,
    COLLECT_LIST(STRUCT(product_id, rating)) as products_rated,
    COUNT(DISTINCT product_id) as total_rated,
    AVG(rating) as avg_rating
  FROM ratings
  GROUP BY user_id
),
target_user AS (
  SELECT 
    user_id,
    products_rated,
    total_rated,
    avg_rating
  FROM user_rating_vectors
  WHERE user_id = 1
),
similarity_scores AS (
  SELECT 
    urv.user_id as similar_user_id,
    tu.user_id as target_user_id,
    ROUND(
      (SUM(r1.rating * r2.rating) / 
        (SQRT(SUM(POWER(r1.rating, 2))) * SQRT(SUM(POWER(r2.rating, 2))))) 
      , 3) as cosine_similarity
  FROM user_rating_vectors urv
  CROSS JOIN target_user tu
  LEFT JOIN ratings r1 ON urv.user_id = r1.user_id
  LEFT JOIN ratings r2 ON tu.user_id = r2.user_id 
    AND r1.product_id = r2.product_id
  WHERE urv.user_id != tu.user_id
  GROUP BY urv.user_id, tu.user_id
  HAVING COUNT(DISTINCT r1.product_id) >= 2
),
top_similar_users AS (
  SELECT 
    similar_user_id,
    target_user_id,
    cosine_similarity,
    ROW_NUMBER() OVER (PARTITION BY target_user_id ORDER BY cosine_similarity DESC) as rank
  FROM similarity_scores
  WHERE cosine_similarity > 0.5
),
recommendations_unranked AS (
  SELECT 
    tsu.target_user_id,
    r.product_id,
    p.product_name,
    p.category,
    p.price,
    ROUND(AVG(r.rating) * tsu.cosine_similarity, 2) as weighted_rating,
    COUNT(DISTINCT tsu.similar_user_id) as similar_users_count,
    ROUND(AVG(r.rating), 2) as avg_similar_user_rating
  FROM top_similar_users tsu
  INNER JOIN ratings r ON tsu.similar_user_id = r.user_id
  INNER JOIN products p ON r.product_id = p.product_id
  LEFT JOIN purchases pu ON tsu.target_user_id = pu.user_id 
    AND r.product_id = pu.product_id
  WHERE pu.purchase_id IS NULL
  GROUP BY tsu.target_user_id, r.product_id, p.product_name, p.category, p.price
),
final_recommendations AS (
  SELECT 
    target_user_id,
    product_id,
    product_name,
    category,
    price,
    weighted_rating,
    similar_users_count,
    avg_similar_user_rating,
    ROW_NUMBER() OVER (PARTITION BY target_user_id ORDER BY weighted_rating DESC) as recommendation_rank
  FROM recommendations_unranked
)
SELECT 
  target_user_id,
  product_id,
  product_name,
  category,
  price,
  weighted_rating,
  similar_users_count,
  avg_similar_user_rating,
  CASE 
    WHEN recommendation_rank = 1 THEN 'TOP_PRIORITY'
    WHEN recommendation_rank <= 5 THEN 'HIGH'
    WHEN recommendation_rank <= 10 THEN 'MEDIUM'
    ELSE 'LOW'
  END as recommendation_priority
FROM final_recommendations
WHERE recommendation_rank <= 10
ORDER BY target_user_id, recommendation_rank;

Explanation

Step-by-step Solution:

1

Build user_rating_vectors CTE to collect all products each user has rated with their ratings, plus average rating per user.

2

Define target_user to isolate the user we're making recommendations for (user_id = 1).

3

Calculate similarity_scores using cosine similarity: for each other user, compute dot product of rating vectors divided by magnitude product. Only include user pairs with >= 2 common rated products for statistical significance. Filter for similarity > 0.5 (meaningful similarity).

4

Rank similar_users by cosine similarity and keep top similar users (threshold 0.5).

5

Build recommendations_unranked by finding products rated by similar users that the target user hasn't purchased. Exclude already-purchased items using LEFT JOIN on purchases with IS NULL filter. Weight each product's rating by the similarity score of the user who rated it.

6

Calculate weighted_rating (avg_rating * cosine_similarity) to reflect both product quality and similarity confidence. Count similar users recommending each product.

7

Rank recommendations by weighted_rating within target user. Assign priority tiers: TOP_PRIORITY (rank 1), HIGH (2-5), MEDIUM (6-10), LOW (11+). This drives 20-35% incremental revenue via personalized discovery.