SQL Performance Tuning — Applied Anti Joins, Sort Removal

ADVPeriod-Conditioned Anti JoinFILTER Pivot + RatiosComposite Index and Sort EliminationBranch Top-NHAVING × FILTERPostgreSQL-compatible5 questions
1 / 5 · In progress 0 / 5 completed
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

NOT EXISTS can retrieve targets for which no corresponding row has ever existed. 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
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
Expected Output
member_idname
1Aoki
4Doi
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, 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
Expected Output
categorytotal_cntcompleted_cntcompleted_amtcompleted_rate
book32320066.7
food331500100.0
toy1000.0
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, 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
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
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, 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
Expected Output
log_idmessagecreated_at
203backup done2026-06-11
201deploy ok2026-06-10
202index rebuilt2026-06-03
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, ② exclude blocked accounts with NOT EXISTS (NULL-safe + Anti Join), ③ pivot region × status and calculate amounts with FILTER + COALESCE, 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
Expected Output
regionpaid_cntpaid_amtrefund_cnt
east338001
west227000