Module F-3·17 min read

WHERE, ORDER BY, LIMIT, OFFSET, string functions, and date arithmetic — the tools you will use on every single query.

Written by Jatin Jain Saraf · Architect of SupraScan

F-3 — Filtering, Sorting, and Finding What You Need

Who this module is for: You completed F-2, can create tables and perform basic INSERT/SELECT/UPDATE/DELETE. Now you need to find specific rows, sort results, and use the built-in functions that every SQL developer relies on daily. This module covers the tools you will use in almost every query you ever write.


The WHERE Clause — Your Primary Filter

WHERE filters rows before they are returned. Only rows where the condition is TRUE are included.

-- Basic equality
SELECT * FROM products WHERE category = 'peripherals';
 
-- Not equal (two equivalent syntaxes)
SELECT * FROM products WHERE category != 'accessories';
SELECT * FROM products WHERE category <> 'accessories';  -- SQL standard
 
-- Numeric comparisons
SELECT * FROM products WHERE price > 50;
SELECT * FROM products WHERE price >= 50;
SELECT * FROM products WHERE price < 50;
SELECT * FROM products WHERE price <= 50;
 
-- Boolean check
SELECT * FROM products WHERE in_stock = true;
SELECT * FROM products WHERE in_stock;           -- shorthand for = true
SELECT * FROM products WHERE NOT in_stock;        -- shorthand for = false

BETWEEN — Range Check

-- Products priced between $25 and $75 (inclusive on both ends)
SELECT name, price FROM products
WHERE price BETWEEN 25 AND 75;
 
-- Equivalent to: WHERE price >= 25 AND price <= 75

BETWEEN is inclusive — it includes the boundary values.

IN — Match Any Value in a List

-- Products in either of two categories
SELECT name, category FROM products
WHERE category IN ('peripherals', 'accessories');
 
-- Products NOT in certain categories
SELECT name, category FROM products
WHERE category NOT IN ('general', 'accessories');

Trap: if the list (or a subquery feeding it) contains even one NULL, NOT IN silently returns zero rows — not an error, just nothing, because comparing anything to NULL with <> yields NULL, not true. NOT IN (SELECT category FROM other_table) is a classic way to hit this if that column allows NULLs. NOT EXISTS is the safer equivalent when NULLs might be involved.

LIKE and ILIKE — Pattern Matching

LIKE matches a pattern where % means "any sequence of characters" and _ means "any single character".

-- Products whose name starts with 'Wire'
SELECT name FROM products WHERE name LIKE 'Wire%';
-- Matches: Wireless Mouse, Wired Headphones, etc.
 
-- Products whose name contains 'board'
SELECT name FROM products WHERE name LIKE '%board%';
-- Matches: Keyboard, Skateboard, Surfboard, etc.
 
-- Products with exactly 4 characters in the name
SELECT name FROM products WHERE name LIKE '____';
-- Each _ matches exactly one character

LIKE is case-sensitive. ILIKE is the PostgreSQL extension for case-insensitive matching:

-- Finds 'keyboard', 'KEYBOARD', 'Keyboard', etc.
SELECT name FROM products WHERE name ILIKE '%keyboard%';

IS NULL and IS NOT NULL

-- Products with no description
SELECT name FROM products WHERE description IS NULL;
 
-- Products that have a description
SELECT name FROM products WHERE description IS NOT NULL;
 
-- NEVER use = NULL — it does not work
SELECT name FROM products WHERE description = NULL;  -- returns 0 rows (wrong!)

Combining Conditions — AND, OR, NOT

-- Both conditions must be true
SELECT name, price, category FROM products
WHERE category = 'peripherals' AND price < 100;
 
-- Either condition can be true
SELECT name, price, category FROM products
WHERE price < 30 OR in_stock = false;
 
-- Negate a condition
SELECT name, price FROM products
WHERE NOT (price > 100);

Parentheses Are Critical

Without parentheses, AND has higher precedence than OR — this catches many beginners:

-- What you probably meant:
SELECT * FROM products
WHERE (category = 'peripherals' OR category = 'accessories')
  AND price < 50;
 
-- What this means WITHOUT parentheses (AND binds tighter than OR):
SELECT * FROM products
WHERE category = 'peripherals'
   OR (category = 'accessories' AND price < 50);
-- Returns ALL peripherals regardless of price, plus cheap accessories
-- Probably not what you wanted

Rule: when mixing AND and OR, always use parentheses to make intent explicit.


ORDER BY — Sorting Results

Without ORDER BY, PostgreSQL returns rows in no guaranteed order. Do not assume the order will be consistent between queries.

-- Sort by price, cheapest first (ascending is the default)
SELECT name, price FROM products ORDER BY price;
SELECT name, price FROM products ORDER BY price ASC;  -- explicit ascending
 
-- Sort by price, most expensive first
SELECT name, price FROM products ORDER BY price DESC;
 
-- Sort by multiple columns: category first, then price within each category
SELECT name, category, price FROM products
ORDER BY category ASC, price DESC;
 
-- Sort by a computed value
SELECT name, price, price * 0.9 AS discounted
FROM products
ORDER BY discounted;

NULLS FIRST and NULLS LAST

When sorting a column that contains NULL, you control where nulls appear:

-- Rows with NULL description appear last
SELECT name, description FROM products
ORDER BY description NULLS LAST;
 
-- Rows with NULL description appear first
SELECT name, description FROM products
ORDER BY description NULLS FIRST;

By default in PostgreSQL: NULL sorts last with ASC, first with DESC.


LIMIT and OFFSET — Pagination

-- Get only the 5 cheapest products
SELECT name, price FROM products
ORDER BY price ASC
LIMIT 5;
 
-- Page 2 of results (skip first 5, take next 5)
SELECT name, price FROM products
ORDER BY price ASC
LIMIT 5 OFFSET 5;
 
-- Page 3
SELECT name, price FROM products
ORDER BY price ASC
LIMIT 5 OFFSET 10;

⚠️ The performance trap with large OFFSET:

OFFSET 1000 means PostgreSQL reads 1000 rows and discards them before returning your 5. At large offsets, this gets slow. For pagination in real applications, use cursor-based pagination instead (covered in later modules).

Always use ORDER BY with LIMIT — without it, the rows you get are unpredictable and vary between runs.


DISTINCT — Remove Duplicate Rows

-- Get all unique categories (no duplicates)
SELECT DISTINCT category FROM products;
 
-- Get unique combinations of two columns
SELECT DISTINCT category, in_stock FROM products;

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.