SQL • QUERYING DATA

SELECT Queries — Write SELECT queries to retrieve specific columns

Master the foundational SQL statement that drives every data retrieval operation in relational databases.

Historical Context & Motivation

The SELECT statement is the most frequently executed command in the history of relational databases, yet its existence stems from a single, elegant idea: data should be retrieved declaratively — you describe what you want, not how to get it. Before SQL, programmers navigated hierarchical and network databases using procedural, record-at-a-time code — a brittle, error-prone process tightly coupled to physical storage. The SELECT statement liberated application logic from storage mechanics and became the universal interface through which virtually all modern software communicates with structured data.

1970
Codd's Relational Model
Edgar F. Codd published A Relational Model of Data for Large Shared Data Banks at IBM, proposing that data be organized into relations (tables) and manipulated through a high-level, set-oriented language rather than procedural navigation.
1974
SEQUEL at IBM
Donald Chamberlin and Raymond Boyce designed SEQUEL (Structured English Query Language) as a practical implementation of Codd's relational algebra. The SELECT … FROM … WHERE pattern was central to its syntax from the very first prototype, System R.
1979
Oracle V2 Ships
Relational Software Inc. (later Oracle Corporation) released the first commercially available SQL database, proving that SELECT-based querying could scale to production workloads and establishing SQL as the de facto industry standard.
1986
ANSI SQL-86 Standard
ANSI adopted SQL as a formal standard (X3.135-1986), codifying SELECT syntax and ensuring cross-vendor portability. Subsequent revisions — SQL-92, SQL:1999, SQL:2003, SQL:2016, and SQL:2023 — have expanded functionality while preserving the core SELECT grammar.
2020s
SQL Everywhere
SELECT queries now execute across traditional RDBMS engines, cloud data warehouses (BigQuery, Snowflake, Redshift), streaming platforms (ksqlDB, Flink SQL), and even NoSQL systems like CockroachDB and DuckDB — confirming SQL's status as the universal data retrieval language.

Against this backdrop, the fundamental question this lesson addresses is straightforward but profound: given a table with potentially dozens of columns and millions of rows, how do you precisely express which columns you need, and why does that specificity matter for correctness, performance, and maintainability?

Core Principles & Definitions

At its essence, a SELECT query performs a projection — the relational-algebra operation that extracts a vertical slice of a relation by specifying which attributes (columns) to include in the result. Combined with a FROM clause that identifies the source relation, even the simplest SELECT statement embodies two of the three fundamental relational operations (projection and Cartesian product / relation reference). The following principles govern how column-level retrieval works in SQL.

1

Declarative Projection

The SELECT clause lists the desired columns by name (or expression). The database engine determines the optimal physical operations — index scans, sequential reads, column pruning — to deliver those columns. You never specify access paths.
2

Set-Based Semantics

A SELECT query returns a result set — a multiset (bag) of rows unless DISTINCT is applied. Each row in the result contains exactly the projected columns, preserving the relational closure property.
3

Column Aliasing

The AS keyword renames a column in the result set without altering the underlying schema. Aliases improve readability and are essential when expressions (computed columns) appear in the SELECT list.
4

SELECT * vs. Explicit Columns

Using SELECT * retrieves every column from the source table. While convenient for exploration, it introduces coupling to schema changes, increases I/O, and is discouraged in production code. Explicit column lists serve as self-documenting contracts.
5

Expression Columns

The SELECT list can contain arithmetic expressions, string functions, type casts, and even subqueries — each producing a computed column in the result. These derived values do not exist in the base table but are calculated on the fly during query execution.
KEY TAKEAWAY
Think of a SELECT query like a librarian filling a request slip. The FROM clause tells the librarian which shelf (table) to visit, and the SELECT list specifies exactly which chapters (columns) to photocopy. Asking for the entire book (SELECT *) when you only need the bibliography wastes time and paper — just as selecting unnecessary columns wastes network bandwidth, memory, and CPU cycles in a real DBMS.

Visual Explanation — Anatomy of a SELECT Statement

The diagram shows how the SELECT clause projects three outputs — two base columns (first_name, email) and one computed expression (salary * 12 AS annual_pay) — from the five-column employees table, yielding a narrower result set.

Notice that the result set contains only the columns explicitly named in the SELECT list. The id and last_name columns are absent because projection eliminates every attribute not requested. The computed column annual_pay does not exist in the base table; the engine evaluates the expression salary * 12 for each row and includes the result under the alias. This distinction between stored columns and derived columns is central to understanding the expressive power of the SELECT list.

How the SELECT Statement Executes

Although you write SELECT before FROM, the database engine processes clauses in a different logical execution order. Understanding this order is essential for writing correct queries and predicting results, especially once WHERE, GROUP BY, and ORDER BY are involved.

