🚧 Under construction — VoTechWorks is being built. A free tech program — complimentary, as our commitment to get America working.
voTechWorksGrade 13 · a vocational technology learning center, by urbanicity
newSchoolWorks.com › VoTechWorks › Technology & Design › Data Management / Database Administration › PostgreSQL Administration › Lesson 4
TEC-DATA86-03 · Lesson 4 · 11 clock hours · practice

SQL in PostgreSQL: Queries, Joins, CTEs, and Window Functions

Objective. Students write complex queries using joins, CTEs, and window functions against a multi-table PostgreSQL database, producing correct result sets.

⚠ Pending professional review

This lesson, its worked examples and its practice questions has not yet been verified by an industry-certified professional or by a certification or approval body. It is a study draft and may contain errors. Do not use it as instructions for work on a job site or on live equipment. Always follow your instructor, your supervising professional, and current official references and regulations.

VoTechWorks program approval and accreditation are still in progress. Credentials are issued only by the certifying bodies named in each program, after their own exams.

SQL in PostgreSQL: Queries, Joins, CTEs, and Window Functions

Structured Query Language is the primary tool a database administrator uses to retrieve, analyze, and validate data. PostgreSQL 16 implements the SQL standard fully and adds several powerful extensions. This lesson moves beyond single-table SELECT statements and builds the skills you need to write complex, production-quality queries.

Joins: Combining Data from Multiple Tables

Most real databases spread related data across many tables. A join tells PostgreSQL how to match rows from two or more tables.

Always qualify column names with the table name or alias when joining tables that share column names. Ambiguous references cause errors and make queries harder to read.

```sql

SELECT p.product_name, c.category_name

FROM products p

INNER JOIN categories c ON p.category_id = c.category_id;

```

Aggregates and GROUPING SETS

Aggregate functions such as COUNT, SUM, AVG, MIN, and MAX collapse many rows into summary values. GROUP BY controls which columns define each group. PostgreSQL extends this with GROUPING SETS, which produces multiple levels of aggregation in a single query without UNION ALL:

```sql

SELECT region, category_name, SUM(quantity_sold)

FROM sales

GROUP BY GROUPING SETS ((region, category_name), (region), ());

```

The empty set `()` produces a grand total. This is far more efficient than writing three separate queries.

Correlated Subqueries

A correlated subquery references a column from the outer query. PostgreSQL re-evaluates it once for each row of the outer query. Correlated subqueries are readable for small datasets but can be slow on large tables because of repeated execution. Always check the query plan with EXPLAIN ANALYZE before deploying them in production.

```sql

SELECT p.product_name

FROM products p

WHERE p.unit_price > (

SELECT AVG(unit_price) FROM products p2

WHERE p2.category_id = p.category_id

);

```

Common Table Expressions (CTEs)

A Common Table Expression (introduced with the WITH keyword) names a temporary result set that you can reference later in the same query. CTEs improve readability by breaking a complex query into named, logical steps. In PostgreSQL 12 and later, the optimizer can inline non-recursive CTEs, so there is usually no performance penalty compared to a subquery.

```sql

WITH avg_prices AS (

SELECT category_id, AVG(unit_price) AS avg_price

FROM products

GROUP BY category_id

)

SELECT p.product_name, p.unit_price, a.avg_price

FROM products p

JOIN avg_prices a ON p.category_id = a.category_id

WHERE p.unit_price > a.avg_price;

```

A recursive CTE includes a UNION ALL that references the CTE itself, making it possible to traverse hierarchical data such as an organizational chart or a product category tree. The recursive term must eventually produce no rows, or the query will run indefinitely — PostgreSQL enforces a `max_recursive_iterations` safeguard, but always design a clear termination condition.

Window Functions

Window functions perform calculations across a set of rows that are related to the current row, without collapsing those rows into a single output row the way GROUP BY does. You define the window with an OVER clause.

```sql

SELECT region, product_name, total_sales,

RANK() OVER (PARTITION BY region ORDER BY total_sales DESC) AS sales_rank

FROM regional_sales;

```

Window functions execute after WHERE and GROUP BY filtering but before the outer ORDER BY. This means you cannot filter on a window function result in the same WHERE clause — wrap the query in a CTE or subquery first.

Data Safety and Best Practices

