How do I filter SQL query results using WHERE?

When you query a table, you rarely want every row it contains. Most of the time you want specific rows — customers from a certain city, orders above a certain price, or books written by a particular author. The WHERE clause is the tool SQL gives you to express exactly which rows should be returned.

In this tutorial you will learn how to use WHERE with comparison operators, logical operators, ranges, lists, pattern matching, and NULL checks. You will also see the most common mistakes beginners make and how to avoid them.

Prerequisites

To follow along, you need:

  • A working SQL database (PostgreSQL, MySQL, MariaDB, SQL Server, Oracle Database, or SQLite).
  • A client where you can execute SQL statements (a GUI tool like DBeaver, or a command-line client).
  • Basic familiarity with the SELECT statement.

If you have not created a sample database yet, the setup script in the next section will produce everything you need.

Sample Database

Throughout this tutorial we use a small online bookstore schema with two tables: authors and books.

CREATE TABLE authors (
    author_id   INTEGER      PRIMARY KEY,
    author_name VARCHAR(100) NOT NULL,
    country     VARCHAR(50)
);

CREATE TABLE books (
    book_id      INTEGER       PRIMARY KEY,
    title        VARCHAR(200)  NOT NULL,
    author_id    INTEGER       NOT NULL,
    category     VARCHAR(50),
    price        DECIMAL(8, 2) NOT NULL,
    stock        INTEGER       NOT NULL,
    published_on DATE,
    CONSTRAINT fk_books_author
        FOREIGN KEY (author_id) REFERENCES authors (author_id)
);

INSERT INTO authors (author_id, author_name, country) VALUES
    (1, 'Jane Austen',     'United Kingdom'),
    (2, 'Haruki Murakami', 'Japan'),
    (3, 'Chimamanda Ngozi Adichie', 'Nigeria'),
    (4, 'Gabriel Garcia Marquez',   'Colombia');

INSERT INTO books (book_id, title, author_id, category, price, stock, published_on) VALUES
    (1, 'Pride and Prejudice',        1, 'Classic',        12.50, 20,   '1813-01-28'),
    (2, 'Emma',                       1, 'Classic',        10.00,  0,   '1815-12-23'),
    (3, 'Norwegian Wood',             2, 'Fiction',        15.75, 12,   '1987-09-04'),
    (4, 'Kafka on the Shore',         2, 'Fiction',        18.20,  5,   '2002-09-12'),
    (5, 'Half of a Yellow Sun',       3, 'Historical',     16.00,  8,   '2006-08-11'),
    (6, 'Americanah',                 3, 'Contemporary',   14.50,  3,   '2013-05-14'),
    (7, 'One Hundred Years of Solitude', 4, 'Classic',     22.00, 25,   '1967-05-30'),
    (8, 'Love in the Time of Cholera',   4, 'Classic',     19.99,  0,   '1985-09-05'),
    (9, 'Unknown Title',              2, NULL,             13.00,  4,   NULL);

The final row uses NULL for both category and published_on on purpose — we will use it to demonstrate how WHERE handles missing values.

Basic Syntax

The WHERE clause appears after FROM and before clauses like ORDER BY or GROUP BY.

SELECT column_list
FROM table_name
WHERE condition;
  • condition is a boolean expression evaluated once per row.
  • A row is included in the result only when the condition evaluates to true.
  • Rows that evaluate to false or unknown (which is what NULL comparisons return) are excluded.

Practical Example

Suppose the bookstore manager wants to see every book that costs more than 15.00.

SELECT
    book_id,
    title,
    price
FROM books
WHERE price > 15.00;

Reading the query in logical order:

  1. FROM books — start with every row in the books table.
  2. WHERE price > 15.00 — keep only rows where the price is greater than 15.00.
  3. SELECT book_id, title, price — return these three columns.

Expected Result

book_id title price
3 Norwegian Wood 15.75
4 Kafka on the Shore 18.20
5 Half of a Yellow Sun 16.00
7 One Hundred Years of Solitude 22.00
8 Love in the Time of Cholera 19.99

Additional Examples

1. Comparison Operators

SQL supports the standard set of comparison operators.

