How to Use EXISTS in SQL

Use EXISTS when you only need to know whether at least one matching row exists in another query. This is perfect for fast relationship checks, conditional filters, and correlated existence logic.

What you’ll build or solve

You’ll learn how to use EXISTS in SQL to filter rows based on matching related data. You’ll also know when it is clearer than IN.

When this approach works best

This approach is the right choice when the question is simply “does at least one related row exist?”

Common real-world scenarios include:

  • Users with orders
  • Products with reviews
  • Customers with unpaid invoices
  • Students with enrollments
  • Accounts with failed logins

This is a bad idea when the full related values themselves are needed in the result. In that case, use JOIN.

Prerequisites

You only need:

  • Basic SELECT and subquery knowledge
  • Two logically related tables

Step-by-step instructions

Step 1: Add an EXISTS subquery in WHERE

The inner query usually references the outer row.

SELECT *
FROM users u
WHERE EXISTS (
  SELECT 1
  FROM orders o
  WHERE o.user_id = u.user_id
);

This returns only users who have at least one order.

The inner query stops as soon as it finds one match.

This often makes EXISTS efficient for large relationship checks.

What to look for:

  • Returns rows only when a match exists
  • Great for correlated relationship checks
  • Stops on first match
  • Often clearer than IN
  • Use NOT EXISTS for missing relationships

Examples you can copy

Products with reviews

SELECT *
FROM products p
WHERE EXISTS (
  SELECT 1
  FROM reviews r
  WHERE r.product_id = p.product_id
);

Customers with unpaid invoices

SELECT *
FROM customers c
WHERE EXISTS (
  SELECT 1
  FROM invoices i
  WHERE i.customer_id = c.customer_id
    AND i.status = 'unpaid'
);

Students with enrollments

SELECT *
FROM students s
WHERE EXISTS (
  SELECT 1
  FROM enrollments e
  WHERE e.student_id = s.student_id
);

Common mistakes and how to fix them

Mistake 1: Forgetting the correlation key

What the reader might do:

WHERE EXISTS (
  SELECT 1
  FROM orders
)

Why it breaks: every outer row passes if the table has any row at all.

Corrected approach:

Reference the outer row inside the subquery.

Mistake 2: Using EXISTS when related values are needed

What the reader might do:

Filter users with orders, then later need order totals.

Why it breaks: EXISTS only checks presence.

Corrected approach:

Use JOIN when actual related fields are needed.

Mistake 3: Returning full columns in the subquery

What the reader might do:

SELECT *
FROM orders

Why it breaks: the selected inner columns do not matter.

Corrected approach:

Use SELECT 1 for clarity.

Troubleshooting

If every row returns, verify the correlation condition.

If performance is slow, index the related key.

If missing rows are needed instead, use NOT EXISTS.

If related columns are needed in output, switch to JOIN.

Quick recap

  • Use EXISTS for existence checks
  • Correlate the inner query to the outer row
  • SELECT 1 is the clearest pattern
  • Great for relationship filtering
  • Use NOT EXISTS for missing matches