Performance Optimization — Learn Anti Joins, FILTER Pivots, Sort Elimination, and Top-N in Practice
AdvancedPeriod-Conditioned Anti JoinFILTER Pivot + RatiosComposite Index and Sort EliminationBranch Top-NHAVING × FILTERPostgreSQL-compatible5 questions
QUESTION 6
Conditional Anti Join — Find and efficiently exclude members with no participation in the last 90 days using NOT EXISTS
NOT EXISTSCorrelated conditionPartial-match exclusionComposite-index use
Background

In Basic 2 Q6, we used NOT EXISTS to find “people who have never participated.” In practice, a more advanced pattern appears constantly: period-conditioned exclusion, such as “no participation in the last 90 days (= dormant).” The key is to put the additional condition inside the subquery—you can directly express “there is no participation within the last 90 days,” allowing the planner to choose a Hash Anti Join with filtering through a composite index.

-- ✗ NOT IN + period condition: a NULL makes the result empty (the Basic Q6 trap returns)
WHERE member_id NOT IN (SELECT member_id FROM event_entries
                     WHERE entered_at >= DATE '2026-03-14')

-- ✓ Put the condition inside the subquery: NULL-safe + Anti Join
WHERE NOT EXISTS (SELECT 1 FROM event_entries e
                  WHERE e.member_id = m.member_id
                    AND e.entered_at >= DATE '2026-03-14')
Declare the scope of “does not exist” in the expression: The WHERE inside NOT EXISTS is the definition of “what must not exist for a row to pass.” Put matching member_id AND within the period inside the subquery to mean “a person with no participation during the period.” Trying to put it outside breaks the meaning, even though this is an exclusion. The principle is to complete the granularity of every exclusion condition inside the subquery.
Problem

There is a members table (100,000 actual rows) and an event_entries table (3 million actual rows; member_id allows NULL to represent guest participation). Retrieve “dormant members” whose status is active and who have never participated on or after 2026-03-14, in a form that is not broken by NULL and can be optimized as an Anti Join. Return member_id, name (member_id ascending). Also define a composite index to speed up matching on the subquery side.

Source tables
- members
member_idnamestatus
1Aokiactive
2Babaactive
3Chibainactive
4Doiactive
5Endoactive
- event_entries (member_id allows NULL)
entry_idmember_identered_at
122026-05-10
242026-01-20
3NULL2026-05-01
452026-06-01
522026-02-11

※ Imagine this as a mailing list for dormant-member reminders. With NOT IN, the NULL on entry_id=3 (within the period) makes the mailing list empty.

Expected Output

Expected output (member_id ascending):

member_idname
1Aoki
4Doi

Doi (4) is dormant because there is participation history but none during the period. Baba (2) and Endo (5) participated during the period, while Chiba (3) is excluded because inactive.

QUESTION 7
FILTER × GROUP BY Pivot Aggregation — Count, sales, and completion rate by category in one scan
FILTER clauseCross-tabulationNULLIF / COALESCEOne scan
Background

In Basic 2 Q7, FILTER consolidated condition-based aggregates for the whole table into one scan. In the advanced set, we combine it with GROUP BY to build a cross-tabulation (pivot). Practical reports also inevitably need ratios—completion rate, churn rate, and so on—and these can be calculated as expressions of aggregate values within the same scan. There are two traps: integer division truncates to 0, and SUM becomes NULL in a group with zero matching rows.

-- ✗ Integer division: 2 / 3 = 0 (the fraction disappears) / denominator 0 causes division by zero
COUNT(*) FILTER (WHERE …) / COUNT(*)

-- ✓ Multiply by 100.0 to make it numeric + use NULLIF to turn denominator 0 into NULL
ROUND(100.0 * COUNT(*) FILTER (WHERE …) / NULLIF(COUNT(*), 0), 1)
NULLIF(x, 0) is the standard zero-division guard: NULLIF(a, b) returns NULL when a = b and returns a otherwise. Wrap a denominator in NULLIF(denominator, 0) so the whole expression becomes NULL when the denominator is 0 and the query does not fail with an error. Likewise, a SUM(…) FILTER with zero matching rows becomes NULL, so reporting convention is to turn it back into 0 with COALESCE(…, 0).
Problem

