FR

Writing SQL · Chapter 7

Adding conditions

11 min read · 12 questions

So far our queries returned every row of a table. In practice, you almost always want a subset: one customer's sales, the products of one category, the orders of one month. Filtering rows makes the result readable, and the query faster, since the database processes less data.

After SELECT, FROM, DISTINCT and AS, here is the WHERE clause.

WHERE

WHERE keeps only the rows that meet a condition. It is written after FROM:

SELECT CustomerID, ProductID, "NOS (COPA)", Date
FROM perform21
WHERE Date = '2021-08-01'

This query displays the sales of August 2021 (in the perform tables, each month is dated by its first day).

Comparison operators

Operator Meaning Example
= equal to WHERE Category = 'Capsules'
<> or != not equal to WHERE Status <> 'Discontinued'
<, <= less than (or equal to) WHERE "NOS (COPA)" < 0
>, >= greater than (or equal to) WHERE Intensity >= 10
BETWEEN … AND … between two bounds, inclusive WHERE Date BETWEEN '2021-09-01' AND '2021-12-01'
IN (…) equal to one of the listed values WHERE Channel IN ('E-commerce', 'Export')
LIKE matches a text pattern WHERE CustomerName LIKE 'D%'
IS NULL the value is missing WHERE SubCategory IS NULL
IS NOT NULL the value is present WHERE SubCategory IS NOT NULL
AND both conditions are true WHERE condition1 AND condition2
OR at least one condition is true WHERE condition1 OR condition2
NOT negates the condition WHERE NOT Category = 'Others'

Question 7.1

Display every column of the perform24 sales that are returns of goods, i.e. whose revenue "NOS (COPA)" is negative.

Your answer

-- Question 7.1: write your query here
SELECT 
Show the solution
SELECT *
FROM perform24
WHERE "NOS (COPA)" < 0

The column name contains a space and brackets: it goes in double quotes. Negative revenue is a credit note: the customer sent goods back. These rows exist in every company and skew averages if you forget them.

LIKE: searching text

LIKE compares a value with a pattern, a bit like Ctrl + F in a browser. Two special characters make up the pattern:

  • % stands for any sequence of characters, including none;
  • _ stands for exactly one character.
Pattern Finds values that…
'A%' start with A
'%A' end with A
'%A%' contain an A
'_A%' have an A in second position

Take a fictitious users table:

ID Name Age City
1 Alice 28 Paris
2 Bob 34 New York
3 Charlie 22 Berlin
7 Amaury 23 Milan
SELECT *
FROM users
WHERE Name LIKE 'A%'

This query returns Alice and Amaury, the two names starting with A.

On our data, this query finds the customers whose name ends with S:

SELECT CustomerID, CustomerName, StrategicCustomer
FROM customer
WHERE CustomerName LIKE '%S'

Question 7.2

What does this query do? Run it to check.

SELECT ProductID, Designation
FROM product
WHERE Designation LIKE '%Décaféiné%'
Show the solution

It displays the identifier and name of every product whose name contains the word "Décaféiné" (French for decaffeinated), wherever it appears: the % before and after allows any text on either side. It returns 31 products.

Question 7.3

Display the CustomerID and CustomerName columns of the customers whose name ends with the letter "E".

Your answer

-- Question 7.3: write your query here
SELECT 
Show the solution
SELECT CustomerID, CustomerName
FROM customer
WHERE CustomerName LIKE '%E'

The leading % accepts any beginning; the final E sets the last letter. Names are in capitals in the table, so '%e' would return nothing.

Question 7.4

This query returns an error. Fix it, then explain in a comment what it does.

Your answer

SELECT CustomerName
FROM customer
WHERE CustomerName LIKE "D%"
Show the solution
-- Displays the names of customers starting with the letter D
SELECT CustomerName
FROM customer
WHERE CustomerName LIKE 'D%'

The pattern "D%" was in double quotes: the DBMS was looking for a column named D%. A value goes in single quotes. Running the query and reading the error message is often the fastest way to understand what is wrong.

BETWEEN … AND …

BETWEEN selects the values between two bounds, bounds included. It works with numbers and dates; dates are written as 'YYYY-MM-DD'.

SELECT CustomerID, ProductID, "VolKilo (COPA)", "NOS (COPA)", Date
FROM perform21
WHERE Date BETWEEN '2021-09-01' AND '2021-12-01'

This query returns the sales from September to December 2021: both months named are part of the result.

Question 7.5

In perform25, display the CustomerID, ProductID, Date and "GP (COPA)" columns of the sales made in the first quarter of 2025 (January, February and March) with a gross margin between 50,000 and 100,000.

Your answer

-- Question 7.5: write your query here
SELECT 
Show the solution
SELECT CustomerID, ProductID, Date, "GP (COPA)"
FROM perform25
WHERE Date BETWEEN '2025-01-01' AND '2025-03-01'
  AND "GP (COPA)" BETWEEN 50000 AND 100000

Two conditions combined with AND (see below). Since months are dated by their first day, '2025-03-01' does include all of March.

IN: a list of values

IN saves you from chaining equalities. These two filters are equivalent ('Hors domicile' means food service):

SELECT CustomerID, CustomerName, Channel
FROM customer
WHERE Channel IN ('E-commerce', 'Hors domicile')
SELECT CustomerID, CustomerName, Channel
FROM customer
WHERE Channel = 'E-commerce' OR Channel = 'Hors domicile'

Question 7.6

Display the name (CustomerName) and sales rep (KeyAccount) of the customers handled by Paul, Ali or Inès.

