30 Days Gen AI Risk Trial -Start Now
Skip to main content

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

Aona field notesD18
Relational fixture
3 orders. 4 items. One overcount.

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.

Compare the correct and overcounting queries
Synthetic customerOvercounting queryCorrect query
1: SYNTHETIC_ALPHA4,200 cents; 3 joined rows3,900 cents; 2 orders
2: SYNTHETIC_BETA2,700 cents; 1 joined row2,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

05

Share the fixture and the precise question

For an approved AI coding workflow, provide the fictional schema, relevant query, expected result and the question you want answered. Keep the production dump, connection string and customer records out. Review whether the selected account and input path are approved for the material you do share.

Use any suggested query as a hypothesis to test locally. A readable answer is not proof that the fix preserves constraints or works on other cases. This guide prepares a safe relational example; it does not claim that Aona governs database permissions or every SQL/MCP connection.

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.

The relationship that reveals the error
01

Order 101

Two items share one shipping charge

The naive join adds shipping twice

02

Correct level

Aggregate items per order, then total per customer

Expected first total: 3,900 cents

03

Control case

One item on order 201

Both queries return 2,700 cents

Synthetic SQLite join-debugging pack
CheckExpected fixture resultVerification
Correct totals3,900 and 2,700 centsVerified locally
Overcount example4,200 and 2,700 centsVerified locally
Orphan orderForeign-key rejectionVerified locally
Zero item quantityCheck-constraint rejectionVerified 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.md
fixture.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.sql
correct-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.sql
overcounting-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.sql
expected-correct.csvInspect
customer_id,total_cents,order_count
1,3900,2
2,2700,1
Download expected-correct.csv
expected-overcounting.csvInspect
customer_id,total_cents,joined_row_count
1,4200,3
2,2700,1
Download expected-overcounting.csv
verify_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.py

Before 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 case

FAQ

Questions for this decision

Is the fixture compatible with real SQLite?
Yes. It uses standard SQLite tables, constraints and queries. The bundled Python verifier executed it in memory, checked the expected rows and confirmed foreign-key and quantity-check failures.
Does the verifier connect to an existing database?
No. It creates an in-memory database only, accepts no connection string and makes no network calls. It rejects changes to the three bundled SQL files.
Can I use a production schema with fake rows?
First review whether the schema itself discloses proprietary information. Prefer a reduced, invented structure that preserves the relationship behind the bug and follows the owner’s permission decision.
Do the verified SQL outputs prove an AI-generated fix is safe?
No. They establish this fixture’s expected results. Any proposed change needs its own local tests, constraints and approved review before it is applied elsewhere.

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.

  1. SQLite: Foreign Key Support

    Documents foreign-key relationships and enabling enforcement for a connection.

    vendor · checked 2026-09-21
  2. SQLite: SELECT

    Documents joins, grouping and query evaluation used in the fixture.

    vendor · checked 2026-09-21
  3. OAIC: Use of commercially available AI products

    Explains privacy considerations for personal information supplied to AI.

    regulator · checked 2026-09-21
Synthetic SQLite fixtures for AI debugging