Operator Meaning
= Equal to
<> or != Not equal to
<, > Less than / greater than
<=, >= Less than or equal / etc.
SELECT title, category
FROM books
WHERE category = 'Classic';

Returns all books whose category is exactly 'Classic'. String comparisons are case-sensitive in some databases (for example, PostgreSQL) and case-insensitive in others (for example, SQL Server with default collation). Consult your database documentation if this matters for your data.

2. Combining Conditions with AND, OR, and NOT

You can combine multiple conditions to express more precise rules.

SELECT title, price, stock
FROM books
WHERE category = 'Classic'
  AND stock > 0;

“Return classic books that are currently in stock.”

Use parentheses when mixing AND and ORAND has higher precedence than OR, and forgetting this is a common source of bugs.

SELECT title, category, price
FROM books
WHERE (category = 'Classic' OR category = 'Fiction')
  AND price < 20.00;

3. Ranges with BETWEEN

BETWEEN is a readable shortcut for a closed range. Both endpoints are included.

SELECT title, price
FROM books
WHERE price BETWEEN 12.00 AND 16.00;

This is equivalent to:

SELECT title, price
FROM books
WHERE price >= 12.00
  AND price <= 16.00;

4. Lists with IN

Use IN when you want to match any value from a fixed list.

SELECT title, category
FROM books
WHERE category IN ('Classic', 'Fiction');

This is much cleaner than chaining OR conditions.

5. Pattern Matching with LIKE

LIKE matches strings against a pattern. The two standard wildcards are:

  • % — matches any sequence of characters (including none).
  • _ — matches exactly one character.
SELECT title
FROM books
WHERE title LIKE '%Love%';

Returns any title containing the substring Love.

SELECT author_name
FROM authors
WHERE author_name LIKE 'J%';

Returns authors whose name starts with J.

Case sensitivity of LIKE varies between databases. PostgreSQL provides ILIKE for case-insensitive matching; other systems rely on the column’s collation.

6. Handling NULL

NULL is not equal to anything — not even to another NULL. Comparisons involving NULL return unknown, and rows with an unknown result are excluded by WHERE.

To test for missing values, use IS NULL or IS NOT NULL.

SELECT title, published_on
FROM books
WHERE published_on IS NULL;

To exclude rows with a missing category:

SELECT title, category
FROM books
WHERE category IS NOT NULL;

7. Filtering with Dates

Date comparisons work like number comparisons, but the literal format depends on the database.

SELECT title, published_on
FROM books
WHERE published_on >= DATE '2000-01-01';

The DATE '...' prefix is the ANSI-standard form and is accepted by PostgreSQL, MySQL 8+, MariaDB, and SQLite. SQL Server and Oracle Database accept the plain string '2000-01-01' in most contexts as well.

8. Using WHERE in Application Code

When the filter value comes from user input or another variable, use a parameterized query rather than string concatenation. This prevents SQL injection and lets the database reuse the prepared plan.

String sql = """
        SELECT book_id, title, price
        FROM books
        WHERE category = ?
          AND price < ?
        """;

try (PreparedStatement statement = connection.prepareStatement(sql)) {
    statement.setString(1, category);
    statement.setBigDecimal(2, maxPrice);

    try (ResultSet resultSet = statement.executeQuery()) {
        while (resultSet.next()) {
            long bookId = resultSet.getLong("book_id");
            String title = resultSet.getString("title");
            BigDecimal price = resultSet.getBigDecimal("price");
            // process the row...
        }
    }
}

Common Mistakes

Comparing a Value to NULL with =

Incorrect:

SELECT *
FROM books
WHERE category = NULL;

Correct:

SELECT *
FROM books
WHERE category IS NULL;

The first query returns zero rows on every standard-compliant database because category = NULL evaluates to unknown, never true.

Mixing AND and OR Without Parentheses

Incorrect:

SELECT title, category, price
FROM books
WHERE category = 'Classic' OR category = 'Fiction'
  AND price < 20.00;

Because AND binds tighter than OR, this is interpreted as “all classic books, plus fiction books cheaper than 20.00”, which is probably not what you want.

Correct:

SELECT title, category, price
FROM books
WHERE (category = 'Classic' OR category = 'Fiction')
  AND price < 20.00;

