On this page

Validity benchmark

The realism benchmark asks whether generated data looks real. This one asks something more basic: does it obey its own schema? A seed that breaks a CHECK constraint, a composite key or a foreign key fails on INSERT, and test data that cannot be loaded is not test data.

Five SQL schemas, written the way production databases declare their rules:

SchemaWhat makes it hard
saas_billingUNIQUE emails, CHECK enums, CHECK (seats > 0 AND seats <= 500), CHECK (end_date > start_date)
org_charta self-referencing manager_id that must form a tree, CHECK (salary BETWEEN ...)
marketplace_m2ma junction table with a composite PRIMARY KEY, UNIQUE (order_id, line_no), CHECK (price >= cost)
hotel_bookingsCHECK (check_out > check_in), ranges on capacity and guests, a status enum
retail_chain_6_levelsa six-level foreign-key chain from regions to payments

Each schema is generated two ways:

  • misata_from_ddl: misata.from_ddl(ddl) then generate_from_schema, with the row counts set and nothing else configured.
  • faker_script: the script people usually write instead. One Faker or NumPy call per column, foreign keys drawn uniformly from the parent ids, CHECK clauses not read.
  • sdv_hma: SDV 1.38's multi-table model (HMA), used the way it is meant to be: fitted on a valid sample of the same schema (Misata's output under another seed, checked to have zero violations), told every primary and foreign key, and sampled at the same size. It is not given the CHECK clauses: SDV enforces those only when each one is re-declared as an SDV constraint, which is not what most users do. Optional; it runs only when sdv is installed.

The checker restates every rule in plain pandas and shares no code with Misata, so a pass is not Misata grading itself. It counts duplicate primary keys, orphaned foreign keys, NULLs in NOT NULL columns, duplicate UNIQUE values (single and composite), values outside CHECK enums and ranges, violated column-to-column CHECKs, and rows that sit on a cycle of a self-reference.

Results#

Misata 0.9.7, seed 7. Reproduce with

Shell
python benchmarks/validity_bench.py
SchemaGeneratorRowspk duplicatesfk orphansnullsunique duplicatesenum violationsrange violationscheck violationscyclesTotalSeconds
saas_billingmisata_from_ddl35,0000000000001.25
saas_billingfaker_script35,00000021132,9571,4951,487036,1501.09
saas_billingsdv_hman/an/an/an/an/an/an/an/an/acould not run: SynthesizerInputError: HMASynthesizer is not designed to handle a schema with more than 5 tables or relationship depth greater than 2.0
org_chartmisata_from_ddl5,0400000000000.03
org_chartfaker_script5,0400005209,96205,00015,0140.34
org_chartsdv_hma5,04004,73607100004,8076.33
marketplace_m2mmisata_from_ddl35,7850000000000.14
marketplace_m2mfaker_script37,06097002,115038,6221,553042,3870.04
marketplace_m2msdv_hma35,799155005,2920011005,557469.64
hotel_bookingsmisata_from_ddl32,3000000000000.17
hotel_bookingsfaker_script32,30000032420,00020,2079,885050,4161.1
hotel_bookingssdv_hma32,300000404002,65103,055660.02
retail_chain_6_levelsmisata_from_ddl45,8360000000000.15
retail_chain_6_levelsfaker_script45,836000023,975500024,0250.27
retail_chain_6_levelssdv_hman/an/an/an/an/an/an/an/an/acould not run: SynthesizerInputError: HMASynthesizer is not designed to handle a schema with more than 5 tables or relationship depth greater than 2.0
Rule violations per schema (lower is better)
  • Misata from DDL
  • Faker script
  • SDV HMA
saas_billing Misata from DDL: 0 Faker script: 36,150 SDV HMA: could not runsaas_billing036,150SDV HMA: could not runorg_chart Misata from DDL: 0 Faker script: 15,014 SDV HMA: 4,807org_chart015,0144,807marketplace_m2m Misata from DDL: 0 Faker script: 42,387 SDV HMA: 5,557marketplace_m2m042,3875,557hotel_bookings Misata from DDL: 0 Faker script: 50,416 SDV HMA: 3,055hotel_bookings050,4163,055retail_chain_6_levels Misata from DDL: 0 Faker script: 24,025 SDV HMA: could not runretail_chain_6_levels024,025SDV HMA: could not run060,000
Show the numbers
SchemaMisata from DDLFaker scriptSDV HMA
saas_billing036,150could not run
org_chart015,0144,807
marketplace_m2m042,3875,557
hotel_bookings050,4163,055
retail_chain_6_levels024,025could not run

Source: benchmarks/results/summary.json, validity.totals; Misata 0.9.7, seed 7

Misata reports zero violations on every schema. SDV's multi-table model, trained on valid data, still breaks rules it was never told: composite keys (5,292 repeated (order_id, line_no) pairs), check_out > check_in (2,651 stays) and the manager hierarchy (4,736 manager_id values pointing at no employee, because HMA does not model self-references). Its open-source version refuses two of the five schemas outright (more than five tables, or foreign keys more than two levels deep), and took 8 to 11 minutes to fit the two it could, where Misata takes under two seconds.

Two things Misata's result costs, stated plainly:

  • Composite keys can shorten a table. Where a composite key cannot be made unique by renumbering (a junction table of two foreign keys), repeated pairs are dropped, so product_tags delivers 5,045 of the 6,000 rows asked for. A sequence column in a composite key (line_no) is renumbered within its group instead, and only rows past the CHECK maximum are dropped.
  • Durations come from the column names, not your data. A date pair ordered by a CHECK is generated as the earlier date plus a duration that fits the names: stays (check_out > check_in) skew short with a median of three nights, shipping takes one to ten days, a contract's end_date runs one month to two years. Other pairs repaired by the constraint move by a gap drawn from the rows that already satisfied it.

What fixing this found#

This benchmark was written for 0.9.7 and failed on its first run. The fixes it forced are in the release:

  • from_ddl dropped every self-referencing foreign key, so manager_id was random integers that looped back on themselves. It now keeps them, and the generator builds the hierarchy as a forest.
  • Table-level CHECK (a > b) between two columns was ignored; it is now an inequality constraint.
  • Composite PRIMARY KEY (a, b) and UNIQUE (a, b) were ignored, and once declared they were enforced per batch, which the union of batches broke. They now hold over the whole table.
  • An inequality repair set the violating value equal to the other column, which breaks a strict > and piles rows up at a zero gap.
  • UNIQUE text columns skipped semantic generation, so a UNIQUE sku was filled with sentences and a regions.name with people's names.

Prefer generating data visually?

Misata Studio lets you turn natural language prompts into relational schemas with exact outcome invariants, live preview tables, and one-click database seeding without writing code.

Last updated Edit this page.md