SynthForge SynthForge SynthForge IO

Integration · DuckDB

Generate DuckDB test data, with foreign keys intact

Realistic, referentially-correct multi-table datasets you can load into DuckDB with one COPY. Free, no credit card.

Short answer

Design your tables in SynthForge, generate, and export the DuckDB bundle. You get DuckDB-dialect CREATE TABLE statements with foreign keys inline, CSV data files, and an import_duckdb.sql script that loads everything with native COPY commands. Every child row references a real parent row by construction, and the whole thing lands in a synthforge_import schema.

See the data

first_name last_name date_of_birth
Tammy Morrow 1969-08-30
Paul Pisacane 1913-10-03
Raymond Leung 1951-01-30
Hildreth Lowery 1918-05-10
Joseph Torres 1950-04-28
Robert Mote 1936-09-25
Kaylee Bean 2023-03-24
Marcella Noha 2010-03-17
Sierra Jenkins 2000-09-11
Monique McGreevy 2006-08-16
Reese Blair 2016-01-18
Montana Friel 2004-05-09

Person names follow US Census and SSA frequencies. First names match birth year on the same row.

Download sample pack customers, products, orders, order_items · SQL + CSV · No signup required

Try these after you load the pack

sql · orphan check (expect 0)
SELECT COUNT(*) AS orphan_orders
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id
WHERE c.customer_id IS NULL;
sql · top surnames
SELECT last_name, COUNT(*) AS n,
       ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 3) AS pct
FROM customers
GROUP BY last_name
ORDER BY n DESC
LIMIT 10;
sql · 1950s first-name cohort
SELECT first_name, COUNT(*) AS n
FROM customers
WHERE EXTRACT(YEAR FROM CAST(date_of_birth AS DATE)) BETWEEN 1950 AND 1959
GROUP BY first_name
ORDER BY n DESC
LIMIT 10;

Why generate rather than hand-roll

If you have ever populated a DuckDB file for a demo or a benchmark, you have probably written a script with random values, then discovered your orders reference customers that do not exist, then added dedup and parent-lookup code that has nothing to do with what you were testing.

SynthForge resolves the table dependency graph, generates parents first, and draws each foreign key from the parent IDs that actually exist, so the data is referentially valid the moment it lands. A single COPY per table loads it, with no server to stand up.

How to do it

1. Define the schema

Describe it in plain English ("a customers table, an orders table with a customer_id foreign key, line_items referencing orders"), build it in the visual editor, or paste a CREATE TABLE script and let the importer pick out tables, columns, and single-column foreign keys.

2. Set distributions and cardinality (optional)

Numeric columns can follow Normal, LogNormal, Exponential, or Triangular distributions. Set per-relationship ratios like "5 to 20 orders per customer" instead of hand-typing a row count for every child table. This is the step that makes the data look real.

3. Generate and export DuckDB

Pick DuckDB as the SQL dialect. The export bundle contains import_duckdb.sql (the CREATE TABLE DDL with foreign keys declared inline, plus a COPY statement per table) and one csv/<table>.csv data file per table.

4. Load it

Run from the extracted bundle directory so the csv/ paths resolve. One command creates every table and loads every row. Everything goes into a synthforge_import schema, so running this against a database you already use will not touch your own tables:

bash
duckdb mydb.duckdb < import_duckdb.sql

# the script runs native COPY statements like:
#   COPY synthforge_import.customers FROM 'csv/customers.csv' (FORMAT CSV, HEADER);
#   COPY synthforge_import.orders    FROM 'csv/orders.csv'    (FORMAT CSV, HEADER);

# query them by schema:
#   SELECT count(*) FROM synthforge_import.customers;
#
# somewhere else? find and replace synthforge_import first.

5. (Alternative) Use the Parquet export for analytics

SynthForge also exports Parquet, which DuckDB reads natively. Handy when you are benchmarking analytical queries rather than loading tables.

sql
CREATE TABLE orders AS
SELECT * FROM read_parquet('parquet/orders.parquet');

Frequently asked questions

Does DuckDB enforce the foreign keys?
DuckDB supports FOREIGN KEY constraints and SynthForge declares them inline in the CREATE TABLE statements. Independent of enforcement, the data is referentially valid by construction: child foreign keys are sampled from generated parent IDs, so every reference resolves.
How large a dataset can I generate?
Up to 1,000,000 rows per table and 1,000,000 rows per dataset, plus a 200 MB cap on the generated download and per-account rate limits. The download cap is what binds on wide, text-heavy tables: 25 columns including free text reaches 200 MB at roughly 575,000 rows. A typical 15-column table generates 200,000 rows in about 12 seconds.
Can I get the same schema for Postgres or another engine later?
Yes. The same schema exports to PostgreSQL, MySQL, SQLite, SQL Server, MariaDB, DuckDB, and CockroachDB, plus JSON, JSONL, and Parquet.
Does SynthForge use my real data?
No. SynthForge generates greenfield data from the schema you give it; it does not ingest or de-identify a real database. If you need to anonymize real production data, that is a different tool (see Tonic or NVIDIA NeMo).

Related

Ready to get started?

Design a multi-table schema, generate referentially-intact data, and export the DuckDB bundle. No credit card.