From the orders table (5 million actual rows), aggregate the following four values for each category in one scan only: total_cnt (all rows), completed_cnt (completed rows), completed_amt (completed sales total; 0 when there are no matches), and completed_rate (completion rate as a percentage to one decimal place, using a formula that does not fail with denominator 0). Output in ascending category order.

Source table
- orders
order_idcategorystatusamount
1bookcompleted1200
2bookcancelled800
3foodcompleted500
4foodcompleted700
5foodcompleted300
6toypending900
7bookcompleted2000

※ If you write the “completion-rate report by category” as subqueries for each category × aggregate type, it becomes 5 million rows × (number of categories × 4) scans.

Expected Output

Expected output (ascending category, 3 rows):

categorytotal_cntcompleted_cntcompleted_amtcompleted_rate
book32320066.7
food331500100.0
toy1000.0

toy has zero completed rows—SUM becomes NULL and the rate numerator becomes 0. The question tests COALESCE and numeric conversion.

QUESTION 8
One Composite Index for WHERE + ORDER BY — All the way to Keyset Pagination
Composite indexORDER BYKeyset paginationSort-node elimination
Background

In Basic 2 Q8, a single-column index eliminated the sort for ORDER BY. Real-world list screens almost always combine “filtering + sorting + pagination.” A form such as WHERE category = 'tech' ORDER BY published_at DESC LIMIT 3 becomes a single operation with a composite index (category, published_at DESC): jump to the equality interval → read the interval in order → stop after N rows. For pagination, keyset pagination (using the previous page’s last-row value as the boundary) avoids OFFSET and keeps deep pages fast.

-- ✗ OFFSET: rows to discard are still read (page 100 = read and discard 297 rows, then return 3)
ORDER BY published_at DESC LIMIT 3 OFFSET 297

-- ✓ keyset: jump directly from the previous page’s last value through the index
WHERE category = 'tech' AND published_at < '2026-06-02'
ORDER BY published_at DESC LIMIT 3
Column-order principle: “equality → sort column”: Entries in a composite index are ordered by column 1 and then column 2. Put category (equality) first so tech entries form a contiguous interval, already ordered by published_at DESC within that interval. With the reverse order, (published_at DESC, category), tech rows are scattered across the full index; the sort may disappear, but skipping irrelevant categories remains.
Problem

From the articles table (2 million actual rows), retrieve the three latest articles in category = 'tech' (page 1) in descending published_at order. Also write (1) the composite index that eliminates the sort stage for this query and (2) the page-2 query that does not use OFFSET (assume the last row on page 1 had published_at = '2026-06-02'). Return article_id, title, published_at.

Source table
- articles (the heap is ordered by neither category nor date)
article_idcategorytitlepublished_at
1lifeMorning Habits2026-05-01
2techIndex Design2026-06-09
3lifeOrganizing Your Days Off2026-04-12
4techJOIN Basics2026-06-11
5lifeThe Science of Sleep2026-03-30
6techSorting and Memory2026-06-02
7techUsing CTEs2026-05-20
8techIntroduction to Statistics2026-04-25

※ Imagine a category-based latest-articles list with a “Load more” button. Each access either sorts 2 million rows or walks three entries in one contiguous index interval—that is the difference.

Expected Output

Expected output (page 1: tech, published_at descending, 3 rows):

article_idtitlepublished_at
4JOIN Basics2026-06-11
2Index Design2026-06-09
6Sorting and Memory2026-06-02

Page 2 (keyset method):

article_idtitlepublished_at
7Using CTEs2026-05-20
8Introduction to Statistics2026-04-25

Both pages use only Limit → Index Scan in the execution plan. There is no Sort node and no discarded OFFSET scan.

QUESTION 9
Latest N Across Monthly Tables — Per-Branch Top-N and Merge Append
UNION ALLPer-branch LIMITMerge AppendAvoiding full-table Sort
Background

In Basic 2 Q9, we learned that UNION ALL is for combining disjoint sources. The advanced step is what comes next: retrieving only the latest N rows from the combined result. The naive form “connect everything, then sort the whole result” is expensive. If each table has an index on created_at, take Top-N in each branch first (at most N × number of branches), then reorder the outer result and keep N rows. With the right conditions PostgreSQL can even use Merge Append to merge already ordered streams.

-- ✗ Append everything → Sort 6 million rows → 3 rows
Limit → Sort (6 million rows) → Append → Seq Scan ×2

