Guides
How to document a database you didn't design
To document a database, you record what each table and column means and how the tables really join, along with the rules that turn raw rows into the numbers people report. Start from the system catalog. Then profile the actual data, find the joins nobody declared, read the SQL your team already trusts, and have the person who owns each area approve the meanings. What you end up with is a data dictionary plus a map of relationships, and it needs to stay current as the schema changes.
If you inherited the database, you already know the hard part. The schema tells you a column is VARCHAR(20) and calls it ref2. What ref2 holds, and whether it joins to anything, lives in the heads of a few people who built the system or have been around long enough. The steps below are how to get it out of their heads and onto a page, in an order that saves their time.
Step 1: Decide what the documentation is for
Documentation for a migration, a reporting team, an audit or an AI agent overlaps, but the four aren't identical. A migration needs every table and every dependency. A reporting team needs the dozen tables behind the numbers that reach management. An audit needs owners and the places personal data sits. An AI agent needs meanings, joins and rules for whatever people are likely to ask about.
Pick the use first, then rank tables by how much they matter to it. On most legacy databases a minority of tables carries most of the activity, and some tables are empty or abandoned. Document the important ones properly before you touch the rest.
Step 2: Pull the catalog
Every relational database describes itself. In PostgreSQL, MySQL, Snowflake and Redshift that description lives in information_schema. In Oracle it's the data dictionary views such as ALL_TABLES and ALL_TAB_COLUMNS.
A starting query for PostgreSQL:
SELECT table_schema, table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY table_schema, table_name, ordinal_position;
And the foreign keys someone did declare:
SELECT tc.table_name, kcu.column_name,
ccu.table_name AS references_table,
ccu.column_name AS references_column
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
AND ccu.table_schema = tc.table_schema
WHERE tc.constraint_type = 'FOREIGN KEY';
Expect the second list to be short. Older systems often enforce relationships in application code and declare almost nothing.
Step 3: Profile the data
A column type gives you the shape of a value. The data tells you what it is. For each important column, look at row counts, null rates, distinct counts, minimum and maximum values, and the most common values.
-- PostgreSQL: a quick profile of one column
SELECT COUNT(*) AS row_count,
COUNT(DISTINCT status) AS distinct_values,
ROUND(100.0 * COUNT(*) FILTER (WHERE status IS NULL) / COUNT(*), 1) AS pct_null,
MIN(status) AS min_value,
MAX(status) AS max_value
FROM orders;
Profiling is where you find out that ref2 is null in 94% of rows, or that a "date" column holds text in three formats. It also flags tables that haven't had a new row in years.
Step 4: Find the joins nobody declared
This is the step that takes longest by hand. Candidate joins come from matching column names and types, but names mislead. Two numeric columns both called product_id can come from different systems and mean different things. Check the values instead: what share of the child column's values exist in the parent column?
-- PostgreSQL: does shipments.order_ref really point at orders.order_ref?
SELECT COUNT(*) AS shipment_rows,
COUNT(*) FILTER (WHERE o.order_ref IS NOT NULL) AS matched,
ROUND(100.0 * COUNT(*) FILTER (WHERE o.order_ref IS NOT NULL) / COUNT(*), 1) AS match_pct
FROM shipments s
LEFT JOIN (SELECT DISTINCT order_ref FROM orders) o
ON o.order_ref = s.order_ref;
Record every join you find as either declared (in the schema) or inferred (from the data), and write down the evidence for each inferred one. A future reader, human or AI, needs to know which joins they can lean on.
Step 5: Read the SQL people already trust
Views, stored procedures, saved report queries and the query log contain the business logic nobody wrote down, such as the filter that removes test accounts or the status codes that mean "cancelled". Collect the queries behind numbers that reach management and read their WHERE clauses closely. This is also where lineage gets recovered from the database itself, because the queries show which tables feed which reports.
Step 6: Decode the codes
Status codes, type flags and lookup IDs carry a lot of meaning in older systems. Sometimes it's worse: in one plant database, a single generic answer column held every value, and what a value meant depended on a numeric question ID. One ID was an energy meter reading and another was units produced, stored as text and needing conversion before anyone could do arithmetic on it. Find these patterns early, because every report built on them depends on getting the mapping right.
Step 7: Write the meanings, and get them approved
For each table and column, write a business name, a plain meaning, the grain (what one row represents), the unit, and the allowed values. Then put a name next to it: the person who can say "yes, that's right". Meanings nobody has approved are guesses with good formatting. Our data dictionary template has a column for each of these.
Step 8: Keep it current
Documentation decays the day after you finish it. Tie updates to schema changes: when a table or column changes, re-check the meanings and joins that depend on it before anyone reuses them. A data dictionary with a "last reviewed" date on every row is easier to trust than a beautiful one from two years ago.
Where tools help, and where the months go
Schema documentation tools such as SchemaSpy, Dataedo, dbdocs and DbSchema turn the catalog into browsable pages and diagrams, which covers step 2 and part of step 3. Data catalogs such as Atlan, DataHub and OpenMetadata add search and ownership across many systems. All of them are worth a look.
Steps 4 to 7 are where the months go, because they need judgment about meaning. That's the part Antcluster works on.
How Antcluster documents a database with you
Antcluster connects read-only to your database and does the slow parts of this guide, then hands the result to your engineers for review.
Start with what the business already knows
Ask questions in plain language from day one, before the documentation is finished, and get answers with the SQL and records behind them. You don't hand-build a semantic layer first.
Get the unwritten knowledge onto the page
Antcluster profiles the data, proposes meanings for columns and codes, and finds joins nobody declared, keeping them apart from declared foreign keys and showing the evidence and confidence for each. It reads the SQL your team already trusts and uses it to seed the business definitions. Your engineers approve or reject. Corrections stick, so a join someone rejects stays rejected.
Then look further
Once the database is mapped, you can ask questions that cross areas nobody had connected, and get candidates with evidence for the people who know the business to confirm.
Why this is possible now
Engineers have seen what happened to codebases. Claude Code and similar agent harnesses let a small team attempt work that once needed a much larger one, porting an entire console game to PC being the sort of thing teams now try. Part of the reason is that code documents itself well enough for an agent to check its own work: interfaces, types where the language has them, tests that pass or fail and builds that break.
A database documents its shapes and very little of its meaning. The meaning is in people's heads and in years of queries. When an agent can draft that meaning and an engineer can review it, data work gets what code already had. The result is less time reconstructing what tables mean before each project, and more time for the projects themselves. Data teams get to the migrations and agents that kept slipping, and people outside the data team can ask their own questions, whether the database belongs to a small company or a large group, and spot problems nobody had thought to look for.
It stays documented
The database, the agent and the documentation move together. Each investigation can add a reviewed meaning or a corrected join, and the next one starts from there. The ontology underneath is written in OWL 2 and expands itself as new tables and approved findings arrive, with an engineer reviewing what changes. When the schema changes, the affected logic gets re-checked before anyone reuses it.
How long it takes
Understanding a 2,000-table database typically takes a team of two to four analysts and engineers three to six months. Antcluster gets to the semantics of a database that size in a single day, ready for your engineers to review.
Get your database documented
Connect one database read-only, inside your network if you prefer. Antcluster drafts the data dictionary and the relationship map, and your engineers review them with the evidence in front of them.
Questions about documenting a database
What should database documentation include? At minimum: what each important table and column means, how tables join (declared and inferred), the allowed values for codes, the business rules behind reported numbers, and who owns each area.
How long does it take to document a database? For a large legacy database, done by hand, months. Steps 4 to 7 above take most of the time because they need people who know the business.
Can I generate database documentation automatically? The catalog part, yes, with free tools. The meaning part can be drafted automatically from the data and from trusted SQL, but someone who knows the business should approve it before it's relied on.
What's the difference between a data dictionary and database documentation? A data dictionary is one piece of database documentation: the table-by-table, column-by-column record. Full documentation adds relationships, business rules and ownership.