← back to the section

Everything an application remembers — customers, orders, products — lives in a database. The UI shows only part of it, and may show it incorrectly: the screen says "Paid" while no payment was recorded. To verify that data was actually saved, and saved correctly, a tester looks directly into the database. The language for talking to a database is SQL.

You don't need to know everything to check data: a few read queries are enough. Let's go through the essentials using an online store — the tables come from the course sandbox, so the queries can be run as they are.

the screen says “paid” — we ask the tables about that order shop · order ord-06 Paid payment accepted, thanks 7,980 ₽ · card SELECT status, paid_at FROM orders WHERE id = 'ord-06'; orders id status paid_at ord-06 PENDING_PAYMENT NULL status never changed, no payment time SELECT count(*) FROM payments WHERE order_id = 'ord-06'; payments id order_id amount no rows at all success on screen, orders unchanged, payments emptythe defect is “payment not recorded”, not “wrong label”

The screen is what the browser drew, not what stayed in the database. Two queries tell those apart: the first looks at the order itself (status never changed, no payment time), the second at the payments table, where the order has no rows at all. Hence the wording of the defect: the payment was not recorded.

What a database and tables are

A database is most often organized as a set of tables — like sheets in a spreadsheet: columns and rows. For example, a customer table with columns id, email, first_name, and an orders table with columns id, customer_id, total_amount, status, paid_at.

A tester usually only reads data rather than changing it, and a read query starts with the word SELECT.

SELECT: display data

The basic query — "show these columns from this table":

live example

SELECT id, first_name, email FROM customer;
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

FROM names the table, SELECT names the columns. An asterisk * means "all of them":

live example

SELECT * FROM orders;
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

WHERE: filter the rows you need

WHERE is a filter: "show only the rows where the condition is true." The most common job a tester has is finding a record in the database that they just created through the UI.

live example

SELECT * FROM customer WHERE email = 'a.volkova@example.com';
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

"Show the customer with this email": you registered on the site, ran the query, confirmed the record appeared with the right name.

Conditions can be combined:

live example

SELECT * FROM orders WHERE customer_id = 'cus-01' AND status = 'PAID';
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

"Paid orders of customer cus-01" — this is how you verify that after payment the status became PAID and didn't stay PENDING_PAYMENT. Text in a condition goes in single quotes, spelled exactly the way the application stores it: PAID and paid are usually different values to a database, and a query with the wrong case silently returns zero rows.

The screen said "paid" — let's check

The case from the picture. First the order itself:

live example

SELECT id, status, paid_at FROM orders WHERE id = 'ord-06';
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

The status is PENDING_PAYMENT and paid_at is empty (NULL means "no value"). Now the payments table:

live example

SELECT count(*) FROM payments WHERE order_id = 'ord-06';
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

Zero. Payment happened on screen, but nothing reached the database — and the defect is now worded as "the payment was not recorded," not "the label is wrong."

A couple more useful things

  • Row count: SELECT count(*) FROM orders WHERE customer_id = 'cus-01'; — how many orders a customer has. That's how you check one order was created, not two from a double click.
  • Sorting: ORDER BY created_at DESC at the end — "newest first": that's how you find the record you just created.
  • JOIN (linking tables). An order stores customer_id, while the customer's email lives in customer; JOIN combines the tables so you see both. The syntax is something to look up — early on it's enough to know it can be done.

Be careful with the production database

Two safety rules:

  • Work with the test database whenever possible, not the production one with real users; if production is your only access, don't run anything you're not sure about.
  • A tester almost always only reads (SELECT). Commands that change data (UPDATE, DELETE) must not be touched without a clear reason and understanding — you can corrupt data.

Where this applies

SQL turns checking from "it looks right on screen" into "it's definitely right in the database": you created an order — it's there once and with the right amount; you deleted it — the record is gone or marked as deleted. This is grey-box testing in action.

The usual stumbling block is fear: SQL looks like "programming." In practice SELECT ... WHERE covers the checks — it reads almost like plain English.

In short

  • Data lives in tables, and a tester needs one kind of query — the reading SELECT.
  • WHERE picks rows, and text in it goes in single quotes, spelled the way the database stores it.
  • "Screen versus database" is two queries: the object itself and the table linked to it.
  • NULL is not zero and not an empty string but "no value": an empty paid_at means nobody marked the order as paid.
  • count(*) catches duplicates from a double click: you expected one row and there are two.