CASE Expression in PostgreSQL

Learn how to use the PostgreSQL CASE expression to add conditional if-else branching logic inside SELECT, WHERE, GROUP BY, ORDER BY, and aggregate functions.

5 min read · Back to overview

Quick Answer

The CASE expression evaluates a list of WHEN conditions in order and returns the result for the first condition that is true. If no condition matches it returns the ELSE value, or NULL when no ELSE is provided. It can appear anywhere a value expression is valid.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

The CASE expression is SQL’s built-in conditional logic. Because it is an expression rather than a statement, you can use it anywhere a value is expected—inside SELECT, WHERE, GROUP BY, ORDER BY, and aggregate functions like SUM and COUNT. PostgreSQL evaluates each WHEN condition in order and returns the result of the first one that is true.

Syntax

General form — evaluates arbitrary Boolean conditions:

CASE
  WHEN condition_1 THEN result_1
  WHEN condition_2 THEN result_2
  ...
  ELSE else_result
END

Simple form — compares one expression against a list of values (equality only):

CASE expression
  WHEN value_1 THEN result_1
  WHEN value_2 THEN result_2
  ...
  ELSE else_result
END

Practical Example

Create a products table and classify items by price tier using the general form:

CREATE TABLE products (
  id         SERIAL PRIMARY KEY,
  name       VARCHAR(100) NOT NULL,
  unit_price NUMERIC(10, 2) NOT NULL,
  category   VARCHAR(50)
);

INSERT INTO products (name, unit_price, category) VALUES
  ('Economy Widget',    4.99,  'widgets'),
  ('Standard Widget',  24.99,  'widgets'),
  ('Premium Widget',   89.99,  'widgets'),
  ('Enterprise Widget', 249.99, 'widgets');

SELECT
  name,
  unit_price,
  CASE
    WHEN unit_price <  10.00 THEN 'Budget'
    WHEN unit_price <  50.00 THEN 'Mid-range'
    WHEN unit_price < 100.00 THEN 'Premium'
    ELSE 'Enterprise'
  END AS price_tier
FROM products
ORDER BY unit_price;

Expected output:

        name          | unit_price | price_tier
----------------------+------------+------------
 Economy Widget       |       4.99 | Budget
 Standard Widget      |      24.99 | Mid-range
 Premium Widget       |      89.99 | Premium
 Enterprise Widget    |     249.99 | Enterprise

Use the simple form to expand a short status code into a label:

SELECT
  id,
  name,
  CASE category
    WHEN 'widgets'     THEN 'Widget Products'
    WHEN 'accessories' THEN 'Accessories'
    WHEN 'services'    THEN 'Service Items'
    ELSE 'Uncategorized'
  END AS category_label
FROM products;

Conditional Aggregation

A powerful pattern is combining CASE with aggregate functions to produce a pivot-style breakdown in a single query pass:

SELECT
  SUM(CASE WHEN unit_price <  10.00               THEN 1 ELSE 0 END) AS budget_count,
  SUM(CASE WHEN unit_price >= 10.00 AND unit_price < 50.00  THEN 1 ELSE 0 END) AS midrange_count,
  SUM(CASE WHEN unit_price >= 50.00 AND unit_price < 100.00 THEN 1 ELSE 0 END) AS premium_count,
  SUM(CASE WHEN unit_price >= 100.00               THEN 1 ELSE 0 END) AS enterprise_count
FROM products;

Use CASE in ORDER BY to apply a custom sort order without altering stored data:

SELECT name, unit_price
FROM products
ORDER BY
  CASE
    WHEN unit_price <  10.00 THEN 1
    WHEN unit_price <  50.00 THEN 2
    WHEN unit_price < 100.00 THEN 3
    ELSE 4
  END;

Inspect how PostgreSQL plans a conditional aggregation to make sure it is not doing unnecessary work:

EXPLAIN (ANALYZE, BUFFERS)
SELECT
  SUM(CASE WHEN unit_price < 10.00 THEN 1 ELSE 0 END) AS budget_count
FROM products;

Testing with Vela

Before shipping a CASE-heavy reporting query to production, clone the production database to a Vela branch and run the query there. You can iterate on the CASE conditions, verify the output against real data distributions, and confirm the execution plan with EXPLAIN ANALYZE—all without risking production query performance or locking any live tables.

Production Tips

  • Always include an ELSE clause. Without it, rows that match no WHEN return NULL, which can silently corrupt aggregations.
  • Place the most common or most selective WHEN conditions first—PostgreSQL evaluates them in order and short-circuits on the first match.
  • Avoid CASE in WHERE clauses when performance matters; index access is typically lost. Rewrite as OR conditions or add a computed column with an expression index.
  • Conditional aggregation with SUM(CASE WHEN ... THEN 1 ELSE 0 END) is the standard SQL way to pivot row counts into columns without a dedicated PIVOT syntax.
  • In ORDER BY, use CASE to implement arbitrary sort priority rather than storing a sort-order column in the table.
  • For complex branching logic shared across many queries, consider extracting the CASE into a stored function or a generated column for maintainability.

Continue in Conditional Expressions & Operators: COALESCE.

Related in this section: COALESCE · ISNULL · NULLIF

Frequently Asked Questions

What does the CASE expression do in PostgreSQL?
CASE evaluates a list of Boolean conditions from top to bottom and returns the result of the first condition that is true. It is SQL's equivalent of an if/else or switch statement and can be placed anywhere a value is expected—SELECT, WHERE, GROUP BY, ORDER BY, and inside aggregate functions.
Does using CASE in a WHERE clause block index use?
Usually yes. Filtering on a CASE expression prevents the planner from using a standard B-tree index because the indexed column is wrapped inside an expression. Rewriting the condition as a set of OR predicates or creating an expression index on the CASE result typically restores index access.
What is the difference between the general and simple CASE forms?
The general form (CASE WHEN condition THEN result ...) evaluates arbitrary Boolean expressions, supporting ranges, IS NULL, and complex comparisons. The simple form (CASE expression WHEN value THEN result ...) compares one expression against a list of values using equality only.
What happens if no WHEN condition matches and there is no ELSE clause?
The CASE expression returns NULL. To avoid unexpected NULLs in query results, always include an ELSE clause with a sensible default.
What is the safest way to use CASE in production queries?
Always include an ELSE clause to handle unexpected values, put the most selective conditions first for readability, and test with EXPLAIN ANALYZE when using CASE in a WHERE clause to confirm the planner is not choosing an unnecessarily expensive plan.