Logical Execution Order

  1. Step 1 — FROM: Identify the source table(s) and construct the initial working set of all rows and all columns.
  2. Step 2 — WHERE: Filter rows that do not satisfy the predicate (horizontal restriction / selection).
  3. Step 3 — GROUP BY: Partition remaining rows into groups based on grouping columns.
  4. Step 4 — HAVING: Filter groups that do not satisfy the aggregate predicate.
  5. Step 5 — SELECT: Evaluate expressions, apply aliases, and project the requested columns — this step occurs near the end, not at the beginning.
  6. Step 6 — ORDER BY: Sort the result set based on the specified column(s) or expression(s).
⚠️ Why does this matter for SELECT?
Because SELECT is logically evaluated after WHERE, you cannot reference a column alias defined in the SELECT list inside a WHERE clause. For example, SELECT salary * 12 AS annual_pay FROM employees WHERE annual_pay > 50000 will fail in most RDBMS engines because annual_pay does not exist when WHERE is evaluated. You must repeat the expression: WHERE salary * 12 > 50000.

Relational Algebra Mapping

PROJECTION
π_{col₁, col₂, …, colₙ}(R)
π (pi) denotes the projection operator. col₁ … colₙ are the attributes to retain, and R is the input relation. In SQL: SELECT col₁, col₂ FROM R.
PROJECTION WITH SELECTION
π_{col₁, col₂}(σ_{predicate}(R))
σ (sigma) denotes the selection (row-filter) operator. The composition reads: first filter rows of R by predicate, then project onto col₁, col₂. In SQL: SELECT col₁, col₂ FROM R WHERE predicate.

Syntax Variants & Column Expressions

The SELECT list supports a rich vocabulary of expressions beyond simple column references. Mastering these variants allows you to reshape query output without modifying the schema, compute derived metrics inline, and produce human-readable labels for application consumption. The table below categorizes the most common patterns with their SQL syntax and semantics.

Common SELECT list variants and their semantics
VariantSQL ExampleDescription
Single columnSELECT email FROM users;Retrieves one column from the table. Simplest form of projection.
Multiple columnsSELECT first_name, last_name, email FROM users;Comma-separated list selects multiple columns. Order in the SELECT list determines column order in the result.
All columns (*)SELECT * FROM users;Wildcard selects every column. Useful for ad-hoc exploration but discouraged in production queries.
Arithmetic expressionSELECT price, quantity, price * quantity AS total FROM orders;Computes a new column from existing numeric columns. Supports +, −, ×, / operators.
String concatenationSELECT first_name || ' ' || last_name AS full_name FROM users;Concatenates strings. Syntax varies: || (ANSI/PostgreSQL), CONCAT() (MySQL), + (SQL Server).
Column aliasSELECT email AS contact_email FROM users;Renames the column header in the result set. AS is optional in most dialects but improves readability.
DISTINCTSELECT DISTINCT department FROM employees;Removes duplicate rows from the result. Operates on the entire projected tuple, not just one column.
Left: SELECT * reads and transfers all six columns. Right: SELECT name, email reads only two, reducing I/O to roughly one-third. In columnar storage engines like Parquet or Redshift, the savings are even greater because unneeded column files are never opened.

Worked Example — Building a SELECT Query Step by Step

Suppose you are working with a products table that has the following schema: products(product_id INT, name VARCHAR(100), category VARCHAR(50), unit_price DECIMAL(10,2), stock_qty INT, supplier_id INT). Your task is to produce a report showing each product's name, its category, the unit price, and the total inventory value (unit price × stock quantity), with the inventory value displayed under a meaningful alias.

Constructing a Multi-Column SELECT with a Computed Expression
1
Step 1 — Identify the Source TableThe data resides in the products table. Write the FROM clause first to ground the query: FROM products.
2
Step 2 — List the Required Base ColumnsThe report calls for name, category, and unit_price. Place these in the SELECT list, separated by commas.
SELECT name, category, unit_price
3
Step 3 — Add the Computed ColumnTotal inventory value equals unit_price * stock_qty. Append this expression to the SELECT list with an alias for clarity.
unit_price * stock_qty AS inventory_value
4
Step 4 — Assemble the Complete QueryCombine all elements. The complete, syntactically valid SQL statement is:
SELECT name, category, unit_price, unit_price * stock_qty AS inventory_value FROM products;
5
Step 5 — Verify the ResultFor a product with name = 'Widget A', category = 'Hardware', unit_price = 12.50, and stock_qty = 200, the result row would be: ('Widget A', 'Hardware', 12.50, 2500.00). The engine evaluated 12.50 × 200 = 2500.00 and labeled the output column inventory_value.
inventory_value = 2500.00

SELECT * vs. Explicit Columns — Strengths & Limitations

