Normalization checker
Paste a spreadsheet β a header line and some rows, as CSV, TSV, semicolon or pipe separated β and see where it breaks first, second and third normal form: the lists hiding inside cells, the columns that tell one row from another, and the columns that depend on something other than the key. You get a proposed set of tables as CREATE TABLE sketches, PostgreSQL first with the SQL Server types in comments. The rules are the ones from Normalization with a real example, and the example below is that post's six-row visits sheet.
Your sheet is checked on the server and forgotten. Nothing is stored and nothing is logged: the rows are counted in memory and the answer comes straight back. Prefer not to paste real patient or customer data? Swap the names for made-up ones that repeat the same way. The analysis only cares which values repeat together, not what they say.
Check these first
What the rows show
Free API β use it from your own tools
The same engine powers an open API (this page uses it too). No signup, no key β just a rate limit of 60 requests per minute per IP. Handy for checking an export before it becomes a table, or in an import script that should stop when a column turns out to hold a list. The API stores nothing.
curl
curl -X POST https://www.coder000.com/api/v1/normalization-checker \
-H "Content-Type: application/json" \
-d '{"input": "row_no,doctor_name,doctor_room\n1,Dr. Elena Rossi,Room 1\n2,Dr. Elena Rossi,Room 1\n3,Dr. Kwame Mensah,Room 2"}'
C#
using var http = new HttpClient();
var csv = await File.ReadAllTextAsync("visits.csv");
var response = await http.PostAsJsonAsync(
"https://www.coder000.com/api/v1/normalization-checker",
new { input = csv });
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine(result.GetProperty("output").GetString()); // the CREATE TABLE sketches
foreach (var w in result.GetProperty("warnings").EnumerateArray())
Console.WriteLine("! " + w.GetProperty("change").GetString());
Uniform response shape: { "output": "β¦", "notes": [...], "warnings": [...] }.
output is the SQL sketch, or null when the paste could not be read as a sheet. Each note
is one finding (the separator and row count, a candidate key, a list, a dependency, a proposed table)
with a change, an explanation and a link to the post behind the
rule. warnings always start with the reminder that everything was counted over your rows.
Limits: 100,000 characters, 5,000 rows and 60 columns per request.
How it works, and what it cannot do
No AI, and no guessing at what your columns mean: the checker counts. It takes the separator from the header line (whichever of tab, comma, semicolon or pipe appears most there), lets a value in double quotes hold that separator, and compares cells ignoring case and extra spaces. Then it makes four passes over your rows.
- Lists inside cells (first normal form). A column is a repeating group when at least two rows hold several values of the same kind, split by a comma and a space, a semicolon, or a spaced pipe. Lists with the same count in every row are paired by position and go into one child table. A column where every cell has exactly the same number of parts, such as "Garcia, Maria", is read as one structured value rather than a list, and a column named like notes, comments or address is never split.
- Candidate keys. A single column with no repeated value and no empty cell; if there is none, the first pair of columns that is distinct in every row, then the first triple. An integer row number wins the choice of row key.
- Dependencies (second and third normal form). A β B holds when every value of A that repeats always comes with the same value of B, on the rows where both are filled, and B is not the same everywhere. Columns that determine each other become one entity (doctor_name β doctor_room); if A β B and B β C, C is reported under B only. The same pass runs inside the split lists, which is how a price that depends on the medicine alone is caught.
- Proposed tables. One table per entity, with a surrogate id and the naming column as UNIQUE; one child table per list, keyed on the row and the value; and the rest of the sheet as the main table, pointing at the entities. PostgreSQL types, with the SQL Server type in a comment where it differs, parents first so the script runs top to bottom.
What it cannot do is tell a rule from a coincidence. The analysis is counting over the pasted rows, not a proof: a dependency that holds in six rows may not hold in six thousand, and a column that happens to be distinct in a sample is not unique by rule, which is why the sketch marks those with a comment instead of a constraint. It does not know that two patients can share a name, that a doctor may move rooms next month, or which of two columns that match one to one is the real identifier. It reads at most 5,000 rows and 60 columns, compares values as text (2.5 and 2.50 count as different), and names tables by simple English rules (doctor_name β doctors) that you may want to change. Treat the output as the first draft of a design review, then check every finding against how the business actually works.