Filtering Essentials

Lesson 0004 — IS NULL, IN, BETWEEN, LIKE & Real-World Filtering — ~16 min read

Real data is messy. Columns are NULL, empty, or a single space. Values fall in ranges, belong to sets, or follow patterns — not just exact matches. Specialized operators let you filter what WHERE + = cannot.

At this stage you can SELECT rows and filter with = and AND/OR. In production, data is rarely complete: missing values, empty strings, ranges, sets, and patterns. This lesson covers IS NULL / IS NOT NULL, IN / NOT IN, BETWEEN / NOT BETWEEN, and LIKE / NOT LIKE.

The Dataset — Messy users

These operators only make sense when data is messy. We will use a realistic users table with NULL, empty strings, and spaces deliberately mixed in.

CREATE TABLE users (
  id                INT PRIMARY KEY AUTO_INCREMENT,
  email             VARCHAR(120) NOT NULL,
  full_name         VARCHAR(80)  NOT NULL,
  city              VARCHAR(60)  NULL,
  signup_at_utc     DATETIME     NOT NULL,
  last_purchase_inr DECIMAL(10,2) NULL,
  last_coupon_code  VARCHAR(30)  NULL,
  is_active         TINYINT(1)   NOT NULL DEFAULT 1
);

CREATE INDEX idx_users_email     ON users(email);
CREATE INDEX idx_users_signup_at ON users(signup_at_utc);
CREATE INDEX idx_users_city      ON users(city);

INSERT INTO users
(email, full_name, city, signup_at_utc, last_purchase_inr, last_coupon_code, is_active)
VALUES
('raj@tuf.com',            'Raj',            'Bengaluru', '2025-12-01 00:00:00',  999.00, 'WELCOME10',    1),
('test_user1@gmail.com',   'Test User One',  'Delhi',     '2025-12-05 10:00:00',  499.00, NULL,          1),
('testXuser2@gmail.com',   'Test User Two',  'Delhi',     '2025-12-10 12:00:00',  750.00, 'WELCOME_2026', 1),
('aayush@company.com',     'Aayush',         NULL,        '2025-11-30 23:59:59',  NULL,   NULL,          1),
('neha@example.com',       'Neha',           '',          '2025-12-31 23:59:59', 1500.00, 'TUF_50',       1),
('mohit@gmail.com',        'Mohit',          'Mumbai',    '2026-01-01 00:00:00',  299.00, 'NEWYEAR10',    1),
('sara@tuf.com',           'Sara',           'Bengaluru', '2025-10-10 05:00:00', 2000.00, NULL,          1),
('arjun@yahoo.com',        'Arjun',          'Pune',      '2025-12-20 09:00:00',  799.00, 'FLASH_SALE',   0),
('john.doe@gmail.com',     'John Doe',       'Chennai',   '2025-12-05 18:30:00',  300.00, NULL,          1),
('jane_doe@gmail.com',     'Jane Doe',       'Chennai',   '2025-12-06 18:30:00', 1200.00, 'WELCOME_BACK', 1),
('support+trial@tuf.com',  'Support Trial',  'Gurugram',  '2025-12-07 10:00:00',  NULL,   NULL,          1),
('priya@outlook.com',      'Priya',          'Hyderabad', '2025-12-08 10:00:00',  999.00, 'WELCOME10',    1),
('sameer@rediffmail.com',  'Sameer',         NULL,        '2025-12-09 10:00:00',  100.00, NULL,          1),
('emptycity@demo.com',     'Empty City',     ' ',         '2025-12-10 10:00:00',  499.00, NULL,          1),
('khushi@gmail.com',       'Khushi',         'Delhi',     '2025-12-11 10:00:00',  500.00, 'REFERRAL5',    1),
('promo@demo.com',         'Promo',          'Mumbai',    '2025-12-25 00:00:00', 1499.00, 'TUF_50',       1),
('intern@tuf.com',         'Intern',         'Bengaluru', '2025-12-22 20:00:00',  899.00, 'WELCOME_BACK', 1),
('hello@sample.com',       'Hello',          'Delhi',     '2025-12-02 08:00:00',  NULL,   NULL,          1);
CREATE TABLE users (
  id                SERIAL PRIMARY KEY,
  email             VARCHAR(120) NOT NULL,
  full_name         VARCHAR(80)  NOT NULL,
  city              VARCHAR(60)  NULL,
  signup_at_utc     TIMESTAMP    NOT NULL,
  last_purchase_inr NUMERIC(10,2) NULL,
  last_coupon_code  VARCHAR(30)  NULL,
  is_active         SMALLINT     NOT NULL DEFAULT 1
);