Although SELECT queries do not modify data, you must still follow safe practices in a shared database environment. Always run complex queries against a development or training instance (such as the inventory_db VM provided) before running them on production. Never use a superuser role for routine querying; use a role with only SELECT privileges on the relevant tables. If you accidentally begin a transaction that modifies data, issue ROLLBACK immediately. Before running any long-running query in production, estimate its cost with EXPLAIN ANALYZE on the training instance first to avoid locking tables or overwhelming shared resources.

Putting It Together

The progression from joins to aggregates to CTEs to window functions reflects the way query complexity grows in real DBA work. Master each layer before combining them. Read the query plan at each stage to understand what PostgreSQL is actually doing — this skill underpins performance tuning, which you will study in a later lesson.

Key terms10

INNER JOINA join that returns only rows where the join condition matches in both tables; unmatched rows from either table are excluded.
LEFT OUTER JOINA join that returns all rows from the left table and matched rows from the right; columns from the right table are NULL where no match exists.
FULL OUTER JOINA join that returns all rows from both tables, with NULLs filling columns on whichever side has no matching row.
Common Table Expression (CTE)A named temporary result set defined with the WITH keyword at the start of a query, used to improve readability and modularize complex logic.
Recursive CTEA CTE that references itself with UNION ALL to traverse hierarchical or graph-structured data; requires a termination condition to avoid infinite loops.
Window FunctionA SQL function that computes a value across a set of rows related to the current row, defined by an OVER clause, without collapsing rows like GROUP BY.
GROUPING SETSA PostgreSQL GROUP BY extension that computes aggregates at multiple levels of granularity in a single query, replacing multiple UNION ALL statements.
Correlated SubqueryA subquery that references a column from the outer query and is re-evaluated for each row the outer query processes.
RANK()A window function that assigns the same rank value to tied rows and leaves gaps in the ranking sequence after ties.
LAG()A window function that returns the value of a specified column from a prior row within the same partition, used for period-over-period comparisons.
In class: Complete a structured query exercise set of 15 problems of increasing difficulty against the inventory_db dataset, covering INNER/LEFT/FULL OUTER joins, correlated subqueries, and aggregates with GROUPING SETS; self-check answers against a provided answer key. · Rewrite two of the subquery problems using Common Table Expressions (WITH clauses) and a third using a recursive CTE to generate a product category hierarchy; compare readability and explain to the class why each form was chosen. · Write three analytical queries using window functions (ROW_NUMBER, RANK, LAG) to identify top-selling products by region and month; verify results are identical to equivalent non-window approaches using a provided validation query.
Materials: PostgreSQL 16 VM with inventory_db, psql, DBeaver (optional GUI), PostgreSQL Documentation Chapter 7 (Queries), query exercise booklet, answer key (instructor copy)
Check: Instructor spot-checks five randomly selected exercises for correct output using a diff against the answer key; pass = at least 80% of the 15 exercises produce correct result sets.

Practice check8

Answer each question, then check it. Nothing is recorded; this is your own practice.

SQL and Relational Data
1. A query uses a LEFT OUTER JOIN between the `orders` table and the `shipments` table. An order exists that has no matching shipment record. What will appear in the shipment columns for that order row in the result set?
SQL and Relational Data
2. Which SQL clause introduces a Common Table Expression in PostgreSQL?
Query Performance and Optimization
3. A developer writes a correlated subquery that references the outer table inside a WHERE clause. On a table with 500,000 rows, the query runs very slowly. What is the most likely cause?
Advanced SQL Features
4. You need to return the rank of each product's sales within its region, showing tied products with the same rank and skipping subsequent rank numbers. Which window function should you use?
SQL and Relational Data
5. Which GROUPING SETS expression would produce subtotals by region, subtotals by category, and a grand total all in one query?
Advanced SQL Features
6. A recursive CTE is used to traverse a product category hierarchy. The developer forgets to include a termination condition. What safeguard does PostgreSQL apply by default?
Advanced SQL Features
7. A window function is used in the SELECT list of a query that also has a WHERE clause. A developer tries to filter rows based on the window function result directly in the same WHERE clause. What happens?
Query Performance and Optimization
8. When rewriting a correlated subquery as a CTE in PostgreSQL 12 or later, what is generally true about query performance?
Record this lesson on my transcript. Sign in with your key and enrollment email to record lessons (enroll free if you have no key). This page stays open to read and practise.