Webbo3 Data Analysis Bootcamp · SQL Module · Lesson 2
SELECT Queries: Retrieving Data with Column Selection, Table References, and Row Filtering
A hands-on lesson covering how to retrieve every column, choose specific columns, reference tables correctly, and filter rows using the WHERE clause and comparison operators.
In the last lesson you installed MySQL, created your first database, and built a table with the right data types and constraints. But a database that only stores data is a vault with no door. The purpose of a database is to answer questions, and the tool you use to ask those questions is the SELECT statement. SELECT is the most fundamental command in SQL. It is how you read data without changing it. Every report you generate, every dashboard you populate, every analysis you perform starts with a SELECT query. This lesson teaches you to retrieve data precisely: all of it, specific pieces of it, and only the rows that match your conditions. Master this lesson before moving forward, because every advanced query you will ever write is built on these foundations.
1. SELECT All Columns: SELECT *
The asterisk in SQL means everything. When you write SELECT *, you are asking the database to return every column in the table, in the order they were defined when the table was created. This is the fastest way to see what a table contains, especially when you are exploring an unfamiliar database for the first time.
The syntax. Assuming you have a customers table from the previous lesson, run this in MySQL Workbench:
SELECT * FROM customers;
MySQL returns every row and every column. If the table has ten columns and five hundred rows, you get all five hundred rows with all ten columns displayed. This is useful for initial exploration, but it is a habit you should break quickly in production work. SELECT * has three significant drawbacks. First, it retrieves columns you do not need, which wastes network bandwidth and memory. Second, if the table schema changes later, for example a developer adds a large TEXT column for notes, your query suddenly starts retrieving that heavy data without your knowledge. Third, it makes your query harder to read and debug because the reader cannot see which columns are actually being used.
When to use SELECT *. Use it only during development, debugging, or when you genuinely need every column and the table is small. In every other situation, especially in reports, dashboards, and application code, name the specific columns you need. This is not just best practice. It is a performance and maintainability requirement.
Limiting results during exploration. If a table has millions of rows and you run SELECT *, MySQL will attempt to return all of them, which can freeze MySQL Workbench. To protect yourself, always add a LIMIT clause during exploration:
SELECT * FROM customers LIMIT 10;
This returns only the first ten rows. It is a safety net. Make it a reflex to type LIMIT whenever you use SELECT * on a table whose size you do not know.
2. SELECT Specific Columns
Naming specific columns is the professional standard. It makes your query explicit, efficient, and resilient against schema changes. It also lets you control the order in which columns appear in the result, which matters when you are exporting data to a report or feeding it into a chart.
Selecting individual columns. Instead of SELECT *, list the columns you need, separated by commas:
SELECT first_name, last_name, email FROM customers;
MySQL returns only those three columns, in that exact order, for every row in the table. The query is faster to execute and faster to transfer over the network because it ignores columns like phone, registration_date, and is_active that you did not request.
Column aliases for readable output. Sometimes the column names in your database are technical or abbreviated, like cust_fn or reg_dt. You can rename them in the output using the AS keyword:
SELECT first_name AS 'First Name', last_name AS 'Last Name', email AS 'Email Address' FROM customers;
The column headers in your result grid now display the friendly names inside the quotes instead of the raw column names. This is essential when you are exporting query results to a CSV that a manager will open in Excel. Aliases do not change the actual column names in the database. They only change the display name in that specific query result.
Concatenating columns. You can combine multiple columns into one output column using the CONCAT function:
SELECT CONCAT(first_name, ' ', last_name) AS 'Full Name', email FROM customers;
This returns a single column called Full Name that contains values like "Chidinma Okafor." The space inside the quotes ensures the first and last names are separated. You can concatenate as many columns and literal strings as you need, separating each with a comma inside the CONCAT function.
3. The FROM Clause
The FROM clause tells MySQL which table to pull data from. It seems simple, but there are nuances that affect how you write queries in real environments, especially when a database contains many tables or when you are querying across multiple databases.
Basic table reference. The simplest form is what you have already seen:
SELECT first_name, last_name FROM customers;
This assumes the customers table exists in the currently selected database, the one you activated with USE webbo3_retail. If you have not selected a database, or if you want to query a table in a different database without switching contexts, you must use a fully qualified name:
SELECT first_name, last_name FROM webbo3_retail.customers;
This is database_name.table_name. It is useful when you are connected to one database but need to pull reference data from another without running USE repeatedly. In large organizations, analysts often query across multiple databases, and fully qualified names prevent ambiguity.
Table aliases for readability. When tables have long names, or when you join multiple tables in a single query, typing the full table name before every column becomes tedious. You can assign a short alias in the FROM clause:
SELECT c.first_name, c.last_name, c.email FROM customers AS c;
The AS keyword is optional. You can also write FROM customers c. The alias c now represents the customers table throughout the query. For a single table this is unnecessary, but when you join four or five tables in later lessons, aliases like c, o, p, and r make queries dramatically more readable and less error-prone.
4. The WHERE Clause: Filtering Rows
So far your queries have returned every row in the table. In practice, you almost never want that. A sales table with two years of data contains millions of rows. A customer table might have fifty thousand entries. The WHERE clause is how you tell MySQL which rows to keep and which to ignore. It is the filter. Without it, you are drinking from a fire hose. With it, you are sipping exactly what you need.
Basic WHERE syntax. The WHERE clause comes after the FROM clause:
SELECT first_name, last_name, email FROM customers WHERE is_active = TRUE;
This returns only rows where the is_active column contains TRUE, or 1. Customers who are inactive are excluded from the result entirely. The WHERE clause is evaluated before the SELECT clause, which means MySQL first filters the rows, then retrieves only the specified columns from the rows that survived the filter.
Filtering on text columns. Text values in the WHERE clause must be enclosed in single quotes:
SELECT first_name, last_name FROM customers WHERE last_name = 'Okafor';
Double quotes work in MySQL but single quotes are the SQL standard and work across all database systems. Build the habit of using single quotes for text values now. If the text value itself contains a single quote, for example a name like O'Brien, escape it with a backslash: 'O\'Brien'.
Filtering on date columns. Dates must also be enclosed in single quotes, and they should be in the standard YYYY-MM-DD format:
SELECT first_name, email, registration_date FROM customers WHERE registration_date >= '2026-01-01';
This returns all customers who registered on or after January 1, 2026. MySQL stores dates internally as serial numbers, but you write them as strings in the query, and MySQL converts them automatically for comparison.
Combining conditions with AND and OR. You can filter on multiple criteria:
SELECT first_name, last_name, email FROM customers WHERE is_active = TRUE AND registration_date >= '2026-01-01';
This returns only customers who are active AND registered this year. Both conditions must be true for a row to appear. If you use OR instead, a row appears if either condition is true:
SELECT first_name, last_name, email FROM customers WHERE is_active = TRUE OR registration_date >= '2026-01-01';
This returns customers who are active, regardless of when they registered, plus customers who registered this year, regardless of whether they are active. Be careful with OR. It expands your result set, sometimes dramatically. When combining AND and OR in the same query, use parentheses to make your logic explicit:
SELECT * FROM customers WHERE (is_active = TRUE AND registration_date >= '2026-01-01') OR last_name = 'Okafor';
This returns either active customers who registered this year, OR any customer with the last name Okafor regardless of activity or registration date. Without the parentheses, MySQL evaluates AND before OR by default, which produces a different and usually unintended result. Parentheses remove ambiguity.
5. Comparison Operators: =, !=, and Beyond
The WHERE clause relies on comparison operators to evaluate whether each row meets your condition. You must know these operators precisely, because a single wrong symbol can return thousands of incorrect rows or no rows at all.
Equality: = The single equals sign tests whether two values are the same. It works for numbers, text, dates, and booleans:
SELECT * FROM customers WHERE region = 'Lagos';
Inequality: != or <> These two symbols both mean not equal. != is more common in modern SQL, but <> is the ANSI standard and works in every database system. Use whichever you prefer, but be consistent:
SELECT * FROM customers WHERE region != 'Lagos';
SELECT * FROM customers WHERE region <> 'Lagos';
Both queries return customers who are not in Lagos. If the region column contains NULL values, those rows are excluded from both results, because NULL is not equal to anything, not even another NULL. You will learn how to handle NULL properly in the next lesson.
Greater than and less than: >, <, >=, <= These operators compare numeric and date values:
SELECT * FROM orders WHERE total_amount > 50000;
SELECT * FROM orders WHERE total_amount <= 10000;
SELECT * FROM customers WHERE registration_date < '2025-06-01';
The first query returns orders above fifty thousand naira. The second returns orders at or below ten thousand naira. The third returns customers who registered before June 1, 2025. Notice that dates are compared chronologically, so earlier dates are considered less than later dates.
BETWEEN: inclusive range testing. BETWEEN is a shorthand for checking whether a value falls within a range, including both endpoints:
SELECT * FROM orders WHERE total_amount BETWEEN 10000 AND 50000;
This returns orders where total_amount is greater than or equal to 10,000 and less than or equal to 50,000. It is exactly equivalent to writing total_amount >= 10000 AND total_amount <= 50000, but it is shorter and more readable. BETWEEN works for dates too:
SELECT * FROM orders WHERE order_date BETWEEN '2026-01-01' AND '2026-03-31';
IN: matching a list of values. IN checks whether a value matches any item in a specified list:
SELECT * FROM customers WHERE region IN ('Lagos', 'Abuja', 'Port Harcourt');
This returns customers in any of those three regions. It is cleaner and faster than chaining OR conditions: region = 'Lagos' OR region = 'Abuja' OR region = 'Port Harcourt'. You can also use NOT IN to exclude a list of values:
SELECT * FROM customers WHERE region NOT IN ('Lagos', 'Abuja');
LIKE: pattern matching for text. LIKE is used when you do not know the exact value but know a pattern. The percent sign % represents any number of characters, including zero. The underscore _ represents exactly one character:
SELECT * FROM customers WHERE first_name LIKE 'A%';
SELECT * FROM customers WHERE email LIKE '%@gmail.com';
SELECT * FROM customers WHERE last_name LIKE 'Okafo_';
The first query returns customers whose first name starts with A. The second returns customers whose email ends with @gmail.com. The third returns customers whose last name is Okafor, Okafoe, or any six-letter name starting with Okafo and ending with any single character. LIKE is powerful but slower than exact equality because it cannot use standard indexes efficiently. Use it for reporting and exploration, but avoid it in high-performance application queries when possible.
NOT: reversing any condition. You can negate any comparison by prefixing it with NOT:
SELECT * FROM customers WHERE NOT is_active = TRUE;
SELECT * FROM orders WHERE order_date NOT BETWEEN '2026-01-01' AND '2026-03-31';
The first query returns inactive customers. The second returns orders outside the first quarter of 2026. NOT is straightforward, but when combined with AND and OR, use parentheses to keep the logic clear.
Quick recap: SELECT * retrieves every column, use it only for exploration and always add LIMIT · SELECT specific columns by naming them, use AS for aliases, and CONCAT to combine columns · FROM specifies the table, use database.table for cross-database queries and aliases for readability · WHERE filters rows before they are returned, evaluated before SELECT · AND requires both conditions to be true, OR requires either, use parentheses to control precedence · = tests equality, != and <> test inequality, > < >= <= compare magnitude · BETWEEN tests inclusive ranges, IN tests membership in a list, LIKE tests text patterns with % and _ · NOT reverses any condition.
Using AI to Move Faster in SQL Query Writing
The syntax in this lesson is simple enough to memorize, but real-world queries grow complex quickly. AI can help you write correct SQL faster, debug errors, and translate business questions into working queries. Here is how to apply it without letting it replace your understanding.
1. Translate business questions into SQL with natural language.
If your manager asks, "How many active customers registered in Lagos after January 1st?" you can describe this to Copilot or ChatGPT: "Write a MySQL query that counts active customers in the Lagos region who registered on or after 2026-01-01." The AI will generate a query using COUNT, WHERE, AND, and the correct date format. Your job is to verify that the logic matches the question, that the column names match your actual schema, and that the date boundary is inclusive or exclusive as intended. Never run AI-generated SQL on production data without reading it first.
2. Use AI to explain why a query returns unexpected results.
If you run SELECT * FROM customers WHERE region != 'Lagos' and the result seems too small, paste the query and your schema into an AI assistant and ask: "Why might this query return fewer rows than expected even though I know there are customers outside Lagos?" AI will likely point out that NULL values in the region column are excluded by !=, which is a common beginner trap. This turns a frustrating debugging session into a five-minute learning moment.
3. Generate sample data for practice.
To practice the operators in this lesson, you need a table with enough variety to test =, !=, BETWEEN, IN, and LIKE meaningfully. Ask AI: "Generate twenty INSERT statements for a customers table with varied names, regions, registration dates, and active statuses so I can practice WHERE clause filtering." You will get realistic, diverse data that exercises every edge case, including NULLs, duplicate names, and dates spanning multiple years. Copy the INSERT statements into MySQL Workbench, run them, and then practice writing your own SELECT queries against the data.
4. Refactor verbose queries into cleaner alternatives.
If you write a query with multiple OR conditions like region = 'Lagos' OR region = 'Abuja' OR region = 'Port Harcourt', ask AI: "Refactor this MySQL query to use IN instead of OR, and explain why it is better." AI will rewrite the query and explain that IN is more readable, easier to maintain, and often more efficient for the database optimizer. This is how you learn best practices: by comparing your first draft to a refined version and understanding the difference.
5. Verify AI-generated queries against your schema before executing.
AI does not know your actual table names, column names, or data types unless you provide them. It might guess customer_id when your column is named cust_id. It might write registration_date >= '2026-01-01' when your column is actually a DATETIME and needs a time component. Always compare the generated query to your DESCRIBE output. Adjust column names, data types, and logic to match reality. Treat AI output as a first draft written by a competent but uninformed colleague, not as a finished product.
A habit worth building from this lesson onward: whenever you need to write a query with more than one condition, sketch the logic in plain English first, then ask AI to translate it to SQL, then audit the result against your schema and your understanding of AND, OR, and parentheses. This workflow, think, generate, verify, is faster than staring at a blank editor, and it ensures you still own the logic even when AI writes the syntax.
Next lesson: sorting results with ORDER BY, handling NULL values, and eliminating duplicates with DISTINCT.