SQL 14 🛢️ Querying Data with SELECT and FROM
Every SQL query that reads data starts with SELECT. It is the statement that retrieves rows from one or more tables and returns them as a result set. The SELECT statement is part of DQL — Data Query Language — and it is the most frequently used statement in SQL. Every application that displays data, every report that summarizes information, every dashboard that shows a metric depends on a SELECT statement somewhere in the chain.
The previous chapters covered the structure of tables, the constraints that define the data, and the data types that the columns hold. This chapter covers the statement that reads that data. The SELECT statement has two required clauses: the column list and the FROM clause. Everything else — filtering, sorting, grouping, joining — is built on top of these two clauses.
Key point: The SELECT statement is declarative. You describe what data you want, not how to get it. The query optimizer in the database decides the best way to execute the query — which indexes to use, which order to join the tables, which filtering strategy to apply. The result is the same regardless of how the database reaches it. This separation between declaration and execution is what makes SQL powerful and portable.
Why the SELECT statement matters
A table stores data. The SELECT statement retrieves it. Without SELECT, the data is inaccessible. Every other DML statement — INSERT, UPDATE, DELETE — modifies the data, but only SELECT reads it.
The read problem. An application needs to display a list of users. The SELECT statement retrieves the users. The application formats them. The data flows from the database to the application through the result set.
The report problem. A manager needs a summary of sales by region. The SELECT statement aggregates the data. The report is generated from the result set.
The verification problem. A developer needs to check whether a migration worked. The SELECT statement verifies the data. The developer compares the expected result to the actual result.
The exploration problem. A developer joins a new team and needs to understand the schema. The SELECT statement explores the data. The developer sees the columns, the types, and the relationships in the result set.
The trade-off. The SELECT statement is powerful but can be abused. A SELECT * on a table with millions of rows and no WHERE clause retrieves everything. The query is slow, the result set is huge, and the network is saturated. The power comes with the responsibility to use the statement carefully.
a. The Basic SELECT Statement
The simplest SELECT statement retrieves all columns and all rows from a single table.
SELECT * FROM customers;
The asterisk (*) is a wildcard that means “all columns.” The FROM clause specifies the table. The result set contains every column and every row in the table.
The explicit column list is the recommended form. It retrieves only the columns that are needed.
SELECT customer_id, first_name, last_name, email FROM customers;
The explicit list has three advantages. It reduces the amount of data transferred. It makes the query self-documenting. It prevents the query from breaking when a column is added or removed from the table.
The SELECT statement can retrieve literal values that are not in any table.
SELECT 1;
SELECT 'Hello, World';
SELECT CURRENT_DATE;
These queries are useful for testing. They return a single row with the specified value.
The column names in the result set can be aliased with the AS keyword.
SELECT
customer_id AS id,
first_name AS first,
last_name AS last,
email AS contact
FROM customers;
The alias changes the column name in the result set. The AS keyword is optional. customer_id id is equivalent to customer_id AS id.
The alias can also be quoted if it contains spaces or special characters.
SELECT
first_name || ' ' || last_name AS "Full Name"
FROM customers;
The || operator concatenates strings in PostgreSQL, Oracle, and SQLite. MySQL and SQL Server use CONCAT() instead.
b. The FROM Clause
The FROM clause specifies the source of the data. It can reference a table, a view, a subquery, or a table function.
SELECT * FROM customers;
SELECT * FROM active_customers;
SELECT * FROM (SELECT * FROM customers WHERE is_active = TRUE) AS active;
A view is a stored query that behaves like a table. A subquery is a query nested inside another query. A table function is a function that returns a set of rows.
The FROM clause can reference multiple tables. When it does, the result is a Cartesian product — every row from the first table combined with every row from the second.
SELECT * FROM customers, orders;
If customers has 100 rows and orders has 500 rows, the result has 50,000 rows. The Cartesian product is rarely what the developer wants. It is usually a mistake that happens when the join condition is forgotten. The explicit JOIN syntax is preferred for combining tables.
The FROM clause can also specify a schema-qualified table name.
SELECT * FROM public.customers;
The schema prefix is useful when the search path includes multiple schemas or when the table name is ambiguous.
c. The Result Set
The result of a SELECT statement is a result set. The result set is a table with rows and columns. The columns are determined by the select list. The rows are determined by the FROM clause and the other clauses.
The result set is not stored in the database. It is computed on demand and returned to the client. The client processes the result set and displays it, stores it, or passes it to another program.
The SELECT statement can return zero rows, one row, or many rows. A query that returns zero rows is not an error — it simply means that no rows matched the criteria.
SELECT * FROM customers WHERE customer_id = 999;
If no customer has an id of 999, the result set is empty. The application handles the empty result.
The result set can be limited with the LIMIT clause in PostgreSQL and MySQL.
SELECT * FROM customers LIMIT 10;
The LIMIT clause restricts the number of rows returned. The TOP clause is the equivalent in SQL Server.
SELECT TOP 10 * FROM customers;
The FETCH FIRST clause is the standard SQL syntax.
SELECT * FROM customers FETCH FIRST 10 ROWS ONLY;
The OFFSET clause skips a number of rows before returning the result.
SELECT * FROM customers ORDER BY customer_id LIMIT 10 OFFSET 20;
The OFFSET clause is useful for pagination. The combination of LIMIT and OFFSET retrieves a page of rows.
Complete Example Session
This session demonstrates the SELECT and FROM clauses on a small database.
-- ============================================
-- PART 1: THE TABLES
-- ============================================
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
city VARCHAR(50)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total DECIMAL(10, 2) NOT NULL
);
INSERT INTO customers VALUES
(1, 'Alice', 'Johnson', 'alice@example.com', 'New York'),
(2, 'Bob', 'Smith', 'bob@example.com', 'Chicago'),
(3, 'Carol', 'Williams', 'carol@example.com', 'New York'),
(4, 'Dave', 'Brown', 'dave@example.com', NULL);
INSERT INTO orders VALUES
(1001, 1, '2026-01-15', 99.99),
(1002, 1, '2026-02-20', 149.99),
(1003, 2, '2026-01-10', 49.99),
(1004, 3, '2026-03-05', 299.99);
-- ============================================
-- PART 2: SELECT ALL COLUMNS
-- ============================================
SELECT * FROM customers;
-- Output:
-- customer_id | first_name | last_name | email | city
-- -------------+------------+-----------+-------------------+----------
-- 1 | Alice | Johnson | alice@example.com | New York
-- 2 | Bob | Smith | bob@example.com | Chicago
-- 3 | Carol | Williams | carol@example.com | New York
-- 4 | Dave | Brown | dave@example.com | null
-- ============================================
-- PART 3: SELECT SPECIFIC COLUMNS
-- ============================================
SELECT customer_id, first_name, last_name FROM customers;
-- Output:
-- customer_id | first_name | last_name
-- -------------+------------+-----------
-- 1 | Alice | Johnson
-- 2 | Bob | Smith
-- 3 | Carol | Williams
-- 4 | Dave | Brown
-- ============================================
-- PART 4: SELECT WITH ALIASES
-- ============================================
SELECT
customer_id AS id,
first_name AS first,
last_name AS last,
email AS contact
FROM customers;
-- Output:
-- id | first | last | contact
-- ----+-------+----------+-------------------
-- 1 | Alice | Johnson | alice@example.com
-- 2 | Bob | Smith | bob@example.com
-- 3 | Carol | Williams | carol@example.com
-- 4 | Dave | Brown | dave@example.com
-- ============================================
-- PART 5: SELECT WITH EXPRESSIONS
-- ============================================
SELECT
customer_id,
first_name || ' ' || last_name AS full_name,
UPPER(email) AS email_upper
FROM customers;
-- Output:
-- customer_id | full_name | email_upper
-- -------------+---------------+-------------------
-- 1 | Alice Johnson | ALICE@EXAMPLE.COM
-- 2 | Bob Smith | BOB@EXAMPLE.COM
-- 3 | Carol Williams| CAROL@EXAMPLE.COM
-- 4 | Dave Brown | DAVE@EXAMPLE.COM
-- ============================================
-- PART 6: SELECT FROM MULTIPLE TABLES
-- ============================================
SELECT * FROM customers, orders;
-- Output: 16 rows (4 customers × 4 orders)
-- The Cartesian product is rarely what you want.
-- ============================================
-- PART 7: SELECT WITH LIMIT
-- ============================================
SELECT * FROM orders ORDER BY order_date LIMIT 2;
-- Output:
-- order_id | customer_id | order_date | total
-- ----------+-------------+------------+--------
-- 1003 | 2 | 2026-01-10 | 49.99
-- 1001 | 1 | 2026-01-15 | 99.99
-- ============================================
-- PART 8: SELECT WITH OFFSET
-- ============================================
SELECT * FROM orders ORDER BY order_date LIMIT 2 OFFSET 2;
-- Output:
-- order_id | customer_id | order_date | total
-- ----------+-------------+------------+--------
-- 1002 | 1 | 2026-02-20 | 149.99
-- 1004 | 3 | 2026-03-05 | 299.99
-- ============================================
-- PART 9: SELECT WITH NO ROWS
-- ============================================
SELECT * FROM customers WHERE customer_id = 999;
-- Output: empty result set
-- ============================================
-- PART 10: THE SELECT AND FROM SUMMARY
-- ============================================
-- SELECT: the columns to return
-- FROM: the table or tables to query
-- *: all columns
-- AS: column alias
-- ||: string concatenation
-- LIMIT: maximum rows
-- OFFSET: skip rows
The ten parts cover the tables, selecting all columns, selecting specific columns, selecting with aliases, selecting with expressions, selecting from multiple tables, selecting with limit, selecting with offset, selecting with no rows, and the summary.
Quick Reference
The SELECT Clause
| Element | Purpose |
|---|---|
* | All columns |
column | A specific column |
column AS alias | Rename the column |
expression | A computed value |
function(column) | A function call |
The FROM Clause
| Source | Example |
|---|---|
| Table | FROM customers |
| View | FROM active_customers |
| Subquery | FROM (SELECT ...) AS sub |
| Schema-qualified | FROM public.customers |
The Limiting Clauses
| Clause | Database |
|---|---|
LIMIT n | PostgreSQL, MySQL, SQLite |
TOP n | SQL Server |
FETCH FIRST n ROWS ONLY | Standard SQL |
The Result Set
| Property | Description |
|---|---|
| Rows | Determined by FROM and WHERE |
| Columns | Determined by SELECT |
| Empty | No rows matched |
| Not stored | Computed on demand |
Best Practices
✅ Do This:
-- Use explicit column lists
SELECT customer_id, first_name, last_name FROM customers; -- ✅
-- Use aliases for computed columns
SELECT first_name || ' ' || last_name AS full_name FROM customers; -- ✅
-- Use LIMIT for pagination
SELECT * FROM orders ORDER BY order_date LIMIT 10; -- ✅
-- Use the schema prefix when needed
SELECT * FROM public.customers; -- ✅
❌ Don’t Do This:
-- Don't use SELECT * in production code
SELECT * FROM customers; -- ❌
-- Don't use the Cartesian product by accident
SELECT * FROM customers, orders; -- missing join condition -- ❌
-- Don't use SELECT without LIMIT on large tables
SELECT * FROM orders; -- millions of rows -- ❌
-- Don't use reserved words as aliases
SELECT customer_id AS order FROM customers; -- ORDER is reserved -- ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Too many rows | No LIMIT | Add LIMIT |
| Missing columns | Used SELECT * and the table changed | Use explicit column list |
| Duplicate column names | Joining tables with the same column | Use aliases |
| Cartesian product | Missing join condition | Use explicit JOIN |
| Performance issues | SELECT * on a large table | Select only the needed columns |
Real-World Examples
1. Select All
SELECT * FROM customers;
2. Select Specific Columns
SELECT customer_id, first_name, last_name FROM customers;
3. Select with Alias
SELECT first_name AS first FROM customers;
4. Select with Expression
SELECT price * quantity AS total FROM order_items;
5. Select with Concatenation
SELECT first_name || ' ' || last_name AS full_name FROM customers;
6. Select with Function
SELECT UPPER(email) FROM customers;
7. Select with Limit
SELECT * FROM customers LIMIT 10;
8. Select with Offset
SELECT * FROM customers LIMIT 10 OFFSET 20;
9. Select from View
SELECT * FROM active_customers;
10. Select from Subquery
SELECT * FROM (SELECT * FROM customers WHERE city = 'New York') AS ny;
Visual
The SELECT Statement
┌──────────────────────────────────────────────┐
│ SELECT column1, column2 │
│ FROM table_name │
│ │
│ The SELECT clause specifies the columns. │
│ The FROM clause specifies the table. │
│ │
│ The result is a result set. │
│ │
└──────────────────────────────────────────────┘
The Result Set
┌──────────────────────────────────────────────┐
│ customers │
│ ┌────────────┬────────────┬───────────┐ │
│ │customer_id │first_name │last_name │ │
│ ├────────────┼────────────┼───────────┤ │
│ │1 │Alice │Johnson │ │
│ │2 │Bob │Smith │ │
│ │3 │Carol │Williams │ │
│ └────────────┴────────────┴───────────┘ │
│ │
│ SELECT customer_id, first_name FROM customers│
│ │ │
│ ▼ │
│ Result Set │
│ ┌────────────┬────────────┐ │
│ │customer_id │first_name │ │
│ ├────────────┼────────────┤ │
│ │1 │Alice │ │
│ │2 │Bob │ │
│ │3 │Carol │ │
│ └────────────┴────────────┘ │
│ │
└──────────────────────────────────────────────┘
The Cartesian Product
┌──────────────────────────────────────────────┐
│ customers (4 rows) │
│ × │
│ orders (4 rows) │
│ = │
│ 16 rows │
│ │
│ Each customer is combined with each order. │
│ The result is rarely what you want. │
│ Use JOIN to combine tables correctly. │
│ │
└──────────────────────────────────────────────┘
The Pagination
┌──────────────────────────────────────────────┐
│ PAGINATION │
│ │
│ Page 1: LIMIT 10 OFFSET 0 │
│ Page 2: LIMIT 10 OFFSET 10 │
│ Page 3: LIMIT 10 OFFSET 20 │
│ │
│ The LIMIT is the page size. │
│ The OFFSET is the page number × page size. │
│ │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
SELECT | Specifies the columns to return |
FROM | Specifies the source table |
* | All columns |
AS | Column alias |
| ` | |
LIMIT | Maximum rows |
OFFSET | Skip rows |
| Result set | Computed on demand |
| Empty result | No rows matched |
Key takeaways:
- Every
SELECTstatement has two required clauses: the column list and theFROMclause. The column list specifies what to return. TheFROMclause specifies where to get it. Everything else is built on these two clauses . SELECT *returns all columns. The explicit column list is preferred because it reduces data transfer, documents the query, and prevents breakage when the table changes.- Aliases rename the columns in the result set. The
ASkeyword is optional. The alias can be quoted if it contains spaces or special characters. - The
FROMclause can reference tables, views, subqueries, and table functions. Multiple tables in theFROMclause produce a Cartesian product, which is rarely what the developer wants. Use explicitJOINsyntax to combine tables. - The
LIMITandOFFSETclauses control the number of rows returned.LIMITrestricts the number of rows.OFFSETskips rows. The combination is used for pagination. - The result set is computed on demand and not stored. The result set is returned to the client, which processes it. An empty result set is not an error — it means no rows matched the criteria.
- The
SELECTstatement is declarative. The developer describes what data is wanted, not how to get it. The query optimizer decides the best way to execute the query.
Remember: The SELECT statement is the foundation of every query. It retrieves the data. The FROM clause specifies the source. The column list specifies the columns. The aliases rename them. The LIMIT and OFFSET control the number of rows. The result set is the output. Every other clause — WHERE, ORDER BY, GROUP BY, JOIN — is built on top of these two clauses. The SELECT and FROM are the first things to learn and the last things to forget.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!