Forgetting WHERE in an UPDATE or DELETE

Before you delete or update rows, verify the filter with a SELECT first.

SELECT *
FROM books
WHERE stock = 0;

Only after confirming the target rows should you run:

DELETE FROM books
WHERE stock = 0;

Warning: Running UPDATE or DELETE without a WHERE clause modifies every row in the table. Practice these statements only in a disposable learning database or after taking a verified backup.

Using <> with NULL

The expression category <> 'Classic' will not return rows where category IS NULL, because comparing NULL to anything yields unknown. If you need those rows too, add an explicit NULL check:

SELECT title, category
FROM books
WHERE category <> 'Classic'
   OR category IS NULL;

Database Compatibility

The core WHERE clause and the operators shown here (=, <>, <, >, <=, >=, AND, OR, NOT, BETWEEN, IN, LIKE, IS NULL, IS NOT NULL) are part of ANSI SQL and work in:

  • PostgreSQL
  • MySQL
  • MariaDB
  • SQL Server
  • Oracle Database
  • SQLite

Notable differences you may encounter:

  • Case sensitivity. String equality and LIKE are case-sensitive in PostgreSQL by default but often case-insensitive in MySQL and SQL Server, depending on collation.
  • Case-insensitive LIKE. PostgreSQL offers ILIKE. Other systems typically rely on collation or functions such as LOWER().
  • Date literals. The DATE '2000-01-01' form is portable to most systems; Oracle Database also accepts it, while some tools prefer TO_DATE('2000-01-01', 'YYYY-MM-DD').
  • Boolean values. PostgreSQL has a native BOOLEAN type; SQL Server and Oracle Database traditionally use BIT or numeric flags.

When in doubt, consult the documentation for the specific database and version you are using.

Best Practices

  • List the columns you need. Combine a specific SELECT list with a precise WHERE clause instead of scanning SELECT *.
  • Use parameters, not string concatenation. This prevents SQL injection and improves plan reuse.
  • Handle NULL explicitly. If a column can be NULL, decide whether those rows should be included and add IS NULL / IS NOT NULL accordingly.
  • Parenthesize mixed logical expressions. It makes intent obvious and avoids precedence bugs.
  • Verify destructive filters with SELECT first. Never run an UPDATE or DELETE before you have seen the rows it will affect.
  • Prefer readable operators. BETWEEN and IN communicate intent better than long chains of AND/OR.
  • Beware of functions on filtered columns. Wrapping a column in a function (for example, WHERE LOWER(title) = 'emma') can prevent an index from being used. If this matters, measure with your database’s execution-plan tool before optimizing.

Conclusion

You learned how to use the SQL WHERE clause to filter rows with comparison, logical, range, list, pattern-matching, and NULL conditions. You also saw how operator precedence, three-valued logic, and parameterized queries affect real-world usage. Always verify an UPDATE or DELETE condition with SELECT before modifying data.

How do I understand what SQL is and why it is used?

What is SQL?

SQL (Structured Query Language, pronounced “sequel” or “S-Q-L”) is a domain-specific programming language designed for managing and manipulating data stored in relational databases.

Think of it as the standard “language” you use to talk to a database — to ask it questions, store information, update records, or delete data.

The Core Idea

Imagine a giant, organized filing cabinet (the database) with many labeled drawers (tables). Each drawer contains index cards (rows) with specific fields (columns) like name, age, email, etc.

SQL is the set of commands you use to:

  • Put cards in (INSERT)
  • Find specific cards (SELECT)
  • Change information on cards (UPDATE)
  • Throw cards away (DELETE)

Basic SQL Examples

1. Querying Data (SELECT)

SELECT name, email
FROM users
WHERE age > 18;

“Give me the name and email of all users older than 18.”

2. Inserting Data (INSERT)

INSERT INTO users (name, email, age)
VALUES ('Alice', '[email protected]', 25);

3. Updating Data (UPDATE)

UPDATE users
SET email = '[email protected]'
WHERE name = 'Alice';

4. Deleting Data (DELETE)

DELETE FROM users
WHERE age < 18;

Why is SQL Used?