-- ✓ 3 rows from each branch index → reorder at most 6 rows and keep 3
Limit → Sort/Merge (≤6 rows) → Append
        ├─ Limit → Index Scan Backward (logs_202605)
        └─ Limit → Index Scan Backward (logs_202606)
Why the result is correct: The overall top 3 must be included in the union of each branch’s top 3; a row ranked fourth or lower within one branch cannot be in the overall top 3. Therefore, branch-local LIMIT 3 compresses the sort input to 6 rows without changing the result. Parenthesize each UNION ALL branch to give it its own ORDER BY / LIMIT.
Problem

Logs are split into monthly tables logs_202605 / logs_202606 (3 million actual rows each; log ID ranges are disjoint; each table has a descending index on created_at). Retrieve the three latest logs across both tables in a form that does not cause a full-table sort. Return log_id, message, created_at (created_at descending).

Source tables
- logs_202605 (created_at DESC index exists)
log_idmessagecreated_at
101deploy ok2026-05-28
102cache warm2026-05-30
103batch done2026-05-15
- logs_202606 (created_at DESC index exists)
log_idmessagecreated_at
201deploy ok2026-06-10
202index rebuilt2026-06-03
203backup done2026-06-11

※ Imagine an “Latest logs” widget for an operations dashboard. The requirement is to handle month boundaries correctly without sorting 6 million rows.

Expected Output

Expected output (created_at descending, 3 rows):

log_idmessagecreated_at
203backup done2026-06-11
201deploy ok2026-06-10
202index rebuilt2026-06-03

The message is the same (deploy ok) in both months, but log_id and created_at differ, so UNION would not remove these two rows. With disjoint sources, use UNION ALL to avoid unnecessary duplicate-elimination cost.

QUESTION 10
Capstone — Monthly Union + NULL-Safe Exclusion + FILTER Pivot + HAVING Report SQL
CapstoneUNION ALLNOT EXISTSHAVING × FILTER
Background

This is the final synthesis of Advanced 2. Build a monthly sales report by combining all the components learned so far: ① combine monthly tables with UNION ALL (Q9), ② exclude blocked accounts with NOT EXISTS (Q6: NULL-safe + Anti Join), ③ pivot region × status and calculate amounts with FILTER + COALESCE (Q7), and the new element, ④ select groups with HAVING—aggregate values (including FILTER aggregates!) can be used as conditions.

-- Pattern assembly (data flow = execution order)
FROM ( ... UNION ALL ... ) s        -- ① combine (Append only)
WHERE NOT EXISTS ( ... )            -- ② exclude rows (upstream, before aggregation)
GROUP BY ...                        -- ③ group + FILTER aggregates
HAVING aggregate FILTER (...) >= n       -- ④ select groups (after aggregation)
ORDER BY aggregate_column DESC
Separating WHERE and HAVING is the capstone: Blocking is a row-level condition, so use WHERE (the earlier it reduces rows, the lighter the later stages). “Only regions with at least two paid rows” is knowable only after aggregation, so use HAVING. The sorting question is: “Can this condition be judged by looking at one row?”
Problem

Combine monthly sales sales_202605 / sales_202606 (ID ranges are disjoint; each has several million actual rows), exclude sales from blocked accounts, and aggregate paid count, paid sales total, and refund count by region. Output only regions with at least two paid rows, ordered by paid sales total descending. blocked_accounts.customer_id allows NULL (rows under review may be present). Return region, paid_cnt, paid_amt, refund_cnt.

Source tables
- sales_202605
sale_idregionstatusamountcustomer_id
1eastpaid100011
2westpaid150012
3eastrefund40013
4northpaid30015
- sales_202606
sale_idregionstatusamountcustomer_id
11eastpaid200011
12westrefund700999
13eastpaid80014
14westpaid120012
- blocked_accounts (customer_id allows NULL)
block_idcustomer_id
1999
2NULL

※ Also consider the number of scans and what NULL does if you write this as “UNION + NOT IN + a subquery per region × status + an outer count filter.”

Expected Output

Expected output (paid_amt descending):

regionpaid_cntpaid_amtrefund_cnt
east338001
west227000

sale_id=12 (the refund for blocked customer 999) is excluded. north drops out in HAVING because it has only one paid row. With NOT IN, the NULL in blocked_accounts makes the entire report return zero rows.