SQL for QA

The SQL skills QA actually needs and how basic queries improve defect investigation and data verification.

Updated:

Contents

SQL for QA centers on discovering what actually happened to data, rather than deep query optimization or DBA work. When UI and API disagree, the database often provides the most direct evidence.

Even basic SQL substantially improves testing. It verifies whether records were created, statuses persisted, duplicates appeared, relationships are correct, reports reconcile, or data disappeared after an action.

Why QA needs SQL

In everyday work, SQL usually serves three goals:

  • →Check actual data state after a scenario
  • →Investigate defects faster
  • →Identify whether the problem lies in UI, API, logic, or data

Many apparent button defects are actually data-state problems.

  • →A record appears in the UI but related rows are missing
  • →The API reports success but an update was not saved
  • →A report uses the wrong data
  • →Retries create duplicates
  • →A filter appears broken because stored data already differs from product expectations

Without SQL, these investigations can be slow and poorly informed.

What to learn first

QA does not need the entire language initially. Start by becoming confident with a few essentials.

  • →SELECT retrieves data
  • →WHERE filters rows
  • →ORDER BY controls ordering
  • →LIMIT avoids retrieving unnecessary rows
  • →COUNT measures quantity
  • →JOIN connects data across tables
  • →GROUP BY exposes aggregates and duplicates
  • →IS NULL and IS NOT NULL check null values

These cover a large share of everyday testing tasks.

Understand each construct's purpose as well as its syntax. With order, user, and payment tables, SQL can verify the complete scenario rather than merely display rows.

What SQL gives QA

SQL's greatest strength is precision.

The UI shows selected information; an API exposes one call's response. Direct data inspection can answer:

  • →Was the record created at all?
  • →How many such records actually exist?
  • →Are there duplicates?
  • →Which user owns the entity?
  • →Was the correct status saved?
  • →Are there orphaned records?
  • →Does the amount match the interface?
  • →Did the operation really change the record, or only appear to?

This is particularly useful for integrations, queues, retries, background jobs, bulk updates, and complex entity relationships.

JOIN for QA

JOIN is especially valuable because it shows how entities relate, beyond inspecting tables individually.

  • →Every order has an owner
  • →An order has the required order items
  • →A payment belongs to the correct order
  • →A user has the correct role
  • →No records reference nonexistent objects

Often a record exists but is linked incorrectly or part of its relationship chain is broken. JOIN makes these defects easier to see.

Common SQL mistakes in QA

  • →Confusing NULL with an empty value
  • →Overlooking duplicate rows introduced by JOIN
  • →Inspecting only the first convenient row
  • →Executing unnecessary writes

The safest normal approach is to read first and make deliberate changes only in an appropriate environment. SELECT is sufficient for most investigations.

Practical example

Suppose a user reports two identical orders.

The UI provides a partial view, and an API endpoint shows one part. SQL can establish much more:

  • →Whether two order rows actually exist
  • →Whether their business keys match
  • →Whether they were created at the same time
  • →Whether both have identical order items
  • →Whether a retry created the duplicate
  • →Whether there are two payments or only one
  • →Whether the duplication is real or the UI displays one record twice

Here SQL becomes a practical QA tool rather than an optional technical extra.

Practical habits for QA

  • →Start with a simple SELECT
  • →Inspect one entity first, then its relationships
  • →Always establish which key identifies the data
  • →Avoid conclusions from one row when a broader pattern matters
  • →Treat write queries carefully
  • →When uncertain, compare SQL evidence with API and UI behavior

SQL is especially powerful as a bridge between system layers.

Key takeaways for QA

  • →QA uses SQL to verify data reality rather than to program a database.
  • →Basic SELECT, WHERE, JOIN, COUNT, and GROUP BY support strong defect investigations.
  • →Many product defects are data-state problems.
  • →Connecting UI, API, and database evidence is a valuable testing skill.
  • →SQL makes diagnosis more precise, faster, and more methodical.

Further reading

  • →In this knowledge base: Database Testing
  • →In this knowledge base: JSON and XML
  • →External resource: PostgreSQL joins tutorial

Practice on one shared dataset: eight checked exercises

Use the linked QA Hub exercise sheet and sql-for-qa-fixture.sql instead of creating a second dataset. It contains four fictional customers and five orders, with integer-cent amounts. The walkthrough here uses SQLite 3; the resource also describes portable SQL, but these instructions do not claim a PostgreSQL execution. Download both files, open a terminal in that directory, and keep one session open.

plain text
sqlite3 :memory:
-- At the SQLite prompt:
.read sql-for-qa-fixture.sql
SELECT COUNT(*) FROM qa_customers;
-- Expected: 4
SELECT COUNT(*) FROM qa_orders;
-- Expected: 5

The fixture creates temporary tables in the current session. A new :memory: session starts empty; rerunning CREATE in the same session without a reset fails because the tables already exist. Close the disposable session and start a fresh one to reset. No production connection or credentials are needed.

Predict the rows before opening the answer sheet

plain text
Task in resource	Expected result
1 Paid orders	(101, 1, 1200), (103, 2, 700), (105, 3, 0)
2 Orders per customer, including zero	(1, Ada, 2), (2, Boris, 2), (3, Chen, 1), (4, Dina, 0)
3 Paid revenue per customer	(1, 1200), (2, 700), (3, 0), (4, 0)
4 Customers without orders	(4, Dina)
5 Duplicate non-null emails	(ada@example.invalid, 2)
6 Invalid paid amount under the exercise rule	(105, 0)
7 At least two orders	(1, 2), (2, 2)
8 Missing email	(4, Dina)

These are the same task numbers and results as the downloadable sheet. The duplicate email and zero-value paid order are deliberately seeded examples. Whether they represent a defect in a real product depends on its requirements. Do not infer uniqueness, positive-value or normalization rules merely from a query.

The LEFT JOIN trap: a correct total with a missing customer

Compare these two queries after loading the shared fixture. The reporting rule is to show all four customers, including those with no paid orders. The first query places the paid filter in WHERE:

plain text
SELECT c.customer_id, COALESCE(SUM(o.amount_cents), 0) AS paid_cents
FROM qa_customers c
LEFT JOIN qa_orders o ON o.customer_id = c.customer_id
WHERE o.status = 'paid'
GROUP BY c.customer_id ORDER BY c.customer_id;

It returns (1,1200), (2,700), (3,0): Dina disappears. Her unmatched joined row has NULL order fields, so the WHERE condition does not keep it. The sum still equals 1900 cents, which is why checking only a grand total misses the defect.

Put the filter into the join condition so unmatched customers survive:

plain text
SELECT c.customer_id, COALESCE(SUM(o.amount_cents), 0) AS paid_cents
FROM qa_customers c
LEFT JOIN qa_orders o
  ON o.customer_id = c.customer_id AND o.status = 'paid'
GROUP BY c.customer_id ORDER BY c.customer_id;

Expected: (1,1200), (2,700), (3,0), (4,0). Assert both the exact customer IDs and amounts. COUNT(o.order_id) is needed for order counts; COUNT(*) counts an unmatched outer-join row too. Check NULL with IS NULL rather than = NULL. The answer sheet includes these cases with runnable queries.

Turn a mismatch into useful evidence

Record the fixture or data version, SQL dialect, query, product rule, expected rows and actual rows. Sort explicitly before comparing ordered results. For an unordered result, compare sets or multisets according to whether duplicates are meaningful. A successful query execution is not proof of a correct business result.

Continue learning

Extend checks to database behavior

Report a data mismatch with evidence

Download the shared SQL fixture

Download all eight SQL exercises and answers

SQLite documentation: SELECT, outer joins and filtering