1. Universal Standard

SQL works across most relational databases: MySQL, PostgreSQL, Oracle, SQL Server, SQLite, etc. Learn it once, use it almost anywhere.

2. Declarative, Not Procedural

You describe WHAT you want, not HOW to get it. The database engine figures out the most efficient way to fetch the data.

-- You just say what you want:
SELECT * FROM orders WHERE total > 1000;
-- You don't write loops or index lookups yourself

3. Handles Massive Data Efficiently

SQL databases are optimized to handle millions or billions of records with speed, using indexes, query optimizers, and caching.

4. Data Integrity & Relationships

SQL enforces rules (constraints, foreign keys) that keep data consistent and reliable. For example, you can’t have an order that references a non-existent customer.

5. Powerful for Analysis

SQL can aggregate, group, and analyze data:

SELECT country, COUNT(*) AS user_count, AVG(age) AS avg_age
FROM users
GROUP BY country
ORDER BY user_count DESC;

6. Transactions & Safety

SQL supports ACID transactions (Atomicity, Consistency, Isolation, Durability) — critical for banks, e-commerce, and any system where correctness matters.

Where Is SQL Used?

  • Web Applications — user accounts, posts, comments (e.g., stored via Spring Data JPA in Java apps)
  • Mobile Apps — local storage (SQLite)
  • Business Systems — CRM, ERP, HR platforms
  • Analytics & Data Science — reporting, dashboards, BI tools
  • Banking & Finance — transactions, ledgers
  • E-commerce — products, orders, inventory

How to Start Learning SQL

  1. Install a database — SQLite (easiest) or PostgreSQL
  2. Try interactive tutorials — SQLBolt, Mode Analytics SQL Tutorial, LeetCode SQL problems
  3. Practice on real data — download sample databases like Chinook or Sakila
  4. Master the “Big 6”: SELECT, FROM, WHERE, GROUP BY, ORDER BY, JOIN

Summary

Aspect Description
What A language for talking to relational databases
Why Efficient, standardized, safe, and powerful data management
Where Nearly every application that stores structured data
How Declarative statements like SELECT, INSERT, UPDATE, DELETE

SQL is one of the most valuable and enduring skills in software development — it has been around since the 1970s and remains the backbone of data-driven applications today.

How do I use PreparedStatement to prevent SQL injection?

Using PreparedStatement is one of the most effective ways to prevent SQL injection in Java. It works by separating the SQL query structure from the data, ensuring that user input is treated strictly as data and never as part of the executable SQL command.

Here is how you use it:

1. The Key Concept: Placeholders

Instead of concatenating strings (which is where the danger lies), you use a question mark (?) as a placeholder for every dynamic value.

2. Implementation Example

