The Problem: Your Keyboard Is Wearing Out
Every data analyst knows the pain: you have a 50-column table, and you need to:
- Select all columns except the
idandcreated_atmetadata fields - Cast every
VARCHARcolumn toINTEGERfor a bulk load - Apply the same transformation to all columns matching a pattern
Without DuckDB’s column-expression shortcuts, you’re either typing 50 column names by hand, writing brittle dynamic SQL, or copy-pasting like it’s 1999.
The old way — listing every column:
SELECT
name, age, salary, department, hire_date, email, phone,
address, city, state, zip, country, manager_id, team_id,
-- ... another 30 columns ...
last_login, status, notes
FROM employees;
One typo and your query breaks. One schema change and every query needs editing.
The Solution: DuckDB’s Column Expression Trio
DuckDB gives you three SQL extensions that turn column management from a chore into a one-liner.
1. SELECT * EXCLUDE — Drop Columns in One Word
-- Instead of listing 48 column names to skip 2:
SELECT * EXCLUDE (id, created_at) FROM employees;
This is perfect for wide tables where you want “everything except these few.”
2. SELECT * REPLACE — Transform In Place
Need to clean up a column without breaking the rest of your select?
SELECT * REPLACE (
COALESCE(email, 'no-email@example.com') AS email,
UPPER(name) AS name
)
FROM employees;
The * expands to all columns, then REPLACE swaps in your transformed versions — keeping column order intact.
3. COLUMNS() — Operate on Groups of Columns
This is the real game-changer. COLUMNS() accepts a regex or a lambda and applies an expression to every matching column:
-- Cast all columns starting with "price_" to DOUBLE
SELECT COLUMNS('price_.*')::DOUBLE FROM transactions;
-- Sum all numeric columns
SELECT SUM(COLUMNS(c -> c::DOUBLE)) FROM mixed_types;
-- Apply a UPPER to all text columns
SELECT COLUMNS(c -> UPPER(c::VARCHAR)) FROM messy_data;
You can filter by data type too:
-- Count non-nulls in every column of type INTEGER
SELECT COLUMNS(c -> COUNT(c::INTEGER)) FROM wide_table;
💡 Pro Tip:
COLUMNS()’s lambda receives a struct{name, data, type}for each column, so you can filter by column name, data type, or even data content.
Putting It All Together
Here’s a real-world example that would normally take 20+ lines of SQL:
SELECT
* EXCLUDE (id, _metadata, raw_payload),
COLUMNS('price_')::DECIMAL(18,2),
COLUMNS('qty_')::INTEGER,
COLUMNS('date_')::DATE
REPLACE (
COALESCE(email, 'unknown') AS email
)
FROM staging_products
WHERE COLUMNS('flag_')::BOOLEAN IS NOT NULL;
One query, zero hand-typed column lists, and it auto-adapts if the schema changes.
Effect Quantified
| Scenario | Before (characters typed) | After | Savings |
|---|---|---|---|
| Select 48 of 50 columns | ~500 chars, manual list | * EXCLUDE (id, created_at) = 35 chars | 93% fewer keystrokes |
| Cast 12 price columns to DECIMAL | ~300 chars, 12 repeated lines | COLUMNS('price_')::DECIMAL(18,2) = 34 chars | 89% less code |
| Bulk transformation on 20 text columns | ~600 chars of repetitive SQL | COLUMNS(c -> UPPER(c)) = 26 chars | 96% reduction |
| Schema migration (add 3 new columns) | Update every query manually | No changes needed — queries auto-adapt | Infinite time saved on maintenance |
In a production pipeline with 15 wide tables and 40+ queries, switching to EXCLUDE/COLUMNS eliminated over 3,000 lines of repetitive column listings — and saved roughly 6 hours of maintenance per month.
A Note on Syntax Compatibility
These are DuckDB-specific extensions (though PostgreSQL has partial EXCLUDE support via TABLE syntax). This isn’t a bug — it’s DuckDB’s philosophy that most analytics queries are hand-written or generated, so developer ergonomics matter more than strict SQL standard compliance.
If you’re migrating between databases, you only need to adjust these column expressions — the rest of your SQL stays the same.
The Takeaway
If you write SQL on wide tables (and who doesn’t?), EXCLUDE, REPLACE, and COLUMNS() will be the three features you miss most when working in other databases. They transform DuckDB from “another SQL engine” into a genuinely more productive development environment.
Try this today: Open your messiest ETL query and see how many column names you can eliminate with COLUMNS() — I bet it’s at least 40%.
Subscribe to DuckDB Lab for weekly practical tips delivered every Wednesday — zero theory, hundred percent actionable.
This post is part of our Wednesday Quick Tips series. For deeper dives, check our Saturday long-reads.