> ## Content Index
> Fetch the complete content index at: https://anukulsaini.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# SQL for Product Managers: The Only Guide You'll Actually Use
- URL: https://anukulsaini.com/sql-for-product-managers-with-queries/
- Published: 2026-04-23T21:12:07.000Z
- Updated: 2026-04-23T21:12:07.000Z
- Description: Learn SQL as a product manager with real queries, practical examples, and a cheat sheet you can bookmark. No fluff — just what you need to make faster, smarter product decisions.
- Author: Anukul Saini
- Tags: Guide

I'll be honest with you.

For the first year of my PM career, I was 100% dependent on my data analyst for every single number. 

Want to know why DAU dropped? 

Ping the analyst. 

Want to know which feature is actually being used? 

Ping the analyst. 

Want to pull a quick cohort for a stakeholder meeting happening in 2 hours? 

You guessed it — ping the analyst, then wait, then follow up, then wait some more.

It was slow, frustrating, and honestly a little embarrassing.

Then I learned SQL. 

Not all of it — I'm not a data engineer and I don't want to be. But enough to answer most of my day-to-day product questions myself. And it changed how I work completely.

This guide is exactly what I wish someone had handed me back then. If you read it fully and practice the queries, you'll go from "I need to ask someone for this data" to "let me pull this myself in 5 minutes."

Let's get into it.

## Do PMs Actually Need SQL?

Short answer is yes, but not in the way most people think.

You don't need to build databases. You don't need to write optimized queries that run in milliseconds. You don't need to know what an index is (well, not right now anyway).

What you DO need is to be able to ask questions of your product data and get answers without waiting for someone else. That's it.

Here's what changes when you can write basic SQL:

- You stop being blocked by analyst availability
- Your stakeholder conversations get sharper because your numbers are fresh, not 3 days old
- You catch bugs and anomalies faster because you can spot them yourself
- You write better specs because you actually understand how data flows in your product
- You ask smarter questions in data reviews — because you've already dug through the data yourself

I've seen PMs who know SQL get promoted faster, not because SQL itself is a superpower, but because the data fluency that comes with it makes every other part of your job better.

## How Data is Stored (Without the Jargon)

Before you write a single query, you need to understand where the data lives.

Think of a database like a collection of spreadsheets that are all connected to each other. Each spreadsheet is called a **table**. Each table has **rows** (individual records) and **columns** (attributes of that record).

For example, imagine you're a PM at a food delivery app. Your database probably has:

- A `users` table — one row per user, with columns like `user_id`, `email`, `city`, `created_at`
- An `orders` table — one row per order, with columns like `order_id`, `user_id`, `amount`, `status`, `created_at`
- A `restaurants` table — one row per restaurant, with columns like `restaurant_id`, `name`, `city`, `cuisine_type`

These tables talk to each other through **keys**. The `user_id` in the `orders` table connects back to the `user_id` in the `users` table. This is what lets you ask questions like "which users placed more than 3 orders last month?"

**The most important thing you can do as a PM:** Get your company's data schema (basically a map of all tables and how they connect) from your data engineer or analyst on Day 1\. Save it somewhere you can always find it. It'll save you hours every week.

## Where Do You Write SQL?

You don't need to install anything complicated. Most companies use tools that let you write SQL right in a browser. The most common ones you'll run into are:

| Tool                       | What It Is                    | Best For                   |
| -------------------------- | ----------------------------- | -------------------------- |
| **Metabase**               | Browser-based BI tool         | Quick queries + dashboards |
| **Mode Analytics**         | SQL + visualization tool      | Analysis + sharing reports |
| **Redash**                 | Open-source query tool        | Teams with custom setups   |
| **Google BigQuery**        | Google's cloud data warehouse | Large-scale product data   |
| **Superset**               | Open-source BI by Apache      | Self-hosted teams          |
| **DBeaver**                | Desktop SQL client            | Direct DB connections      |
| **Looker / Looker Studio** | Enterprise BI                 | Dashboards + SQL queries   |

My advice: go to your data or engineering team, tell them you want read-only access to run queries on your product database. Most teams will set this up within a day. Read-only means you can look at data but you can't accidentally change or delete anything — so there's zero risk to the product.

## Reading Data: SELECT

This is where everything starts. The `SELECT` statement is how you tell the database "show me this data."

sql SELECT \* FROM users;

This says: give me all columns (`*`) from the `users` table. In practice, pulling all columns is rarely useful. Be specific:

sql SELECT user\_id, email, created\_at  
FROM users;

This pulls only the columns you care about — much faster and easier to read.

**Real PM use case:** You want to see a list of recent signups before a weekly review meeting.