CREATE INDEX idx_users_email     ON users(email);
CREATE INDEX idx_users_signup_at ON users(signup_at_utc);
CREATE INDEX idx_users_city      ON users(city);

-- same INSERT as SQL (DATETIME → TIMESTAMP)
idemailfull_namecitysignup_at_utclast_purchase_inrlast_coupon_codeis_active
1raj@tuf.comRajBengaluru2025-12-01 00:00:00999.00WELCOME101
2test_user1@gmail.comTest User OneDelhi2025-12-05 10:00:00499.00NULL1
3testXuser2@gmail.comTest User TwoDelhi2025-12-10 12:00:00750.00WELCOME_20261
4aayush@company.comAayushNULL2025-11-30 23:59:59NULLNULL1
5neha@example.comNeha''2025-12-31 23:59:591500.00TUF_501
6mohit@gmail.comMohitMumbai2026-01-01 00:00:00299.00NEWYEAR101
7sara@tuf.comSaraBengaluru2025-10-10 05:00:002000.00NULL1
8arjun@yahoo.comArjunPune2025-12-20 09:00:00799.00FLASH_SALE0
9john.doe@gmail.comJohn DoeChennai2025-12-05 18:30:00300.00NULL1
10jane_doe@gmail.comJane DoeChennai2025-12-06 18:30:001200.00WELCOME_BACK1
11support+trial@tuf.comSupport TrialGurugram2025-12-07 10:00:00NULLNULL1
12priya@outlook.comPriyaHyderabad2025-12-08 10:00:00999.00WELCOME101
13sameer@rediffmail.comSameerNULL2025-12-09 10:00:00100.00NULL1
14emptycity@demo.comEmpty City' '2025-12-10 10:00:00499.00NULL1
15khushi@gmail.comKhushiDelhi2025-12-11 10:00:00500.00REFERRAL51
16promo@demo.comPromoMumbai2025-12-25 00:00:001499.00TUF_501
17intern@tuf.comInternBengaluru2025-12-22 20:00:00899.00WELCOME_BACK1
18hello@sample.comHelloDelhi2025-12-02 08:00:00NULLNULL1

IS NULL — Finding Missing Values

IS NULL checks whether a value is unknown (NULL).

ExpressionMeaning
NULL = ''UNKNOWN — not TRUE
NULL = 0UNKNOWN — not TRUE
NULL = ' 'UNKNOWN — not TRUE
NULL IS NULLTRUE

Key: NULL ≠ empty string, NULL ≠ 0, NULL ≠ space. NULL means “unknown / not recorded” — it cannot be compared with = or !=.

SELECT id, email, city
FROM users
WHERE city IS NULL;
SELECT id, email, city
FROM users
WHERE city IS NULL;
idemailcity
4aayush@company.comNULL
13sameer@rediffmail.comNULL

IS NOT NULL — Finding Present Values

IS NOT NULL keeps rows where the value is present.

SELECT id, email, city
FROM users
WHERE city IS NOT NULL;
SELECT id, email, city
FROM users
WHERE city IS NOT NULL;
idemailcity
1raj@tuf.comBengaluru
2test_user1@gmail.comDelhi
3testXuser2@gmail.comDelhi
5neha@example.com''
6mohit@gmail.comMumbai
7sara@tuf.comBengaluru
8arjun@yahoo.comPune
9john.doe@gmail.comChennai
10jane_doe@gmail.comChennai
11support+trial@tuf.comGurugram
12priya@outlook.comHyderabad
14emptycity@demo.com' '
15khushi@gmail.comDelhi
16promo@demo.comMumbai
17intern@tuf.comBengaluru
18hello@sample.comDelhi

