12-postgresTermsLevel_03SELECT vs. Column List

SELECT * vs. Column List

Level 3 — CRUD Operations (The Four Pillars of SQL) The comparison and best practice standard contrasting selecting all table columns (SELECT *) with explicitly listing target columns (SELECT name, email).


1. Prerequisites

  • SELECT — The baseline data retrieval statement.

2. Term Category

SQL Command / Clause (Projection Performance Optimization): SELECT * vs Explicit Columns contrasts fetching all columns against selecting specific required attributes to minimize network I/O.


3. Explanation

Environment Context

  • Universal Standard (SELECT * is convenient for local CLI debugging but is considered a dangerous anti-pattern inside production application codebases).

(1) Design Motivation — "Why did we design this?"

When writing SQL, the asterisk (*) acts as a wildcard meaning "all columns."

Writing SELECT * FROM users; is fast, convenient, and requires less typing than writing out ten separate column names.

However, in production application backend code, using SELECT * is a severe performance and security risk:

  1. Wasted Network Bandwidth & RAM: If your users table has a bio column storing paragraphs of text or a profile_pic binary field, fetching them when you only wanted to show a list of usernames wastes massive amounts of database memory, web server RAM, and network bandwidth.
  2. App Fragility (Code Breaks): If your backend code maps database rows directly to local objects, and database administrators add or delete columns from the table, your application's object models will mismatch, causing server crashes.
  3. Bypassing Index Optimizations: If you only query columns that are indexed (e.g., querying email which has a B-Tree index), Postgres can read the value directly from the index in memory. This is called an Index-Only Scan and takes microseconds. If you write SELECT *, Postgres is forced to perform slow physical disk reads to fetch the non-indexed columns.

(2) When to use what

  • SELECT *: Only use when run manually inside psql or pgAdmin to quickly explore what columns exist in a table.
  • Explicit Column List: Use always in your application code (NodeJS, Python APIs, database migrations).

(3) Reality Metaphor

Imagine ordering groceries:

  • SELECT * is like calling the supermarket and saying: "Bring me the entire inventory of your store." You only wanted a carton of milk, but now a flatbed truck is dumping thousands of boxes on your lawn, clogging traffic and costing you a fortune.
  • Column List is like giving the store a precise shopping list: "Bring me 1 carton of milk." It is fast, cheap, and fits in a small bag.

(4) Code Examples

Bloated Query vs. Optimized Query

Assume we have a table with a massive text column:

CREATE TABLE wiki_pages (
  id INT PRIMARY KEY,
  title VARCHAR(200),
  author VARCHAR(100),
  body_content TEXT -- Can hold megabytes of text
);

Anti-Pattern (Slow & Wastes Memory):

-- BAD: If we only want to show a index list of titles, 
-- this fetches megabytes of body_content for every single row!
SELECT * FROM wiki_pages;

Best Practice (Fast & Secure):

-- GOOD: Fetches only a few bytes per row, running up to 100x faster!
SELECT title, author FROM wiki_pages;

4. Common Mistakes & Pitfalls

Mistake 1: Using SELECT * in production API routes because "it saves development typing time"

The mistake: Writing a database model function in NodeJS that fetches everything:

// BAD: Fetches password hashes, secure tokens, and huge text blocks 
// and accidentally exposes them to the public API!
const users = await db.query('SELECT * FROM users');
res.json(users.rows);

Why it's wrong: Besides the memory and network costs, SELECT * is a major security leak. If your table stores password hashes (password_hash) or private session tokens, SELECT * pulls these into memory. If you serialize the database row directly to JSON and send it to the frontend, you will leak credentials to the public web.

Fix: Always explicitly name only the columns that your code actually needs to process.

// GOOD: Safe, secure, and performant
const users = await db.query('SELECT id, username, email FROM users');
res.json(users.rows);

Mistake 2: Using SELECT * in Production API Endpoints Disabling Covered Query Optimizations

The mistake: Executing SELECT * FROM users WHERE email = 'a@ex.com' when index { email, id } exists.

Why it's wrong: SELECT * forces fetching the full row from disk (HEAP read). Specifying SELECT id, email allows the query engine to execute a Covered Index Only Scan.

Incorrect:

SELECT * FROM users WHERE email = 'a@ex.com'; -- ❌ Disk HEAP read!

Fix:

SELECT id, email FROM users WHERE email = 'a@ex.com'; -- Covered Index Only Scan

Mistake 3: Breaking Application Code when Table Columns Are Re-Ordered or Added via SELECT *

The mistake: Binding positional array rows in code after executing SELECT *.

Why it's wrong: If a schema migration inserts a new column in the middle of a table, positional array indices in code shift, causing runtime application bugs.

Incorrect:

const [id, name, email] = row; // ❌ Breaks if new column is added!

Fix:

SELECT id, name, email FROM users; -- Immutable explicit column list

5. Practice Exercises

Exercise 1: Explicit Column Selection Optimization

Scenario: Refactor a bloated SELECT * query returning 30 columns on a 1,000,000 row table to return ONLY id and email.

Requirements:

  1. Compare SELECT * vs SELECT id, email.
Answer

Implementation

-- ❌ Bloated projection (transfers unneeded text/bytea columns over network)
-- SELECT * FROM users;

-- ✅ Optimized explicit column projection
SELECT id, email 
FROM users;

Technical Explanation

  1. SELECT * fetches every column, including large TEXT, JSONB, or BYTEA blobs, consuming excess RAM and network I/O.
  2. Explicit column selection reduces network payload sizes significantly.
  3. Allows PostgreSQL to execute Index-Only Scans (Covered Queries).

Exercise 2: Enabling Index-Only Scans via Projection

Scenario: Demonstrate how explicit column projection enables an Index-Only Scan using index { email: 1, username: 1 }.

Requirements:

  1. Execute EXPLAIN ANALYZE SELECT email, username FROM users WHERE email = 'alice@example.com'.
Answer

Implementation

EXPLAIN ANALYZE 
SELECT email, username 
FROM users 
WHERE email = 'alice@example.com';

Technical Explanation

  1. If query projects ONLY fields stored in the B-tree index, PostgreSQL reads keys directly from the index (Index-Only Scan).
  2. SELECT * forces PostgreSQL to fetch raw table heap pages from disk (Heap Fetches).
  3. Critical performance rule.

Exercise 3: API Schema Safety Trade-Offs

Scenario: Explain why using SELECT * in production API handlers causes breaking changes when new table columns are added.

Requirements:

  1. Contrast explicit interface projection vs raw table SELECT *.
Answer

Implementation

API Schema Safety Analysis:
- SELECT * exposes internal/sensitive new columns (e.g. 'password_hash') added during database migrations to client JSON outputs automatically.
- Explicit column lists (SELECT id, username, email) guarantee stable, secure API contracts.

Technical Explanation

  1. Explicit projections prevent accidental data exposure when new columns are added to tables.
  2. Hardens backend API contract security.
  3. Production database practice.

  • SELECT — The parent query command.

7. Key Takeaways

  • SELECT * retrieves all columns in a table; Column List retrieves specific ones.
  • Asterisk queries waste network bandwidth, database RAM, and client memory.
  • SELECT * exposes databases to security leaks (e.g. passwords, tokens).
  • Explicit column lists allow the database to use fast Index-Only Scans.
  • Always use explicit column lists in production backend application scripts.
Built with LogoFlowershow