sql SELECT user\_id, email, created\_at  
FROM users  
ORDER BY created\_at DESC  
LIMIT 50;

That's it. You just pulled the 50 most recent signups, sorted newest first. Simple, right?

## Filtering Data: WHERE

`SELECT` gives you all the data. `WHERE` narrows it down to what you actually care about.

sql SELECT user\_id, email, created\_at  
FROM users  
WHERE city = 'Mumbai';

This returns only users from Mumbai.

You can stack conditions using `AND` and `OR`:

sql SELECT user\_id, email, created\_at  
FROM users  
WHERE city = 'Mumbai'  
AND created\_at >= '2024-01-01';

This gives you Mumbai users who signed up after Jan 1, 2024.

**Date filters are something you'll use every single day as a PM.** Here are the most useful variations:

sql-- Users who signed up in the last 30 days  
WHERE created\_at >= NOW() - INTERVAL '30 days'

\-- Users who signed up in a specific month  
WHERE DATE\_TRUNC('month', created\_at) = '2024-03-01'

\-- Users who have NOT been active since a date  
WHERE last\_active\_at < '2024-01-01'

**Real PM use case:** Find users who completed onboarding but never made their first purchase.

sql SELECT user\_id  
FROM users  
WHERE onboarding\_completed = true  
AND first\_purchase\_at IS NULL;

That query alone can kick off a whole retention initiative.

## 6\. Sorting and Limiting Results

Two simple but important tools:

**ORDER BY** sorts your results:

sql SELECT user\_id, total\_spend  
FROM users  
ORDER BY total\_spend DESC; -- highest to lowest

**LIMIT** caps how many rows come back:

sql SELECT user\_id, total\_spend  
FROM users  
ORDER BY total\_spend DESC  
LIMIT 10;

Always use `LIMIT` when you're exploring data. Without it, a query on a large table can return millions of rows and either crash your tool or take forever to load.

Think of `LIMIT` as a safety net. Get into the habit of writing it at the end of every exploratory query.

## Aggregations — Where the Real PM Power Lives

This is where SQL goes from "useful" to "genuinely changes how you work."

Aggregations let you summarize data — count things, sum them up, calculate averages. Combined with `GROUP BY`, you can slice any metric by any dimension.

**The core aggregation functions:**

| Function        | What It Does         | Example                   |
| --------------- | -------------------- | ------------------------- |
| COUNT()         | Counts rows          | How many users signed up? |
| SUM()           | Adds values          | Total revenue this month  |
| AVG()           | Averages values      | Average session length    |
| MIN()           | Smallest value       | Earliest signup date      |
| MAX()           | Largest value        | Highest order value       |
| COUNT(DISTINCT) | Counts unique values | Unique active users (DAU) |

**Real PM query — Daily Active Users:**

SELECT  
DATE(event\_timestamp) AS date,  
COUNT(DISTINCT user\_id) AS dau  
FROM user\_events  
WHERE event\_timestamp >= NOW() - INTERVAL '30 days'  
GROUP BY DATE(event\_timestamp)  
ORDER BY date;

This gives you a 30-day DAU table in one query. You can drop it straight into a chart.

**GROUP BY** is what makes aggregations actually useful. It says "give me this number, broken down by this dimension."

SELECT  
country,  
COUNT(DISTINCT user\_id) AS total\_users,  
AVG(session\_length\_seconds) AS avg\_session  
FROM users  
GROUP BY country  
ORDER BY total\_users DESC;

Now you know where your users are and how long they stay in your product, broken down by country. That's a real insight for a growth conversation.

**HAVING** is like `WHERE` but for aggregated results:

SELECT  
feature\_name,  
COUNT(DISTINCT user\_id) AS users\_who\_used\_it  
FROM feature\_usage  
GROUP BY feature\_name  
HAVING COUNT(DISTINCT user\_id) < 100;

This tells you which features have fewer than 100 users — perfect for a feature audit or sunsetting conversation.

## JOINs — Connecting Tables Together

JOINs are the thing most PMs are scared of. They look complicated but once you understand the logic, they're not bad at all.

A JOIN combines two tables based on a shared column. Think of it like a VLOOKUP in Excel, but more powerful.

**The two you'll use 90% of the time:**

**INNER JOIN** — Returns only rows that have a match in BOTH tables:

SELECT users.user\_id, users.email, orders.order\_id, orders.amount  
FROM users  
INNER JOIN orders ON users.user\_id = orders.user\_id;

This gives you only users who have placed at least one order.