Note: '' and ' ' are not NULL — they are present values (empty/space). Real missing data comes in many forms.

Normalizing Missing Values

In production, treat NULL, empty string, and spaces as “missing” by normalizing with TRIM + NULLIF:

SELECT id, email, city
FROM users
WHERE NULLIF(TRIM(city), '') IS NOT NULL;
SELECT id, email, city
FROM users
WHERE NULLIF(TRIM(city), '') IS NOT NULL;
idemailcity
1raj@tuf.comBengaluru
2test_user1@gmail.comDelhi
3testXuser2@gmail.comDelhi
6mohit@gmail.comMumbai
7sara@tuf.comBengaluru
8arjun@yahoo.comPune
9john.doe@gmail.comChennai
10jane_doe@gmail.comChennai
11support+trial@tuf.comGurugram
12priya@outlook.comHyderabad
15khushi@gmail.comDelhi
16promo@demo.comMumbai
17intern@tuf.comBengaluru
18hello@sample.comDelhi

TRIM(city) removes spaces, NULLIF(…, '') turns empty into NULL, then IS NOT NULL keeps only truly present cities.

Why does WHERE city = NULL return 0 rows?

IN — Membership in a List

IN checks if a value matches any value in a list.

SELECT id, email, city
FROM users
WHERE city IN ('Delhi', 'Mumbai');
SELECT id, email, city
FROM users
WHERE city IN ('Delhi', 'Mumbai');
idemailcity
2test_user1@gmail.comDelhi
3testXuser2@gmail.comDelhi
6mohit@gmail.comMumbai
15khushi@gmail.comDelhi
16promo@demo.comMumbai
18hello@sample.comDelhi

NOT IN — Excluding a List

SELECT id, email, city
FROM users
WHERE city NOT IN ('Delhi', 'Mumbai');
SELECT id, email, city
FROM users
WHERE city NOT IN ('Delhi', 'Mumbai');
idemailcity
1raj@tuf.comBengaluru
5neha@example.com''
7sara@tuf.comBengaluru
8arjun@yahoo.comPune
9john.doe@gmail.comChennai
10jane_doe@gmail.comChennai
11support+trial@tuf.comGurugram
12priya@outlook.comHyderabad
14emptycity@demo.com' '
17intern@tuf.comBengaluru

Pitfall: If city contains NULL, NOT IN can return 0 rows unexpectedly — because NULL comparisons are UNKNOWN. Prefer NOT IN only when the list and column are known non-NULL, or use NOT EXISTS.

BETWEEN — Inclusive Range

BETWEEN checks whether a value lies inside a range, including boundaries.

SELECT id, email, signup_at_utc
FROM users
WHERE signup_at_utc BETWEEN '2025-12-01 00:00:00'
                        AND '2025-12-31 23:59:59';
SELECT id, email, signup_at_utc
FROM users
WHERE signup_at_utc BETWEEN '2025-12-01 00:00:00'
                        AND '2025-12-31 23:59:59';
idemailsignup_at_utc
1raj@tuf.com2025-12-01 00:00:00
2test_user1@gmail.com2025-12-05 10:00:00
3testXuser2@gmail.com2025-12-10 12:00:00
5neha@example.com2025-12-31 23:59:59
8arjun@yahoo.com2025-12-20 09:00:00
9john.doe@gmail.com2025-12-05 18:30:00
10jane_doe@gmail.com2025-12-06 18:30:00
11support+trial@tuf.com2025-12-07 10:00:00
12priya@outlook.com2025-12-08 10:00:00
13sameer@rediffmail.com2025-12-09 10:00:00
14emptycity@demo.com2025-12-10 10:00:00
15khushi@gmail.com2025-12-11 10:00:00
16promo@demo.com2025-12-25 00:00:00
17intern@tuf.com2025-12-22 20:00:00
18hello@sample.com2025-12-02 08:00:00

NOT BETWEEN — Outside the Range

SELECT id, email, signup_at_utc
FROM users
WHERE signup_at_utc NOT BETWEEN '2025-12-01 00:00:00'
                            AND '2025-12-31 23:59:59';
