🚧 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 Science & Analytics › SQL and PostgreSQL for Data Analytics › Lesson 7
TEC-DATA85-04 · Lesson 7 · 11 clock hours · practice

Query Optimization and Indexing

Objective. Students interpret EXPLAIN ANALYZE output to identify sequential scans on large tables and add B-tree and partial indexes that measurably reduce query execution time.

⚠ 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.

Why Query Optimization Matters

When a table holds millions of rows, a poorly written or poorly supported query can take minutes instead of milliseconds. In production data pipelines, slow queries waste cloud compute dollars, delay dashboards, and frustrate stakeholders. Query optimization is the skill of reading what PostgreSQL actually does with a query and changing the table structure or the query itself so the database does less work.

The two main tools in this lesson are EXPLAIN ANALYZE and indexes.

Reading EXPLAIN ANALYZE Output

`EXPLAIN ANALYZE` runs the query and returns the execution plan PostgreSQL chose, along with actual timing measured during that run. The key syntax is:

```sql

EXPLAIN ANALYZE SELECT * FROM health_records WHERE county = 'Alameda';

```

The output is a tree of nodes. Each node represents one operation — a scan, a join, a sort, an aggregate. Read the tree from the innermost (most-indented) node outward; PostgreSQL executes inner nodes first.

For each node you will see:

The costliest node is usually the one with the highest `actual time` total or the one labeled Seq Scan on a large table. A sequential scan reads every page of the table regardless of how many rows match — fine for small tables, expensive for millions of rows.

Recognizing a Problem

Look for these warning signs in the plan:

Indexes: What They Are and How They Help

An index is a separate data structure that maps column values to the physical locations of matching rows. Instead of reading every page, PostgreSQL jumps directly to the rows it needs.

B-tree Indexes

The default index type in PostgreSQL is the B-tree (balanced tree). It supports equality (`=`) and range comparisons (`<`, `>`, `BETWEEN`). Create one with:

```sql

CREATE INDEX idx_county ON health_records (county);

```

After creation, run `EXPLAIN ANALYZE` again. You should see the `Seq Scan` replaced by an Index Scan or Bitmap Index Scan, and the actual time should drop sharply.

Partial Indexes

A partial index covers only rows that satisfy a `WHERE` condition. If 95 % of queries filter on active records only, indexing just those rows keeps the index small and fast:

```sql

CREATE INDEX idx_active_county

ON health_records (county)

WHERE status = 'active';

```

Partial indexes are useful when a column has a skewed distribution — most values are one thing, but you always query for the rare value.

When Indexes Do Not Help

An index can be ignored by the planner when:

Adding too many indexes also slows `INSERT`, `UPDATE`, and `DELETE` operations because each index must be maintained. Index only columns that appear in frequent, selective `WHERE` clauses or join conditions.

Reading the Comparison Table

Your lab notebook table records four columns for each query: query ID, plan node before, actual time before (ms), and actual time after (ms). Calculate the speedup ratio as before ÷ after. A ratio of 10× or greater is common when replacing a sequential scan on a million-row table with a B-tree index scan.

Data Accuracy and Integrity

Record the numbers you actually observe. Do not round aggressively or copy a classmate's timings — execution times vary by machine load, and your instructor will check that results are plausible for your hardware. Fabricating benchmark results is academic dishonesty and also a serious professional ethics violation; in industry, false performance claims can lead to bad infrastructure decisions and cost overruns.

Workplace Safety Note

This lesson runs entirely in software on a local or lab PostgreSQL instance loaded with open, de-identified public data. No personal health information (PHI) is used. When you work with real health data in your career, you must follow your organization's data governance policy, HIPAA requirements where applicable, and any data-use agreements. Never run untested bulk operations — including index builds — on production databases without a maintenance window and a verified backup. Large index builds can lock tables and interrupt live applications.

Key terms9

EXPLAIN ANALYZEA PostgreSQL command that both executes a query and returns the query execution plan annotated with actual timing and row counts, allowing you to see exactly where time is spent.
Sequential Scan (Seq Scan)A plan node in which PostgreSQL reads every page of a table from start to finish, regardless of how many rows match the query condition; efficient for small tables or low-selectivity queries, but slow on large tables.
B-tree IndexPostgreSQL's default index type, based on a balanced tree structure, that supports equality and range comparisons and allows the planner to jump directly to matching rows instead of scanning the whole table.
Partial IndexAn index built only on the subset of rows that satisfy a specified WHERE condition, keeping the index small and fast when queries consistently filter on a predictable value.
Index ScanA plan node in which PostgreSQL uses an index to locate matching rows, typically much faster than a sequential scan when the query is highly selective.
SelectivityThe fraction of table rows a query condition matches; high selectivity means few rows match (good for index use), while low selectivity means many rows match (index may be ignored).
Query Execution PlanThe internal step-by-step strategy PostgreSQL's planner chooses to execute a query, represented as a tree of nodes each describing one operation such as a scan, join, or sort.
Bitmap Index ScanA two-step plan node that first reads the index to build a bitmap of matching page locations, then fetches those pages; useful when multiple rows per page match, bridging index and sequential scan strategies.
ANALYZEA PostgreSQL command that collects statistics about table contents and updates the planner's estimates, helping the optimizer choose better execution plans.

Worked examples4

