| |

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

ElementPurpose
*All columns
columnA specific column
column AS aliasRename the column
expressionA computed value
function(column)A function call

The FROM Clause

SourceExample
TableFROM customers
ViewFROM active_customers
SubqueryFROM (SELECT ...) AS sub
Schema-qualifiedFROM public.customers

The Limiting Clauses

ClauseDatabase
LIMIT nPostgreSQL, MySQL, SQLite
TOP nSQL Server
FETCH FIRST n ROWS ONLYStandard SQL

The Result Set

PropertyDescription
RowsDetermined by FROM and WHERE
ColumnsDetermined by SELECT
EmptyNo rows matched
Not storedComputed 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

PitfallWhy It HappensFix
Too many rowsNo LIMITAdd LIMIT
Missing columnsUsed SELECT * and the table changedUse explicit column list
Duplicate column namesJoining tables with the same columnUse aliases
Cartesian productMissing join conditionUse explicit JOIN
Performance issuesSELECT * on a large tableSelect 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

ItemValue
SELECTSpecifies the columns to return
FROMSpecifies the source table
*All columns
ASColumn alias
`
LIMITMaximum rows
OFFSETSkip rows
Result setComputed on demand
Empty resultNo rows matched

Key takeaways:

  • Every SELECT statement has two required clauses: the column list and the FROM clause. The column list specifies what to return. The FROM clause 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 AS keyword is optional. The alias can be quoted if it contains spaces or special characters.
  • The FROM clause can reference tables, views, subqueries, and table functions. Multiple tables in the FROM clause produce a Cartesian product, which is rarely what the developer wants. Use explicit JOIN syntax to combine tables.
  • The LIMIT and OFFSET clauses control the number of rows returned. LIMIT restricts the number of rows. OFFSET skips 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 SELECT statement 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!