Skip to main content
    Skip to main content
    SQL
    Pattern Matching
    Beginner

    SQL BETWEEN, IN, and LIKE: Pattern Matching Guide

    SQiLs TeamMarch 23, 2026
    SQL BETWEEN, IN, and LIKE: Pattern Matching Guide

    Every SQL query starts with selecting rows, but the real power comes when you filter precisely. Need invoices from Q1? Use BETWEEN. Want customers in three specific countries? Use IN. Searching for tracks with "love" in the title? Use LIKE. These three operators — BETWEEN, IN, and LIKE — are the workhorses of the WHERE clause and show up in virtually every real-world query you will write.

    In this guide you'll learn how each operator works, see real examples on the Chinook music-store database, and practice with hands-on exercises on SQiLs.

    Table of Contents

    BETWEEN — Filter by Range

    BETWEEN selects rows where a value falls within an inclusive range. It works with numbers, dates, and text. Think of it as a shorthand for >= AND <=.

    Syntax

    SELECT columns
    FROM table
    WHERE column BETWEEN low_value AND high_value;
    

    This is equivalent to:

    WHERE column >= low_value AND column <= high_value
    

    Numeric range — find mid-priced tracks

    SELECT
      Name,
      Composer,
      UnitPrice
    FROM Track
    WHERE UnitPrice BETWEEN 0.99 AND 1.49
    ORDER BY UnitPrice DESC
    LIMIT 5;
    
    NameComposerUnitPrice
    War Pigs/Luke's WallTony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne0.99
    Hallowed Be Thy NameSteve Harris0.99
    ParanoidTony Iommi, Bill Ward, Geezer Butler, Ozzy Osbourne0.99
    Midnight RiderGregg Allman0.99
    EruptionEdward Van Halen, Alex Van Halen, Michael Anthony, David Lee Roth0.99

    All 3503 tracks in the Chinook database are priced at either $0.99 or $1.99, so this range catches the $0.99 tier.

    Date range — invoices from early 2021

    SELECT
      InvoiceId,
      InvoiceDate,
      Total
    FROM Invoice
    WHERE InvoiceDate BETWEEN '2021-01-01' AND '2021-03-31'
    ORDER BY InvoiceDate
    LIMIT 5;
    
    InvoiceIdInvoiceDateTotal
    12021-01-01 00:00:001.98
    22021-01-02 00:00:003.96
    32021-01-03 00:00:005.94
    42021-01-06 00:00:008.91
    52021-01-11 00:00:0013.86

    BETWEEN is inclusive on both ends, so invoices on January 1 and March 31 are both included. This makes it perfect for quarterly or monthly reporting.

    Try it yourselfFilter invoices from early 2021 →

    NOT BETWEEN — exclude a range

    Add NOT to reverse the filter:

    SELECT InvoiceId, Total
    FROM Invoice
    WHERE Total NOT BETWEEN 1.00 AND 5.00
    ORDER BY Total DESC
    LIMIT 5;
    
    InvoiceIdTotal
    40425.86
    29925.86
    19425.86
    8925.86
    41123.86

    IN — Match a List of Values

    IN checks whether a value matches any item in a list. It replaces multiple OR conditions and keeps your queries clean and readable.

    Syntax

    SELECT columns
    FROM table
    WHERE column IN (value1, value2, value3);
    

    This is equivalent to:

    WHERE column = value1 OR column = value2 OR column = value3
    

    Filter customers by country

    SELECT
      FirstName,
      LastName,
      Country,
      City
    FROM Customer
    WHERE Country IN ('USA', 'Canada', 'Brazil')
    ORDER BY Country, LastName
    LIMIT 5;
    
    FirstNameLastNameCountryCity
    RobertoAlmeidaBrazilRio de Janeiro
    EduardoMartinsBrazilSao Paulo
    AlexandreRochaBrazilSao Paulo
    FernandaRamosBrazilBrasilia
    LuisGoncalvesBrazilSao Paulo

    Instead of writing three separate OR conditions, IN lets you list all target values in one clean expression. This becomes especially valuable when the list grows to 5, 10, or more values.

    Try it yourselfFind customers in USA or Canada →

    Filter by numeric IDs

    SELECT
      GenreId,
      Name
    FROM Genre
    WHERE GenreId IN (1, 2, 3, 4, 6);
    
    GenreIdName
    1Rock
    2Jazz
    3Metal
    4Alternative & Punk
    6Blues

    IN works equally well with numbers, strings, and even subqueries. When paired with a subquery, it becomes a powerful filtering tool — but that's a topic for the subqueries guide.

    Try it yourselfFilter for specific genre IDs →

    NOT IN — exclude specific values

    SELECT
      FirstName,
      LastName,
      Country
    FROM Customer
    WHERE Country NOT IN ('USA', 'Canada', 'Brazil')
    ORDER BY Country
    LIMIT 5;
    
    FirstNameLastNameCountryCity
    DiegoGutierrezArgentinaBuenos Aires
    MarkTaylorAustraliaSidney
    AstridGruberAustriaVienne
    DaanPeetersBelgiumBrussels
    EnriqueMunozChileSantiago

    LIKE — Wildcard Pattern Matching

    LIKE searches for patterns inside text columns. It uses two wildcard characters:

    WildcardMeaningExample
    %Any sequence of characters (including none)'%love%' matches "Love", "lovely", "gloves"
    _Exactly one character'T_m' matches "Tom", "Tim", but not "Team"

    Syntax

    SELECT columns
    FROM table
    WHERE column LIKE pattern;
    

    Find tracks starting with "The"

    SELECT
      Name,
      Composer
    FROM Track
    WHERE Name LIKE 'The %'
    ORDER BY Name
    LIMIT 5;
    
    NameComposer
    The Beautiful PeopleTwiggy Ramirez/Marilyn Manson
    The Call Of KtuluJ. Hetfield/L. Ulrich/C. Burton/D. Mustaine
    The CursePeers/Sherwood/Sherwood
    The Day That Never ComesJames Hetfield, Lars Ulrich, Kirk Hammett, Robert Trujillo
    The End Of The WorldSylvia Dee/Arthur Kent

    Using 'The %' (with a space after "The") ensures we match tracks starting with the word "The" as a separate word, not just any word beginning with "The" like "Theme" or "Therapy".

    Try it yourselfFind tracks starting with The →

    Search for a keyword anywhere in a string

    SELECT
      Name
    FROM Track
    WHERE Name LIKE '%life%'
    ORDER BY Name
    LIMIT 5;
    
    Name
    A Matter Of Life And Style
    Afterlife
    Half The Man I Used To Be / Shelf Life
    It's My Life
    Limelight

    The % on both sides matches "life" anywhere in the track name. Note that SQLite's LIKE is case-insensitive by default, so this also matches "Life" and "LIFE". In PostgreSQL and other databases, use ILIKE for case-insensitive matching.

    Try it yourselfFind songs ending in Life →

    Underscore wildcard — single character

    SELECT Name
    FROM Track
    WHERE Name LIKE 'B_d%'
    ORDER BY Name
    LIMIT 5;
    
    Name
    Bad Boy Boogie
    Bad Attitude
    Bad Boy
    Bad Seed
    Bed Of Nails

    The _ matches exactly one character, so 'B_d%' finds "Bad", "Bed", "Bid", etc. followed by anything.

    NOT LIKE — exclude a pattern

    SELECT FirstName, LastName, Email
    FROM Customer
    WHERE Email NOT LIKE '%gmail%'
    ORDER BY LastName
    LIMIT 5;
    
    FirstNameLastNameEmail
    RobertoAlmeidaroberto.almeida@riotur.gov.br
    LeonieKohlerleonekohler@surfeu.de
    BjornHansenbjorn.hansen@yahoo.no
    FrantisekWichterlovafrantisekw@jetbrains.com
    HelenaHolyhholy@gmail.com

    Combining Operators

    The real power shows up when you combine BETWEEN, IN, and LIKE with AND/OR in your WHERE clause.

    Find rock or jazz tracks with "love" in the title

    SELECT
      t.Name AS Track,
      g.Name AS Genre
    FROM Track t
    JOIN Genre g ON g.GenreId = t.GenreId
    WHERE g.Name IN ('Rock', 'Jazz')
      AND t.Name LIKE '%Love%'
    ORDER BY t.Name
    LIMIT 5;
    
    TrackGenre
    Ain't Talkin' 'bout LoveRock
    Bold As LoveRock
    Crazy Little Thing Called LoveRock
    Give Me LoveRock
    Immigrant / Inca LoveRock

    High-value invoices from specific countries in 2022

    SELECT
      InvoiceId,
      BillingCountry,
      InvoiceDate,
      Total
    FROM Invoice
    WHERE BillingCountry IN ('USA', 'Canada')
      AND InvoiceDate BETWEEN '2022-01-01' AND '2022-12-31'
      AND Total > 5.00
    ORDER BY Total DESC
    LIMIT 5;
    
    InvoiceIdBillingCountryInvoiceDateTotal
    299Canada2022-09-13 00:00:0025.86
    278USA2022-07-02 00:00:0023.86
    250USA2022-04-12 00:00:0013.86
    254Canada2022-04-22 00:00:0013.86
    261USA2022-05-18 00:00:008.91

    Each operator handles one aspect of filtering: IN narrows to specific countries, BETWEEN defines the date window, and a simple comparison sets the minimum total. Together they pinpoint exactly the rows you need.

    Try it yourselfFind European city customers →

    Cheat Sheet

    OperatorPurposeExampleNotes
    BETWEEN a AND bInclusive range filterWHERE Total BETWEEN 5 AND 10Works with numbers, dates, text
    NOT BETWEENExclude a rangeWHERE Total NOT BETWEEN 5 AND 10Opposite of BETWEEN
    IN (list)Match any value in listWHERE Country IN ('US', 'CA')Cleaner than multiple ORs
    NOT IN (list)Exclude values in listWHERE Country NOT IN ('US')Watch out for NULLs
    LIKE '%text%'Contains textWHERE Name LIKE '%love%'% = any characters
    LIKE 'text%'Starts with textWHERE Name LIKE 'The%'Efficient — can use index
    LIKE '%text'Ends with textWHERE Name LIKE '%ion'Full table scan
    LIKE '_a%'Second character is "a"WHERE Name LIKE '_a%'_ = exactly one character
    NOT LIKEDoes not match patternWHERE Email NOT LIKE '%gmail%'Opposite of LIKE

    Common Mistakes to Avoid

    1. Forgetting that BETWEEN is inclusive

    BETWEEN includes both boundary values. If you want invoices strictly within a date range (excluding the endpoints), use > and < instead.

    -- BETWEEN: includes Jan 1 AND Dec 31
    WHERE InvoiceDate BETWEEN '2022-01-01' AND '2022-12-31'
    
    -- Exclusive: does NOT include the boundary dates
    WHERE InvoiceDate > '2022-01-01' AND InvoiceDate < '2022-12-31'
    

    2. Using NULL with IN or NOT IN

    NULL does not equal anything — not even itself. If your column contains NULLs, NOT IN can return unexpected results.

    -- This silently excludes rows where Composer IS NULL
    SELECT Name FROM Track
    WHERE Composer NOT IN ('U2', 'Metallica');
    
    -- Safer: handle NULLs explicitly
    SELECT Name FROM Track
    WHERE Composer NOT IN ('U2', 'Metallica')
       OR Composer IS NULL;
    

    Try it yourselfFind tracks with missing composers →

    3. Mixing up % and _ wildcards

    % matches zero or more characters. _ matches exactly one. Using _ when you mean % returns far fewer results than expected.

    -- Wrong: matches only 4-character names starting with "The"
    WHERE Name LIKE 'The_'
    
    -- Right: matches anything starting with "The"
    WHERE Name LIKE 'The%'
    

    4. Assuming LIKE is always case-insensitive

    SQLite's LIKE is case-insensitive for ASCII by default, but PostgreSQL's LIKE is case-sensitive. Use ILIKE in PostgreSQL or LOWER() for portable case-insensitive matching.

    -- SQLite: works case-insensitively
    WHERE Name LIKE '%love%'
    
    -- PostgreSQL: use ILIKE for case-insensitive
    WHERE Name ILIKE '%love%'
    
    -- Portable: wrap both sides in LOWER()
    WHERE LOWER(Name) LIKE '%love%'
    

    5. Putting BETWEEN values in the wrong order

    The low value must come first. BETWEEN 100 AND 1 returns zero rows — it does not auto-reverse.

    -- Wrong: returns nothing
    WHERE Total BETWEEN 100 AND 1
    
    -- Right: low value first
    WHERE Total BETWEEN 1 AND 100
    

    Practice Makes Perfect

    The best way to master BETWEEN, IN, and LIKE is to write real queries. Here are exercises that target exactly these operators:

    ExerciseWhat You'll Practice
    Invoices from Early 2021 →BETWEEN with dates
    Find Tracks Starting with The →LIKE with prefix pattern
    Customers in USA or Canada →IN with country list
    Tracks with Missing Composers →NOT IN with NULL handling
    Songs Ending in Life →LIKE with suffix pattern
    Filter for Specific Genre IDs →IN with numeric IDs

    Join 500+ SQL learners already practicing on SQiLs — Browse all exercises →

    Summary

    • BETWEEN filters values within an inclusive range — perfect for dates, prices, and numeric thresholds.
    • IN matches a value against a list — cleaner and faster than chaining multiple OR conditions.
    • LIKE searches for text patterns using % (any characters) and _ (one character) wildcards.
    • All three operators work inside the WHERE clause and can be combined with AND/OR for precise filtering.
    • Add NOT before any operator to reverse the condition: NOT BETWEEN, NOT IN, NOT LIKE.
    • Watch out for NULL behavior with NOT IN — always handle NULLs explicitly.
    • Once you're comfortable filtering rows, level up to GROUP BY and HAVING to start aggregating your filtered data, or explore JOINs to combine data from multiple tables.

    Ready to put this into practice? Start solving exercises on SQiLs →

    Frequently Asked Questions

    What is the difference between LIKE and = in SQL?

    The = operator matches an exact value, while LIKE supports wildcards (% and _) for pattern matching. Use = when you know the exact value (e.g., Country = 'USA') and LIKE when you need partial matches (e.g., Name LIKE '%love%'). = is faster because it can always use an index, while LIKE with a leading % requires a full table scan.

    Can BETWEEN work with dates in SQL?

    Yes, BETWEEN works well with dates. Write WHERE InvoiceDate BETWEEN '2022-01-01' AND '2022-12-31' to find all invoices in 2022. Remember that BETWEEN is inclusive — both boundary dates are included in the results. For datetime columns, be careful with the end date: '2022-12-31' might miss records timestamped later in that day.

    Is SQL LIKE case-sensitive?

    It depends on your database. In SQLite, LIKE is case-insensitive for ASCII characters by default. In PostgreSQL, LIKE is case-sensitive — use ILIKE instead. In MySQL, it depends on the column's collation. For portable code, wrap both sides in LOWER(): WHERE LOWER(Name) LIKE '%love%'.

    When should I use IN vs multiple OR conditions?

    Always prefer IN over chaining OR conditions. IN ('USA', 'Canada', 'Brazil') is more readable, easier to maintain, and performs identically to three OR conditions. The database optimizer treats them the same way. IN also works with subqueries — WHERE CustomerId IN (SELECT CustomerId FROM Invoice) — which OR cannot replicate cleanly.

    Related Posts