1. A query takes 4,200 ms before adding an index and 38 ms after. Calculate the speedup ratio and express it as a multiplier rounded to one decimal place.
  1. Identify before time: 4,200 ms.
  2. Identify after time: 38 ms.
  3. Divide before by after: 4,200 ÷ 38 = 110.526…
  4. Round to one decimal place: 110.5.
Answer: The speedup ratio is approximately 110.5×, meaning the indexed query runs about 110 times faster.
2. You have a table with 1,000,000 rows. A query using WHERE status = 'closed' returns 920,000 rows. Should you create a B-tree index on status? Explain using selectivity.
  1. Calculate selectivity: rows returned ÷ total rows = 920,000 ÷ 1,000,000 = 0.92, or 92%.
  2. Recall the guideline: indexes generally help when selectivity is high — meaning the query returns a small fraction of rows, roughly under 5–10%.
  3. At 92% selectivity, the query matches almost every row. PostgreSQL would need to visit nearly every index entry and then fetch nearly every table page anyway.
  4. A sequential scan reads table pages in order, which is cheaper than the random I/O pattern of an index scan when most pages must be visited.
  5. Conclusion: an index on status is unlikely to help here and may be ignored by the planner.
Answer: No. Because 92% of rows match, selectivity is too low. The planner will likely choose a sequential scan, and the index would not reduce execution time.
3. Before optimization, Query 3 has an actual time of 3,850 ms. After adding a partial index, it drops to 210 ms. What is the time saved in seconds, and what percentage reduction does that represent? Round percentage to one decimal place.
  1. Calculate time saved in milliseconds: 3,850 − 210 = 3,640 ms.
  2. Convert to seconds: 3,640 ÷ 1,000 = 3.64 seconds saved.
  3. Calculate percentage reduction: (3,640 ÷ 3,850) × 100 = 94.545…%.
  4. Round to one decimal place: 94.5%.
Answer: The query is 3.64 seconds faster, a 94.5% reduction in execution time.
4. Your comparison table shows five queries. Execution times before and after (in ms) are: Q1 800→45, Q2 5,200→4,980, Q3 3,100→120, Q4 900→30, Q5 2,400→88. Which queries show a speedup ratio greater than 10×, and do you meet the pass requirement of four of five showing measurable improvement?
  1. Calculate each ratio (before ÷ after): Q1: 800÷45 ≈ 17.8×; Q2: 5,200÷4,980 ≈ 1.04×; Q3: 3,100÷120 ≈ 25.8×; Q4: 900÷30 = 30.0×; Q5: 2,400÷88 ≈ 27.3×.
  2. Identify which exceed 10×: Q1 (17.8×), Q3 (25.8×), Q4 (30.0×), Q5 (27.3×) all exceed 10×. Q2 (1.04×) shows almost no improvement.
  3. Count queries with measurable improvement: Q1, Q3, Q4, Q5 — that is four queries.
  4. Compare to pass requirement: four of five correct is exactly the minimum needed to pass.
Answer: Q1, Q3, Q4, and Q5 show speedup ratios greater than 10×. Q2 shows almost no improvement, likely because the index was not used (low selectivity or wrong column). Four of five meet the threshold, so the pass requirement is exactly met.

The math in this lesson1

Learners compute a query speedup ratio from before and after execution times.
6.RP.3
In class: Slow-query diagnosis: students run five pre-written slow queries against a 1-million-row public health dataset, capture EXPLAIN ANALYZE output, and annotate each plan identifying the costliest node. · Index creation lab: students add appropriate indexes for each slow query, re-run EXPLAIN ANALYZE, and record before-and-after execution times in a comparison table in their lab notebook. · Class discussion: pairs share one case where an index did not help (or caused a table-scan anyway due to low selectivity) and explain the reason; instructor synthesizes findings into a short whiteboard summary of index selection guidelines.
Materials: 1-million-row public health CSV loaded into PostgreSQL (instructor-prepared from CDC open data), EXPLAIN ANALYZE cheat-sheet (instructor-prepared), lab notebook comparison table template
Check: Comparison table must show measurable execution-time improvement for at least four of five queries, with correct identification of the plan node that changed; instructor verifies numbers are from actual query runs, not fabricated; pass requires four of five correct.

Practice check8

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

Data querying and database optimization
1. In EXPLAIN ANALYZE output, which node type indicates that PostgreSQL is reading every row in a large table regardless of the query condition?
Data querying and database optimization
2. A data analyst adds a B-tree index on the `zip_code` column and re-runs EXPLAIN ANALYZE, but the plan still shows a Seq Scan. The query returns 60% of all rows. What is the most likely reason the index was not used?
Data querying and database optimization
3. Which SQL command refreshes the statistics that PostgreSQL's query planner uses to estimate the number of rows a condition will match?
Data querying and database optimization
4. A query filters on `WHERE status = 'active'` and roughly 3% of rows have that status. Which index type is best suited to serve this query efficiently?
Data querying and database optimization
5. In EXPLAIN ANALYZE output, the 'actual time' field for a node is measured in which unit?
Data analysis and performance measurement
6. Before adding an index, a query runs in 6,000 ms. After adding a B-tree index, it runs in 150 ms. What is the speedup ratio?
Data querying and database optimization
7. Why can wrapping a column in a function inside a WHERE clause prevent an index on that column from being used?
Professional ethics and data integrity
8. A student reports that all five queries improved after indexing without running the queries again. What professional and academic problem does this represent?
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.