**LEFT JOIN** — Returns ALL rows from the left table, plus matching rows from the right (nulls where there's no match):

SELECT users.user\_id, users.email, orders.order\_id  
FROM users  
LEFT JOIN orders ON users.user\_id = orders.user\_id;

This gives you ALL users, including those with no orders. Users with no orders will show `NULL` in the `order_id` column.

**Real PM use case — Identify users who signed up but never ordered:**

SELECT users.user\_id, users.email  
FROM users  
LEFT JOIN orders ON users.user\_id = orders.user\_id  
WHERE orders.order\_id IS NULL;

You just found your activation problem segment. That's the list you'd hand to your growth or CRM team for a targeted re-engagement campaign.

## Subqueries and CTEs (When You're Ready to Level Up)

Once you're comfortable with JOINs, these two concepts will make your queries cleaner and more powerful.

**A subquery** is a query inside another query:

SELECT user\_id, email  
FROM users  
WHERE user\_id IN (  
SELECT DISTINCT user\_id  
FROM orders  
WHERE created\_at >= NOW() - INTERVAL '7 days'  
);

This finds users who ordered in the last 7 days. The inner query finds the user IDs; the outer query fetches their details.

**A CTE (Common Table Expression)** does the same thing but is much easier to read. You define it at the top using `WITH`:

WITH recent\_buyers AS (  
SELECT DISTINCT user\_id  
FROM orders  
WHERE created\_at >= NOW() - INTERVAL '7 days'  
)  
SELECT users.user\_id, users.email  
FROM users  
INNER JOIN recent\_buyers ON users.user\_id = recent\_buyers.user\_id;

Same result, but way more readable — especially when you're sharing your queries with analysts or engineers.

I'd recommend CTEs over subqueries almost every time. Your future self (and your teammates) will thank you.

## SQL Cheat Sheet: 10 Queries Every PM Should Bookmark

This is the most practical section of this whole article. Save it, bookmark it, come back to it whenever you need it. Adapt the table and column names to match your own product's schema.

### **1\. Monthly Active Users (MAU)**

SELECT  
DATE\_TRUNC('month', event\_timestamp) AS month,  
COUNT(DISTINCT user\_id) AS mau  
FROM user\_events  
GROUP BY 1  
ORDER BY 1;

### **2\. Day 1 / Day 7 / Day 30 Retention**

SELECT  
DATE(u.created\_at) AS signup\_date,  
COUNT(DISTINCT u.user\_id) AS total\_signups,  
COUNT(DISTINCT CASE WHEN e.event\_timestamp BETWEEN u.created\_at + INTERVAL '1 day'  
AND u.created\_at + INTERVAL '2 days' THEN u.user\_id END) AS day1\_retained,  
COUNT(DISTINCT CASE WHEN e.event\_timestamp BETWEEN u.created\_at + INTERVAL '7 days'  
AND u.created\_at + INTERVAL '8 days' THEN u.user\_id END) AS day7\_retained  
FROM users u  
LEFT JOIN user\_events e ON u.user\_id = e.user\_id  
GROUP BY 1  
ORDER BY 1;

### **3\. Feature Adoption Rate**

SELECT  
COUNT(DISTINCT user\_id) AS users\_who\_used\_feature,  
(COUNT(DISTINCT user\_id) \* 100.0 / (SELECT COUNT(\*) FROM users)) AS adoption\_rate\_pct  
FROM feature\_usage  
WHERE feature\_name = 'your\_feature\_name'  
AND created\_at >= '2024-01-01';

### **4\. Funnel Drop-off Analysis**

SELECT  
COUNT(DISTINCT CASE WHEN step = 'signup' THEN user\_id END) AS step1\_signup,  
COUNT(DISTINCT CASE WHEN step = 'onboarding' THEN user\_id END) AS step2\_onboarding,  
COUNT(DISTINCT CASE WHEN step = 'first\_action' THEN user\_id END) AS step3\_first\_action,  
COUNT(DISTINCT CASE WHEN step = 'purchase' THEN user\_id END) AS step4\_purchase  
FROM funnel\_events  
WHERE created\_at >= NOW() - INTERVAL '30 days';

### **5\. Churn Signal — Users Inactive for 30 Days**

SELECT user\_id, email, last\_active\_at  
FROM users  
WHERE last\_active\_at < NOW() - INTERVAL '30 days'  
AND account\_status = 'active'  
ORDER BY last\_active\_at ASC;

### **6\. Top 10 Power Users**

SELECT  
user\_id,  
COUNT(\*) AS total\_events  
FROM user\_events  
WHERE event\_timestamp >= NOW() - INTERVAL '30 days'  
GROUP BY user\_id  
ORDER BY total\_events DESC  
LIMIT 10;

### **7\. A/B Test Conversion Comparison**

SELECT  
experiment\_group,  
COUNT(DISTINCT user\_id) AS total\_users,  
COUNT(DISTINCT CASE WHEN converted = true THEN user\_id END) AS conversions,  
ROUND(COUNT(DISTINCT CASE WHEN converted = true THEN user\_id END) \* 100.0  
/ COUNT(DISTINCT user\_id), 2) AS conversion\_rate\_pct  
FROM ab\_test\_results  
WHERE experiment\_name = 'your\_experiment\_name'  
GROUP BY experiment\_group;

### **8\. Revenue by Plan / Segment**

SELECT  
plan\_type,  
COUNT(DISTINCT user\_id) AS subscribers,  
SUM(mrr) AS total\_mrr,  
AVG(mrr) AS avg\_mrr  
FROM subscriptions  
WHERE status = 'active'  
GROUP BY plan\_type  
ORDER BY total\_mrr DESC;

### **9\. Zero-Usage Features (Feature Audit)**

SELECT f.feature\_name  
FROM features f  
LEFT JOIN feature\_usage fu ON f.feature\_name = fu.feature\_name  
AND fu.created\_at >= NOW() - INTERVAL '90 days'  
WHERE fu.feature\_name IS NULL;

### **10\. NPS Segment Behavior**

SELECT  
CASE  
WHEN nps\_score >= 9 THEN 'Promoter'  
WHEN nps\_score >= 7 THEN 'Passive'  
ELSE 'Detractor'  
END AS nps\_segment,  
COUNT(DISTINCT u.user\_id) AS users,  
AVG(total\_sessions) AS avg\_sessions,  
AVG(total\_spend) AS avg\_spend  
FROM nps\_responses n  
JOIN users u ON n.user\_id = u.user\_id  
GROUP BY 1  
ORDER BY 2 DESC;

## SQL Best Practices for PMs

A few things I've learned the hard way:

- **Always use LIMIT when exploring.** On large tables, a query without LIMIT can timeout or return millions of rows. Start with `LIMIT 100` and expand once you know the query works.
- **Always get read-only access.** Never run SQL on production with write permissions. You could accidentally delete data. Read-only access protects you and the product.
- **Comment your queries.** Use `--` to add notes, especially when you save queries for later. "Future you" will have no memory of what you were trying to do.
- **Validate your output.** Cross-check your query result against a known number (like a dashboard metric) before sharing it in a meeting.
- **Save your queries.** Keep a personal query library in Notion, GitHub Gist, or even a Google Doc. You'll reuse 80% of them with minor changes.
- **SQL helps you collaborate better, not replace your analyst.** When you come to your data analyst with a half-written query and a clear question, you'll get a much better answer much faster than if you just say "can you pull our retention numbers?"
- **Use AI.** any Ai model can help you write any sql query you want with simple prompts saving a lot of time.

## Your 4-Week SQL Learning Plan

Don't try to learn all of this in a weekend. Here's a plan that actually works:

| Week       | Focus                               | What to Practice                                          |
| ---------- | ----------------------------------- | --------------------------------------------------------- |
| **Week 1** | SELECT, WHERE, ORDER BY, LIMIT      | Pull user lists, filter by date, sort results             |
| **Week 2** | COUNT, SUM, AVG, GROUP BY, HAVING   | Calculate DAU, MAU, revenue by segment                    |
| **Week 3** | INNER JOIN, LEFT JOIN               | Combine user + event + order tables                       |
| **Week 4** | Subqueries, CTEs, real product data | Build funnel queries, retention queries on your actual DB |

## **Free resources I'd recommend:**

- [SQLZoo](https://sqlzoo.net/?ref=anukulsaini.com) — beginner, browser-based practice
- [Mode SQL Tutorial](https://mode.com/sql-tutorial/?ref=anukulsaini.com) — built for analysts, great for PMs
- [GoPractice SQL for PMs](https://gopractice.io/course/sql/?ref=anukulsaini.com) — specifically designed for product managers
- LeetCode Easy SQL problems — once you're comfortable with the basics

## Wrapping Up

SQL didn't make me a data scientist. It made me a better product manager.

The biggest shift wasn't the technical skill itself — it was the mindset shift that came with it. When you know you can get the answer yourself in 10 minutes, you ask more questions. You validate your assumptions more often. You catch things earlier. You show up to meetings with better context.

If you take nothing else from this guide, take this: you don't need to know everything about SQL to get value from it. Learn `SELECT`, `WHERE`, `GROUP BY`, and `JOIN` well, and you'll be able to answer 70% of your daily product questions on your own.

Start small. Pull one query from the cheat sheet above, adapt it to your product's table names, and run it this week. That's it.

I write about what I'm actually learning and doing as a product manager over at [anukulsaini.com](https://anukulsaini.com/). If this was useful, subscribe — I publish stuff like this regularly.