Skip to main content

Combining Predicates: AND / OR / NOT

Level 1: Level 1: SQL Foundations (Reading Source Data)medium13 minANDORNOToperator precedenceparenthesizing conditions

Compose multiple conditions with correct precedence and parentheses.

A misplaced paren silently ships the wrong rows

A WHERE clause is where a business rule becomes code. "Paid or shipped orders in the EU" is one English sentence, but SQL does not read English. It applies a fixed precedence order, and if that order does not match what you meant, the query still runs, returns no error, and hands wrong rows to whatever reads the table next: a dashboard, a payout job, a training set. Filter bugs are quiet, and quiet bugs are the expensive ones.

Precedence: NOT, then AND, then OR

SQL evaluates the logical operators in a fixed order: NOT binds tightest, then AND, then OR. AND relates to OR the way * relates to +. So this filter:

WHERE status = 'paid' OR status = 'shipped' AND region = 'EU'

does not mean "(paid or shipped) and in the EU." SQL groups the tighter AND first and reads it as:

WHERE status = 'paid' OR (status = 'shipped' AND region = 'EU')

The paid branch now stands alone with no region test, so every paid order from every region slips through. Against the seed data, order 3 (paid, US) comes back even though it is not in the EU. The filter silently widened.

The fix: parenthesize the OR group

WHERE (status = 'paid' OR status = 'shipped')
  AND region = 'EU';

Now the alternatives are grouped before AND applies, so both branches must also satisfy region = 'EU'. This returns exactly orders 1 and 2. Parentheses override precedence, and they document intent, so a reviewer never has to recall the precedence table to trust the clause.

When a rule stacks two independent choices, give each choice its own parenthesized group:

WHERE (status = 'paid' OR status = 'shipped')
  AND (region = 'EU'  OR region = 'UK')
  AND account_type <> 'test';

Each OR lives inside its own parentheses, and the ANDs chain the independent conditions together.

Pitfall: NOT is tighter than you think

NOT binds tighter than AND, so NOT status = 'paid' AND region = 'EU' parses as (NOT status = 'paid') AND region = 'EU', not NOT (status = 'paid' AND region = 'EU'). When you mean to negate a whole group, wrap the group: NOT (status = 'paid' AND region = 'EU').

Interview nuance: SQL predicates are three-valued. Every condition evaluates to TRUE, FALSE, or UNKNOWN, and WHERE keeps only rows that come out TRUE. A NULL compared with anything yields UNKNOWN, and NOT UNKNOWN is still UNKNOWN. So account_type <> 'test' (equivalently NOT (account_type = 'test')) silently drops every row where account_type is NULL, even though those rows are not test accounts. To keep them, say so explicitly: (account_type <> 'test' OR account_type IS NULL). Interviewers use this to check whether you actually understand three-valued logic instead of treating NULL like an ordinary value.

Sample data for this example
CREATE TABLE orders (
  order_id INTEGER,
  status   TEXT,
  region   TEXT
);
INSERT INTO orders VALUES
  (1, 'paid',      'EU'),
  (2, 'shipped',   'EU'),
  (3, 'paid',      'US'),
  (4, 'cancelled', 'EU'),
  (5, 'shipped',   'UK');
Worked example (SQL)
SELECT order_id, status, region
FROM orders
WHERE (status = 'paid' OR status = 'shipped')
  AND region = 'EU';

Apply

Your turn

The task this lesson builds to.

Return order_id, status, region for orders that are (status = 'paid' or status = 'shipped') and region = 'EU'. Get the grouping right.

3 hints and 1 automated check are waiting in the workspace.

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Reproduce this business rule verbatim: "orders that are paid OR shipped, in region EU or UK, excluding test accounts." Return order_id, status, region. The grouping must be exact.

3 hints and 1 automated check are waiting in the workspace.