SELECT id, email, signup_at_utc
FROM users
WHERE signup_at_utc NOT BETWEEN '2025-12-01 00:00:00'
                            AND '2025-12-31 23:59:59';
idemailsignup_at_utc
4aayush@company.com2025-11-30 23:59:59
6mohit@gmail.com2026-01-01 00:00:00
7sara@tuf.com2025-10-10 05:00:00

LIKE — Pattern Matching

LIKE matches text patterns. Wildcards:

WildcardMeaningExample Pattern
%Any number of characters (including 0)'%@tuf.com' ends with @tuf.com
_Exactly one character'test_user%' → 9-char prefix

Ends with @tuf.com

SELECT id, email
FROM users
WHERE email LIKE '%@tuf.com';
SELECT id, email
FROM users
WHERE email LIKE '%@tuf.com';
idemail
1raj@tuf.com
7sara@tuf.com
11support+trial@tuf.com
17intern@tuf.com

Starts with test

SELECT id, email
FROM users
WHERE email LIKE 'test%';
SELECT id, email
FROM users
WHERE email LIKE 'test%';
idemail
2test_user1@gmail.com
3testXuser2@gmail.com

_ matches exactly one character

'test_user%' → 10 characters where the 5th character is _ wildcard (any one char), then user:

SELECT id, email
FROM users
WHERE email LIKE 'test_user%';
SELECT id, email
FROM users
WHERE email LIKE 'test_user%';
idemail
2test_user1@gmail.com
3testXuser2@gmail.com

Both match because _ matches _ in row 2 and X in row 3 — it is a wildcard, not a literal underscore.

Escaping _ for a literal underscore

To match a literal _, escape it with \:

SELECT id, email
FROM users
WHERE email LIKE 'test\_user%';
SELECT id, email
FROM users
WHERE email LIKE 'test\_user%';
idemail
2test_user1@gmail.com

Now only the literal underscore row matches.

NOT LIKE — Excluding Patterns

SELECT id, email
FROM users
WHERE email NOT LIKE '%@tuf.com';
SELECT id, email
FROM users
WHERE email NOT LIKE '%@tuf.com';
idemail
2test_user1@gmail.com
3testXuser2@gmail.com
4aayush@company.com
5neha@example.com
6mohit@gmail.com
8arjun@yahoo.com
9john.doe@gmail.com
10jane_doe@gmail.com
12priya@outlook.com
13sameer@rediffmail.com
14emptycity@demo.com
15khushi@gmail.com
16promo@demo.com
18hello@sample.com

Why does 'test\_user%' match only row 2?

Notes

IS NULL / IS NOT NULL :-

NULL ≠ '' ≠ 0 ≠ ' ' — use IS NULL / IS NOT NULL, never = NULL

city IS NULL → ids 4,13; city IS NOT NULL → all except 4,13 (but includes '' and ' ')

Normalize: NULLIF(TRIM(city),'') IS NOT NULL → 14 true cities (excludes NULL/''/' ')

IN / BETWEEN :-

IN ('Delhi','Mumbai') → membership; NOT IN → outside list (NULL pitfall)

BETWEEN '2025-12-01' AND '2025-12-31' → inclusive range; NOT BETWEEN → outside

LIKE / NOT LIKE :-

% = any chars (0+); _ = exactly one char

'%@tuf.com' → 4 rows; 'test%' → 2 rows; 'test_user%' → both ( _ wildcard); 'test\_user%' → only literal _

NOT LIKE '%@tuf.com' → exclude pattern

Messy data pattern :-

Real city has NULL/''/' ' — IS NOT NULL alone not enough; combine TRIM + NULLIF

Prefer IN for sets, BETWEEN for ranges, LIKE for patterns with % and _

Primary sources: MySQL Comparison Operators · Postgres Comparison · Postgres Pattern Matching. Ask me anything that's unclear.

← Prev: Query Fundamentals Next: Aggregation — GROUP BY, HAVING & COUNT →

Questions? Ask your agent — you can follow up on any concept, quiz answer, or how this applies to a specific query. Review the SQL glossary or syntax quick reference for a compressed overview.