Your answer

-- Question 7.6: write your query here
SELECT 
Show the solution
SELECT CustomerName, KeyAccount
FROM customer
WHERE KeyAccount IN ('Paul', 'Ali', 'Inès')

Side note: Paul left the company at the end of 2025 (see the key_account table), yet three customers are still assigned to him. This is exactly the kind of inconsistency an analyst should report.

IS NULL: missing values

NULL means "unknown value". It is neither zero nor empty text, and you do not compare it with =: WHERE SubCategory = NULL never returns anything. Write IS NULL or IS NOT NULL.

SELECT *
FROM customer
WHERE CustomerName IS NULL

Customers 17 and 18 have no information at all: these rows should be fixed at the source.

AND, OR and NOT

AND requires all conditions to be true. OR is satisfied by just one. NOT negates a condition.

Question 7.7

What do these two queries do? Why does the second return more rows?

SELECT *
FROM customer
WHERE CustomerName LIKE 'D%' AND StrategicCustomer = 'Yes'
SELECT *
FROM customer
WHERE CustomerName LIKE 'D%' OR StrategicCustomer = 'Yes'
Show the solution

The first displays the strategic customers whose name starts with D: both conditions must be true, and only one customer is left (DTC WEBSALES).

The second displays the customers whose name starts with D or who are strategic: one condition is enough, hence 14 rows. OR widens the result, AND narrows it.

Question 7.8

In the product table, display every column of the products whose sub-category (SubCategory) is missing and whose category (Category) is 'Others'.

Your answer

-- Question 7.8: write your query here
SELECT 
Show the solution
SELECT *
FROM product
WHERE SubCategory IS NULL
  AND Category = 'Others'

A missing value is tested with IS NULL, never with = NULL. Three products meet both conditions; their brand is #N/A, the sign of a formula error in the source spreadsheet.

Question 7.9

Display the ProductID, Category, SubCategory and Brand columns of the products whose sub-category is missing or whose category is 'Others'. Is the result the same as in question 7.8?

Your answer

-- Question 7.9: write your query here
SELECT 
Show the solution
SELECT ProductID, Category, SubCategory, Brand
FROM product
WHERE SubCategory IS NULL
   OR Category = 'Others'

No: with OR, one of the two conditions is enough. You get 137 products instead of 3: all those in the Others category, plus products from other categories without a sub-category.

Brackets and precedence

When you mix AND and OR, the DBMS evaluates the ANDs first, like multiplication before addition. Brackets impose another order, exactly as in arithmetic.

-- Strong capsules OR organic products (of any category): 80 rows
SELECT ProductID, Category, Intensity, IsOrganic
FROM product
WHERE Category = 'Capsules' AND Intensity >= 10 OR IsOrganic = 'Yes'
-- Capsules that are strong OR organic: 25 rows
SELECT ProductID, Category, Intensity, IsOrganic
FROM product
WHERE Category = 'Capsules' AND (Intensity >= 10 OR IsOrganic = 'Yes')

Question 7.10

In perform21, display every sale from September to December 2021 with a margin rate above 30%. The margin rate is the gross margin divided by revenue: "GP (COPA)" / "NOS (COPA)".

Your answer

-- Question 7.10: write your query here
SELECT 
Show the solution
SELECT *
FROM perform21
WHERE Date BETWEEN '2021-09-01' AND '2021-12-01'
  AND "GP (COPA)" / "NOS (COPA)" > 0.3

A condition can apply to a calculation. 30% is written 0.3, with a decimal point. perform21 has no sales at 0; for another year you would have to exclude those rows ("NOS (COPA)" > 0), because dividing by zero gives NaN in DuckDB, a value DuckDB treats as greater than every number.

Question 7.11

The quality team wants the list of capsules (Category = 'Capsules') still on sale (Status = 'Active') with an intensity of at least 10. Display their ProductID, Designation and Intensity.

Your answer

-- Question 7.11: write your query here
SELECT 
Show the solution
SELECT ProductID, Designation, Intensity
FROM product
WHERE Category = 'Capsules'
  AND Status = 'Active'
  AND Intensity >= 10

Three conditions joined by AND: a product must meet all three to appear.

Writing order and execution order

You write the clauses in this order:

  1. SELECT: the columns to display;
  2. FROM: the source table;
  3. WHERE: the row filter.

But the DBMS executes them in a different order:

  1. FROM: it identifies the table;
  2. WHERE: it keeps the rows that meet the condition;
  3. SELECT: it keeps only the requested columns.

This difference explains a classic mistake: an alias created in the SELECT (with AS) does not exist yet when WHERE is evaluated. In most DBMSs, you therefore cannot filter on an alias.

-- In PostgreSQL or SQL Server this query fails: margin does not exist yet during the WHERE.
-- DuckDB accepts it for convenience, but better not to get used to it.
SELECT *, "GP (COPA)" / "NOS (COPA)" AS margin
FROM perform21
WHERE margin > 0.3

Question 7.12

Display the 2025 sales of Leclerc (CustomerID = 12) that are returns (negative revenue), with the columns CustomerID, ProductID, Date, "VolKilo (COPA)" and "NOS (COPA)".

Your answer

-- Question 7.12: write your query here
SELECT 
Show the solution
SELECT CustomerID, ProductID, Date, "VolKilo (COPA)", "NOS (COPA)"
FROM perform25
WHERE CustomerID = 12
  AND "NOS (COPA)" < 0

The perform25 table only holds 2025: no need to filter on the date. Leclerc has 24 return rows that year.