package org.kodejava.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SecureQueryExample {
    public void getUserDetails(String username) {
        // 1. Define SQL with placeholders (?)
        String sql = "SELECT id, email, status FROM users WHERE username = ?";

        try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "pass");
             // 2. Prepare the statement
             PreparedStatement pstmt = conn.prepareStatement(sql)) {

            // 3. Bind the values (index starts at 1)
            pstmt.setString(1, username);

            // 4. Execute the query
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    System.out.println("User ID: " + rs.getInt("id"));
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Why this prevents SQL Injection

Imagine a malicious user provides this as a “username”: ' OR '1'='1.

  • Vulnerable (String Concatenation):
    SELECT * FROM users WHERE username = '' OR '1'='1' — This changes the logic to return all users.
  • Secure (PreparedStatement):
    The database receives the query structure first. When the input is sent, the database looks literally for a user whose name is the string ' OR '1'='1. Since no such user exists, the attack fails, and the query remains safe.

Best Practices

  • Use setXXX methods: Always use the specific setter for your data type (e.g., setInt(), setString(), setTimestamp()). This adds an extra layer of type validation.
  • Never Concatenate: Even if you use a PreparedStatement, if you build the SQL string using + or StringBuilder before passing it to prepareStatement(), you are still vulnerable.
  • Try-with-resources: As shown above, use try-with-resources to ensure the Connection and PreparedStatement are closed automatically, preventing resource leaks.

How do I execute a simple SQL query with Statement?

Executing a simple SQL query using a Statement object in JDBC follows a straightforward pattern: establish a connection, create the statement, execute the query, and process the results.

Here is a clean example of how to perform a SELECT query:

Simple SQL Query Example

package org.kodejava.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class SimpleQueryExample {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String user = "username";
        String password = "password";

        String sql = "SELECT id, username, email FROM users";

        // Use try-with-resources to ensure resources are closed automatically
        try (Connection conn = DriverManager.getConnection(url, user, password);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {

            // Iterate through the result set
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("username");
                String email = rs.getString("email");

                System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Key Methods to Know

The Statement interface provides different methods depending on the type of SQL you are running:

  1. executeQuery(String sql): Used for SELECT statements. It returns a ResultSet containing the data.
  2. executeUpdate(String sql): Used for INSERT, UPDATE, or DELETE statements. It returns an int representing the number of rows affected.
  3. execute(String sql): A general-purpose method that can execute any SQL statement. It returns true if the result is a ResultSet (query) and false if it is an update count or there are no results.

Important Tips

  • Try-with-Resources: Always use the try-with-resources block (shown above) for Connection, Statement, and ResultSet. This prevents memory leaks by ensuring the database handles are closed even if an exception occurs.
  • Security: While Statement is great for simple or static queries, use PreparedStatement if your query includes variables provided by a user. This prevents SQL Injection attacks.
  • Indices vs. Names: When reading from a ResultSet, you can use column names (e.g., rs.getString("username")) or 1-based indices (e.g., rs.getString(1)). Names are generally more readable and maintainable.

How do I limit MySQL query result?

package org.kodejava.jdbc;

import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SqlLimitExample {
    private static final String URL = "jdbc:mysql://localhost/kodejava";
    private static final String USERNAME = "kodejava";
    private static final String PASSWORD = "s3cr*t";

    public static void main(String[] args) {
        try (Connection connection =
                     DriverManager.getConnection(URL, USERNAME, PASSWORD)) {

            // Create PreparedStatement to get all data from a database.
            String query = "select count(*) from product";
            PreparedStatement ps = connection.prepareStatement(query);
            ResultSet result = ps.executeQuery();

            int total = 0;
            while (result.next()) {
                total = result.getInt(1);
            }

            System.out.println("Total number of data in database: " +
                               total + "\n");

            // Create PreparedStatement to the first 5 records only.
            query = "select * from product limit 5";
            ps = connection.prepareStatement(query);
            result = ps.executeQuery();

            System.out.println("Result fetched with specified limit 5");
            System.out.println("====================================");
            while (result.next()) {
                System.out.println("id:" + result.getInt("id") +
                                   ", code:" + result.getString("code") +
                                   ", name:" + result.getString("name") +
                                   ", price:" + result.getString("price"));
            }

            // Create PreparedStatement to get data from the 4th
            // record (remember the first record is 0) and limited
            // to 3 records only.
            query = "select * from product limit 3, 3";
            ps = connection.prepareStatement(query);
            result = ps.executeQuery();

            System.out.println("\nResult fetched with specified limit 3, 3");
            System.out.println("====================================");
            while (result.next()) {
                System.out.println("id:" + result.getInt("id") +
                                   ", code:" + result.getString("code") +
                                   ", name:" + result.getString("name") +
                                   ", price:" + result.getString("price"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

An example result of our program is:

Total number of data in database: 9

Result fetched with specified limit 5
====================================
id:1, code:P0000001, name:UML Distilled 3rd Edition, price:25.00
id:3, code:P0000003, name:PHP Programming, price:20.00
id:4, code:P0000004, name:Longman Active Study Dictionary, price:40.00
id:5, code:P0000005, name:Ruby on Rails, price:24.00
id:6, code:P0000006, name:Championship Manager, price:0.00

Result fetched with specified limit 3, 3
====================================
id:5, code:P0000005, name:Ruby on Rails, price:24.00
id:6, code:P0000006, name:Championship Manager, price:0.00
id:7, code:P0000007, name:Transport Tycoon Deluxe, price:0.00

Maven Dependencies

<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.4.0</version>
</dependency>

Maven Central