A derived table is a query result used as a table input within an enclosing statement, usually written as a parenthesised SELECT in FROM.
A query can first build one total per customer and then join those totals to the complete customer list. The inner result is a derived table. Breaking the work into clear result shapes can prevent detail joins from multiplying measures.
This reference adds detail to the main course. You can read it independently: the small database below supplies every table and row used in the examples. Start with the question each query answers, then compare the returned values with the data rather than memorising a line of syntax.
Set up the practice database
Run this setup once in a new practice database, before the examples. It uses four customers and six sales. Ana has two sales, Ben has one, Cara has two, and Dev has none.
Sale 106 has no customer link; sale 104 has an unknown amount. These deliberate test cases expose missing information and unmatched relationships.
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name VARCHAR(40) NOT NULL,
city VARCHAR(40),
manager_id INTEGER
);
CREATE TABLE sales (
id INTEGER PRIMARY KEY,
customer_id INTEGER,
amount DECIMAL(10,2),
status VARCHAR(20) NOT NULL,
created_on DATE NOT NULL
);
INSERT INTO customers (id,name,city,manager_id) VALUES
(1,'Ana','Leeds',NULL),(2,'Ben','Leeds',1),
(3,'Cara','Porto',1),(4,'Dev',NULL,2);
INSERT INTO sales (id,customer_id,amount,status,created_on) VALUES
(101,1,10,'paid','2026-01-01'),
(102,1,30,'pending','2026-01-02'),
(103,2,20,'paid','2026-01-02'),
(104,3,NULL,'pending','2026-02-01'),
(105,3,20,'paid','2026-02-02'),
(106,NULL,5,'paid','2026-02-03');An amount of zero would be a known value. The NULL amount here means the value is unknown. A customer with no matching sale differs from a sale whose customer_id is NULL.
The primary keys identify individual records even when amounts or cities repeat. Dates use year-month-day format. The examples specify sorting whenever order matters; insertion order alone does not guarantee display order.
How Derived tables works
Place a SELECT in parentheses in FROM and give it a table alias. The outer query can refer to its selected column names, including useful expression aliases such as known_total. It cannot normally reach source fields the inner SELECT did not expose. Think of the inner output as an interface: its rows and columns are the contract available to the next query level.
Preaggregation is a common use. SELECT customer_id,SUM(amount) AS known_total FROM sales GROUP BY customer_id produces one row per customer link. Joining that result to customers avoids repeating one customer total for every sale when the outer report needs customer-level rows. If two separate one-to-many inputs are needed, aggregate each at the intended joining grain before combining them to avoid a many-by-many multiplication.
Filtering on calculated columns
A derived table also establishes a new scope for selected aliases. An inner calculation amount*2 AS doubled_amount can be filtered by the outer WHERE doubled_amount>40. This is useful because ordinary same-level WHERE cannot portably refer to a newly selected alias. The outer filter sees actual output columns from the inner query rather than relying on engine-specific alias extensions.
A derived table is not automatically a stored temporary table. The optimiser may inline, merge or materialise it according to the query and engine. Do not infer physical execution order solely from the written nesting. An inner ORDER BY also does not generally promise final output order unless it participates in an appropriate limiting operation; write a final outer ORDER BY. Ordinary derived tables are not universally allowed to reference prior FROM items without lateral or apply features, whose syntax and availability differ. Keep simple derived inputs independent unless the engine-specific correlation mechanism is intentional.
MySQL, PostgreSQL, SQL Server and SQLite
The parenthesised SELECT with an alias is supported by all four systems. Some newer engines relax alias requirements, but writing the alias is the portable habit. LATERAL and SQL Server APPLY are related tools for correlated FROM inputs and are not the same universal syntax as this independent derived table.
Check the engine-specific syntax and version requirements in PostgreSQL subqueries in FROM.
Practice task and worked solution
Use a derived table to calculate doubled amounts, then return only sales whose doubled amount exceeds forty.
Try the task before reading its worked solution. The supplied rows make the expected answer checkable by hand.
SELECT id,doubled_amount FROM (
SELECT id,amount*2 AS doubled_amount FROM sales
) AS doubled WHERE doubled_amount>40 ORDER BY id;id | doubled_amount
102 | 60The inner query exposes id and doubled_amount. The outer query can filter that alias because it is now a column of its input. Only thirty doubled to sixty exceeds forty; twenty doubled to forty meets equality but not the strict greater-than condition.
Run the inner customer-total query independently and count its summary rows, including the anonymous link group. Then run the outer customer join and confirm its four identified customer rows. Remove known_total from the inner projection and observe that the outer reference becomes invalid. The scope boundary exposes only selected columns, even though the source amount still exists.
SQL Derived Tables: Subqueries in FROM and Preaggregation Syntax
Derived tables
SELECT c.id,c.name,t.known_total FROM customers AS c
LEFT JOIN (
SELECT customer_id,SUM(amount) AS known_total FROM sales GROUP BY customer_id
) AS t ON t.customer_id=c.id
ORDER BY c.id;SQL Derived Tables: Subqueries in FROM and Preaggregation Summary
- A derived table is a
SELECTin brackets inFROM, given an alias and used like a table. - The outer query sees only the columns the derived table selects.
- It lets the outer query filter on calculated aliases, which
WHEREcan't do at the same level. - Pre-aggregate each one-to-many source to one row per key before joining, to avoid multiplied totals.
LATERALin PostgreSQL and MySQL, andAPPLYin SQL Server, let a subquery inFROMrefer to earlier tables.
SQL Derived Tables: Subqueries in FROM and Preaggregation Examples
Each example shows the code, the output it produces and what each part does.
Customer totals joined to every customer
The derived table contains one sales summary per customer link. The outer left join attaches identified summaries to all customers. Dev has no summary partner and gets NULL, while the anonymous summary is not attached to any customer.
SELECT c.id,c.name,t.known_total FROM customers AS c
LEFT JOIN (
SELECT customer_id,SUM(amount) AS known_total FROM sales GROUP BY customer_id
) AS t ON t.customer_id=c.id
ORDER BY c.id;id | name | known_total
1 | Ana | 40
2 | Ben | 20
3 | Cara | 20
4 | Dev | NULLCommon SQL Derived Tables: Subqueries in FROM and Preaggregation Mistakes
Joining detail tables before summing
Joining raw sales and another detail table before summing can multiply each sale across the other table’s matches. Group each detail source to the intended shared grain first. Also avoid selecting a field in the outer query that was not exposed by the inner SELECT; only the derived result’s columns are in scope.
SQL Derived Tables: Subqueries in FROM and Preaggregation in Real Projects
A dashboard built from per-customer summaries
A customer dashboard can combine spending totals, ticket counts and membership data without making every ticket repeat every sale. Preaggregate each one-to-many measure to one customer row, then join those summaries. Validate each inner count and sum before checking the final combined report.
Document the output columns and grain of a derived stage as an interface for the enclosing query. Preaggregate independent detail relations separately before joining customer-level summaries. This prevents a support-ticket count from repeating each sale and inflating its total. Logical nesting clarifies meaning, while physical materialisation remains a separate optimiser decision.
SQL Derived Tables: Subqueries in FROM and Preaggregation FAQ
Is a derived table permanent?
No. It is part of one statement and does not create a persistent schema object. Use a view for a reusable definition, or a temporary table to store an intermediate result for several statements.
Can the outer query see every original column?
No. Only columns projected by the derived SELECT are exposed through that input. Add any column the outer query needs, such as customer_id for a join, to the inner select list.
Is the inner query always executed first physically?
Not necessarily. The optimiser can merge the derived table into the outer query, push filters inside it or choose another order while preserving its meaning, so don't rely on the written order for side effects.
Is a derived table the same as a CTE?
They do the same job. A CTE names the subquery at the top with WITH, which reads better for several steps and allows the same result to be referenced twice; a derived table sits inline in FROM. Most databases treat simple versions of both the same way.
What is a LATERAL join?
A derived table marked LATERAL can refer to columns of tables listed before it, so it runs per row like a correlated subquery but may return several rows and columns. A typical use is "each customer's three latest sales". PostgreSQL and MySQL 8.0.14+ support it, and SQL Server uses CROSS APPLY.
SQL Derived Tables: Subqueries in FROM and Preaggregation Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Which row survives the doubled_amount>40 derived-table exercise?
SQL Derived Tables: Subqueries in FROM and Preaggregation Quiz
2 questions to check what you learned.
Where is a derived table commonly written?
- As an output alias without a SELECT input
- As a stored CREATE TABLE statement inside WHERE
- As a parenthesised SELECT in FROM
Why preaggregate two independent detail sources before joining?
- To avoid multiplying detail rows and inflating measures
- To remove all legitimate repeated monetary values automatically
- To guarantee every derived stage is physically cached by the optimiser