Comparison of SELECT * versus explicit column selection
CriterionSELECT *Explicit Column List
Development speedFast for ad-hoc queries and data exploration — no need to remember column names.Slightly slower to type, but IDE autocompletion largely eliminates this cost.
Network I/OTransfers all columns, including large BLOBs or TEXT fields you may not need.Transfers only requested columns, reducing payload size significantly.
Schema resilienceAdding or removing a column silently changes the result set, potentially breaking downstream code.Query fails explicitly if a referenced column is dropped — surfacing errors early.
Readability / documentationReaders cannot tell which columns the consuming application actually uses.The SELECT list serves as living documentation of the data contract.
Columnar storage enginesForces the engine to read every column file — defeats the primary advantage of columnar format.Engine reads only the column files needed — orders-of-magnitude faster on wide tables.
Index-only scansRarely possible since all columns must be retrieved, typically requiring table access.If selected columns are covered by an index, the engine can satisfy the query without touching the heap — a significant performance win.
KEY TAKEAWAY
Think of SELECT * as ordering every item on a restaurant menu when you only want a salad. It is fine if you are genuinely sampling the kitchen's full range (exploratory queries in a REPL), but in a production application — where you serve the same meal thousands of times per second — you want a precise, explicit order that minimizes waste and ensures consistency even when the menu changes.

Connection to Advanced Query Techniques

The simple SELECT col₁, col₂ FROM table pattern you have learned forms the skeleton upon which all advanced SQL is built. Every subquery, common table expression (CTE), window function, and set operation ultimately resolves to a SELECT that projects specific columns. The table below maps each basic concept to its advanced counterpart, illustrating how proficiency with explicit column selection scales into more complex scenarios.

Mapping basic SELECT concepts to advanced SQL techniques
Basic ConceptAdvanced ExtensionHow They Connect
Column list in SELECTWindow functions: ROW_NUMBER() OVER (...)Window functions are additional expression columns in the SELECT list — they compute values across related rows without collapsing them.
Column alias (AS)Common Table Expressions (WITH … AS)CTEs name an entire subquery result, much like AS names a single column — both improve readability and enable reuse.
Arithmetic expressionsCASE expressions and conditional logicCASE extends inline computation from arithmetic to branching logic, producing derived columns based on conditions.
SELECT DISTINCTGROUP BY with aggregatesDISTINCT eliminates duplicates post-projection; GROUP BY partitions rows for aggregation — both reduce result cardinality.
Single-table FROMJOINs across multiple tablesWhen joining, the SELECT list must qualify columns with table aliases (e.g., e.name, d.dept_name) to resolve ambiguity — a direct extension of explicit column selection.

As you progress through topics like JOINs, subqueries, and aggregate functions, you will find that the discipline of naming exactly the columns you need becomes even more critical. In multi-table queries, ambiguous column references cause compilation errors, and in analytical queries involving window functions, every column in the SELECT list interacts with the PARTITION BY and ORDER BY specifications. Building the habit of explicit projection now pays compounding dividends as query complexity increases.

Practice Problems

The following problems reference a students table with the schema: students(student_id INT, first_name VARCHAR(50), last_name VARCHAR(50), major VARCHAR(80), gpa DECIMAL(3,2), credits_earned INT, enrollment_year INT). Work through them in order; difficulty escalates from conceptual to open-ended analysis.

PROBLEM 1CONCEPTUAL
Explain the difference between SELECT * and SELECT first_name, last_name when executed against the students table. Under what circumstances is each form appropriate, and what risks does SELECT * introduce in a production environment?
PROBLEM 2BASIC CALCULATION
Write a SELECT query that retrieves each student's first_name, last_name, and gpa from the students table.
PROBLEM 3INTERMEDIATE
Write a query that returns each student's full name as a single column labeled full_name (first name followed by a space then last name), along with a column remaining_credits computed as 120 − credits_earned (assuming 120 credits are needed to graduate). Use ANSI SQL concatenation syntax.
PROBLEM 4APPLIED
A university registrar application needs to display a roster with last_name, major, and a column gpa_category that shows 'Honors' when gpa >= 3.5, 'Good Standing' when gpa >= 2.0, and 'Probation' otherwise. Write the query using a CASE expression in the SELECT list.
PROBLEM 5CRITICAL THINKING
Suppose a colleague writes SELECT DISTINCT major FROM students; and another writes SELECT major FROM students GROUP BY major;. Analyze whether these two queries are semantically equivalent in terms of their result sets. Then discuss a scenario where one form would be preferable over the other from both a correctness and a performance perspective.

Lesson Summary

The SELECT statement is the primary mechanism for retrieving data from a relational database, and the column list within it performs the relational-algebra operation of projection — extracting a vertical slice of the source relation. You can specify individual base columns, arithmetic or string expressions that compute derived values, and column aliases via the AS keyword to rename result columns. The wildcard (*) retrieves all columns but should be reserved for ad-hoc exploration due to its performance, readability, and schema-resilience drawbacks.

Understanding the logical execution order — FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY — clarifies why aliases defined in the SELECT list are not available in WHERE clauses and prepares you for the advanced constructs that build upon explicit column selection: JOINs, window functions, CTEs, and CASE expressions. By always naming exactly the columns your application requires, you write queries that are faster, more maintainable, and more resilient to schema evolution.

Varsity Tutors • SQL • SELECT Queries — Write SELECT queries to retrieve specific columns