Test Data Generation
Determine optimal row counts for test database tables satisfying schema and referential integrity constraints.
What this template is for
Generating realistic test databases requires careful coordination of row counts across tables. Foreign key relationships, cardinality bounds, and coverage requirements create complex interdependencies — for example, if each order must have 1-3 line items, then 800 orders can have at most 2,400 order lines. Manually choosing row counts that satisfy all these constraints while staying close to target sizes is tedious and error-prone.
This template uses Prescriptive reasoning to find optimal row counts for each table in a test database. It encodes referential integrity constraints, cardinality bounds, mandatory participation rules, and coverage requirements as a linear program, then minimizes weighted deviation from target row counts. Higher-priority tables stay closer to their targets.
After determining row counts, the script generates actual test data records with realistic values — emails, dates, prices, and proper foreign key references — giving you a complete, constraint-consistent test dataset.
Who this is for
- QA engineers who need realistic test databases with consistent referential integrity
- Database developers building test fixtures for integration testing
- Data engineers validating ETL pipelines with controlled synthetic data
What you’ll build
- A linear programming model that balances row count targets against schema constraints
- Referential integrity constraints encoding foreign key cardinality bounds
- Mandatory participation and coverage requirements
- A data generation phase that produces actual CSV-ready test records
What’s included
test_data_generation.py— Main script for row count optimization and data generationdata/testgen_schema.csv— Table and column definitions with types and foreign keysdata/testgen_constraints.csv— Cardinality bounds, coverage, and participation rulesdata/testgen_targets.csv— Target, minimum, and maximum row counts per table with prioritiespyproject.toml— Python package configuration
Prerequisites
Access
- A Snowflake account that has the RAI Native App installed.
- A Snowflake user with permissions to access the RAI Native App.
Tools
- Python >= 3.10
Quickstart
-
Download ZIP:
Terminal window curl -O https://docs.relational.ai/templates/zips/v1/test_data_generation.zipunzip test_data_generation.zipcd test_data_generation -
Create venv:
Terminal window python -m venv .venvsource .venv/bin/activatepython -m pip install --upgrade pip -
Install:
Terminal window python -m pip install . -
Configure:
Terminal window rai init -
Run:
Terminal window python test_data_generation.py -
Expected output:
Status: OPTIMALTotal weighted deviation: 1200.00Optimal row counts:Customer: 100 rows (target: 100, deviation: 0)Product: 500 rows (target: 500, deviation: 0)Order: 800 rows (target: 800, deviation: 0)OrderLine: 2400 rows (target: 5000, deviation: 2600)Supplier: 20 rows (target: 20, deviation: 0)SupplierProduct: 1500 rows (target: 1500, deviation: 0)==================================================GENERATED TEST DATA==================================================Customer: 100 rowscustomer_id email region created_date1 user1_abcd@gmail.com North 2020-03-15Product: 500 rowsproduct_id name category price1 Electronics Item 1 Clothing 42.99Order: 800 rowsorder_id customer_id order_date status1 47 2023-06-12 delivered
Template structure
.├── README.md├── pyproject.toml├── test_data_generation.py└── data/ ├── testgen_constraints.csv ├── testgen_schema.csv └── testgen_targets.csvHow it works
1. Load schema metadata
The script reads three CSV files describing the database schema: table definitions with column types and foreign keys, constraints (cardinality bounds, coverage, mandatory participation), and row count targets with priorities:
schema_df = read_csv(data_dir / "testgen_schema.csv")constraints_df = read_csv(data_dir / "testgen_constraints.csv")targets_df = read_csv(data_dir / "testgen_targets.csv")2. Define decision variables
Each table gets two decision variables — actual row count and deviation from target:
Table.x_actual_rows = Property(f"{Table} has actual {Float:actual_rows}")Table.x_deviation = Property(f"{Table} has {Float:deviation}")
problem.solve_for(Table.x_actual_rows, name=["n", Table.table_name], lower=Table.min_rows, upper=Table.max_rows)problem.solve_for(Table.x_deviation, name=["dev", Table.table_name], lower=0)3. Encode referential integrity constraints
Foreign key relationships impose bounds linking child and parent row counts. For example, if each order has 1-3 order lines, the OrderLine count must be between 1x and 3x the Order count:
ParentTable = Table.ref()problem.satisfy(model.require( Table.x_actual_rows <= ParentTable.x_actual_rows * fk_info['max']).where( Table.table_name == child_name, ParentTable.table_name == parent_name))4. Minimize weighted deviation
The objective minimizes deviation from targets, weighted by priority so that critical tables stay closer to their targets:
total_deviation = rai_sum(Table.x_deviation * (11 - Table.priority))problem.minimize(total_deviation)5. Generate test records
After solving, the script generates actual data records with realistic values using the computed row counts:
generated_data = generate_test_data(row_counts)Customize this template
- Add your own schema by editing the three CSV files to match your database structure with its constraints and targets.
- Adjust the scale factor by changing
scale_factorto proportionally scale all targets up or down. - Extend data generation to produce additional column types or more realistic value distributions for your domain.
- Add uniqueness constraints to ensure generated foreign key references follow realistic distributions rather than uniform random.
Troubleshooting
Solver returns INFEASIBLE
The constraints are contradictory — for example, a cardinality bound may require more child rows than the maximum allows. Review testgen_constraints.csv and check that the min/max row ranges in testgen_targets.csv are compatible with the cardinality bounds. Widen the min/max row ranges or relax cardinality constraints.
Large deviations from target row counts
This is expected when constraints are tight. For example, if each order has at most 3 order lines and there are 800 orders, the OrderLine count cannot exceed 2,400 even if the target is 5,000. Review the constraints to understand which ones are binding.
ModuleNotFoundError: No module named 'relationalai'
Make sure you activated the virtual environment and ran python -m pip install . to install all dependencies listed in pyproject.toml.
Connection or authentication errors
Run rai init to configure your Snowflake connection. Verify that your account has the RAI Native App installed and that your user has the required permissions.