Developer data protection
Debug SQL with a synthetic database
Build the smallest invented dataset that preserves the relationships and constraints behind the SQL problem. Do not start by uploading a production dump and masking a few columns. The downloadable SQLite fixture reproduces a join overcount with fictional rows and verified expected output, so the query can be discussed without customer records.
For Developers, DBAs and security engineering
Preserve the relationship that causes the bug, not the original records.
Entirely synthetic dataset. SQL outputs and two constraints verified offline; no AI or endpoint-policy test performed.01
Define the relational question
The example asks why a customer total becomes too high when shipping is added after joining orders to line items. The important feature is one order with multiple items. Customer names, real prices and a full production database are unnecessary to reproduce that relationship.
Write the expected result before asking an assistant to explain the query. A useful fixture includes a case that exposes the error and another that does not. That helps distinguish a correct fix from a query that merely happens to return plausible numbers for one row.
02
Preserve constraints and invent the rows
The pack creates customers, orders and order_items with primary keys, foreign keys and checks for non-negative amounts and positive quantities. All labels and values are invented. The schema is a generic teaching example, not copied from a company database.
When creating a fixture for your own issue, preserve only the relationships and edge cases needed for the question. Review whether schema names, comments or constraints themselves disclose proprietary information. Replacing names in copied production rows is not the same as building a synthetic fixture.
Source context: SQLite: Foreign Key Support · OAIC: Use of commercially available AI products
03
Compare the correct and overcounting queries
The intentionally incorrect query adds an order’s shipping once for every joined item. The synthetic first customer therefore totals 4,200 cents instead of 3,900. The second customer has one item, so both queries return 2,700 cents. This difference makes the cause observable without any real customer data.
The correct query aggregates item amounts per order, adds shipping once, then totals by customer. The expected CSV files include counts as well as amounts so the changed aggregation level is visible. These are locally verified SQL results for the fixture, not observations of an AI model or an Aona control.
| Synthetic customer | Overcounting query | Correct query |
|---|---|---|
| 1: SYNTHETIC_ALPHA | 4,200 cents; 3 joined rows | 3,900 cents; 2 orders |
| 2: SYNTHETIC_BETA | 2,700 cents; 1 joined row | 2,700 cents; 1 order |
Source context: SQLite: SELECT
04
Verify the fixture offline
The included Python verifier uses the standard sqlite3 library and an in-memory database. It checks the unchanged SQL files by hash, executes both queries and verifies that invalid foreign-key and zero-quantity rows are rejected. It opens no production database and makes no network or provider calls.
The fixture enables foreign-key enforcement explicitly, as SQLite documentation requires checking that setting for a connection. If you change the SQL to explore another case, keep that work separate and create new expected results. The supplied verifier deliberately refuses modified SQL rather than silently validating a different exercise.
Source context: SQLite: Foreign Key Support
Put it into practice
Synthetic SQLite join-debugging pack
Run a real relational fixture and compare an overcounting query with verified expected results.
Entirely synthetic dataset. SQL outputs and two constraints verified offline; no AI or endpoint-policy test performed.
Order 101
Two items share one shipping charge
The naive join adds shipping twice
Correct level
Aggregate items per order, then total per customer
Expected first total: 3,900 cents
Control case
One item on order 201
Both queries return 2,700 cents
| Check | Expected fixture result | Verification |
|---|---|---|
| Correct totals | 3,900 and 2,700 cents | Verified locally |
| Overcount example | 4,200 and 2,700 cents | Verified locally |
| Orphan order | Foreign-key rejection | Verified locally |
| Zero item quantity | Check-constraint rejection | Verified locally |
Work through your review
Use the checks to organise the evidence you need. Your selections stay in this tab.
0 of 3 reviewed
Example files for this task
Keep the source material and the instructions together. You can also download the complete worksheet or matrix as CSV.
README.mdInspect
# Synthetic SQLite debugging fixture
All rows, labels and amounts are invented. No customer records, credentials, external connection strings or API calls are included.
Question: why does adding order shipping after a join overcount the first customer?
The optional verifier uses only Python’s standard sqlite3 module and an in-memory database. Run verify_fixture.py from this extracted pack to check the unchanged SQL, both expected query outputs and two constraints. It does not open an existing database or use a network. Changed SQL is rejected by hash.
Review fixture.sql, overcounting-query.sql and correct-query.sql with the expected CSV files. If you choose to share the example with an approved AI client, share only these synthetic files and the question. Treat a suggested query as something to validate locally, not a verified production fix.
## Guide and sources
Canonical guide: https://aona.ai/resources/guides/database-fixtures-ai-debugging/
Source review: 2026-09-21
- SQLite: Foreign Key Support: https://www.sqlite.org/foreignkeys.html
- SQLite: SELECT: https://www.sqlite.org/lang_select.html
- OAIC: Use of commercially available AI products: https://www.oaic.gov.au/privacy/privacy-guidance-for-organisations-and-government-agencies/guidance-on-privacy-and-the-use-of-commercially-available-ai-products
Download README.mdfixture.sqlInspect
-- Fully synthetic SQLite fixture. No real customer data or credentials.
PRAGMA foreign_keys = ON;
CREATE TABLE customers (
customer_id INTEGER PRIMARY KEY,
label TEXT NOT NULL CHECK (label LIKE 'SYNTHETIC_%')
);
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(customer_id),
shipping_cents INTEGER NOT NULL CHECK (shipping_cents >= 0)
);
CREATE TABLE order_items (
item_id INTEGER PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(order_id),
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price_cents INTEGER NOT NULL CHECK (unit_price_cents >= 0)
);
INSERT INTO customers VALUES (1, 'SYNTHETIC_ALPHA'), (2, 'SYNTHETIC_BETA');
INSERT INTO orders VALUES (101, 1, 300), (102, 1, 500), (201, 2, 200);
INSERT INTO order_items VALUES (1001, 101, 2, 1000), (1002, 101, 1, 500), (1003, 102, 3, 200), (1004, 201, 1, 2500);
Download fixture.sqlcorrect-query.sqlInspect
-- Aggregate items per order before adding each order's shipping once.
WITH per_order AS (
SELECT o.order_id, o.customer_id,
o.shipping_cents + COALESCE(SUM(i.quantity * i.unit_price_cents), 0) AS total_cents
FROM orders AS o
LEFT JOIN order_items AS i ON i.order_id = o.order_id
GROUP BY o.order_id, o.customer_id, o.shipping_cents
)
SELECT customer_id, SUM(total_cents) AS total_cents, COUNT(*) AS order_count
FROM per_order
GROUP BY customer_id
ORDER BY customer_id;
Download correct-query.sqlovercounting-query.sqlInspect
-- Intentionally wrong for this task: shipping is added once per joined item.
SELECT o.customer_id,
SUM(i.quantity * i.unit_price_cents + o.shipping_cents) AS total_cents,
COUNT(*) AS joined_row_count
FROM orders AS o
JOIN order_items AS i ON i.order_id = o.order_id
GROUP BY o.customer_id
ORDER BY o.customer_id;
Download overcounting-query.sqlexpected-correct.csvInspect
customer_id,total_cents,order_count
1,3900,2
2,2700,1
Download expected-correct.csvexpected-overcounting.csvInspect
customer_id,total_cents,joined_row_count
1,4200,3
2,2700,1
Download expected-overcounting.csvverify_fixture.pyInspect
# Offline, in-memory SQLite verification of the unchanged synthetic pack.
import hashlib
import json
from pathlib import Path
import sqlite3
root = Path(__file__).parent
expected_hashes = {'fixture.sql': 'ea22bd66ff6182552a4c726dbba7a2634c5e49e0726ab2b471165a1bc1736250', 'correct-query.sql': 'bce555fad1354c7b3dde4a286bfdee0fe2ea9b98b93b229c06b23112f8cbf197', 'overcounting-query.sql': 'b5b71cb2aff59ee818442585c14410c3a538181064f92a6b14fb7bf90871d97f'}
texts = {}
for name, expected in expected_hashes.items():
text = (root / name).read_text()
if hashlib.sha256(text.encode()).hexdigest() != expected:
raise SystemExit("Refusing modified SQL: use the unchanged synthetic fixture.")
texts[name] = text
connection = sqlite3.connect(":memory:")
try:
connection.executescript(texts["fixture.sql"])
assert connection.execute("PRAGMA foreign_keys").fetchone()[0] == 1
correct_rows = connection.execute(texts["correct-query.sql"]).fetchall()
overcounting_rows = connection.execute(texts["overcounting-query.sql"]).fetchall()
assert correct_rows == [(1, 3900, 2), (2, 2700, 1)]
assert overcounting_rows == [(1, 4200, 3), (2, 2700, 1)]
rejected = []
for label, statement in [
("foreign_key", "INSERT INTO orders VALUES (999, 999, 0)"),
("positive_quantity", "INSERT INTO order_items VALUES (9999, 101, 0, 100)")
]:
try:
connection.execute(statement)
except sqlite3.IntegrityError:
rejected.append(label)
else:
raise AssertionError("Expected constraint rejection: " + label)
print(json.dumps({"fixture": "D18 synthetic only", "sqlite_version": sqlite3.sqlite_version,
"correct_rows": correct_rows, "overcounting_rows": overcounting_rows,
"constraints_rejected": rejected, "database": "in-memory", "network_calls": 0}))
finally:
connection.close()
Download verify_fixture.pyBefore you proceed
Keep these distinctions clear
- Masking a production dump and calling it synthetic
- Invent the rows and preserve only the relationship needed to reproduce the issue.
- Checking only a plausible total
- Include a case that exposes the error, a control case and the aggregation counts or constraints that explain it.
Apply it to employee AI use
Bring your actual data path.
Aona can help evaluate supported employee prompt or file submissions used for a coding question.
Aona does not provide database row permissions or guarantee inspection of every query, dump or MCP result. This fixture tests SQL locally, not an Aona policy.
Use the synthetic schema and query in a scoped demonstration of the supported submission path, keeping any real database outside the exercise.
Review your use caseFAQ
Questions for this decision
Is the fixture compatible with real SQLite?
Does the verifier connect to an existing database?
Can I use a production schema with fake rows?
Do the verified SQL outputs prove an AI-generated fix is safe?
Evidence behind the guide
Sources and scope
Prepared by Aona. Sources checked 2026-09-21. The cited material supports the specific points below; it does not certify a product or your use case.
- SQLite: Foreign Key Support
Documents foreign-key relationships and enabling enforcement for a connection.
vendor · checked 2026-09-21 - SQLite: SELECT
Documents joins, grouping and query evaluation used in the fixture.
vendor · checked 2026-09-21 - OAIC: Use of commercially available AI products
Explains privacy considerations for personal information supplied to AI.
regulator · checked 2026-09-21