How to remove blank rows from a database csv export
- Step 1Export the table or query result as CSV — Use your exporter of choice: PostgreSQL
COPY (SELECT ...) TO STDOUT WITH CSV HEADER, MySQLSELECT ... INTO OUTFILEormysql --batch -e, SQL Serverbcpor SSMS → Save Results As → CSV. Note how the exporter renders NULL (empty,\N, orNULL). - Step 2Drop the export onto the Empty Row Remover — Drag the file into the dropzone. Processing starts automatically; the delimiter is detected from the file (tab for
mysql --batch, comma for COPY). There are no options to set. - Step 3Reconcile the counts — The panel shows Total rows in, Empty rows removed, and Rows out. Subtract the header row and
Rows outshould equal your sourceSELECT COUNT(*). IfEmpty rows removedis exactly 1, it was almost certainly the trailing-newline artefact. - Step 4Check the preview — Inspect the 10-row preview to confirm NULL columns still show their sentinel (
\N/NULL) and no real records were dropped. - Step 5Download the compacted CSV — Click Download; the file saves as
<original-name>.no-empty-rows.csv. - Step 6Load into staging / BI / warehouse — Ingest the cleaned file. Your post-load
COUNT(*)now matches the source count.
Exporter blank-row behaviour
How common database exporters produce empty rows and what this tool does. Removal requires every cell to be the literal empty string.
| Exporter | Typical blank-row cause | Default NULL rendering | Removed by tool? |
|---|---|---|---|
PostgreSQL COPY ... TO CSV | Usually none; a trailing newline can read as one empty row | Empty string (or custom via NULL '...') | All-empty rows removed; 0 and explicit NULL strings kept |
MySQL SELECT ... INTO OUTFILE | All-NULL row when fields rendered empty | \N by default | \N rows are KEPT (non-empty); only literal-empty rows removed |
mysql --batch -e | Tab-delimited; trailing newline | NULL literal | NULL rows KEPT; trailing empty line removed |
SQL Server bcp | Field/row terminators can leave a trailing empty row | Empty string | All-empty rows removed |
| SSMS → Save Results As CSV | Trailing newline; occasional blank between result sets | NULL literal | NULL rows KEPT; blank rows removed |
Empty vs NULL — what is removed
The tool removes a row only when every cell is the literal empty string. NULL sentinels are real strings and are preserved.
| Cell content | Treated as | Row removed if all cells are this? |
|---|---|---|
| (nothing — empty string) | Empty | Yes |
| \N | Non-empty (MySQL NULL sentinel) | No — kept |
| NULL (literal text) | Non-empty | No — kept |
| 0 | Non-empty | No — kept |
| a single space | Non-empty | No — kept |
Cookbook
Before/after rows from real database exports. Headers shown; NULL sentinels left intact.
Trailing-newline phantom row from a COPY export
ExamplePostgreSQL COPY ends the file with a newline. A lenient parser reads the dangling line as an empty row, so the loaded row count is one more than the source. The tool drops it.
Input (postgres COPY output, trailing newline): id,name,status 1,Alpha,active 2,Beta,active (blank final line) Output: id,name,status 1,Alpha,active 2,Beta,active
MySQL \N NULL sentinel is preserved (NOT a blank row)
ExampleA MySQL OUTFILE export renders NULL as \N. A row that is all-NULL becomes a row of \N cells — which the tool treats as non-empty and keeps, because \N is real text. This is correct: those are NULL records, not blank rows.
Input: id,email,phone 1,sue@x.com,555-0100 2,\N,\N 3,jon@x.com,\N Output (row 2 kept — \N is non-empty): id,email,phone 1,sue@x.com,555-0100 2,\N,\N 3,jon@x.com,\N
All-empty row from NULLs rendered as empty string
ExampleIf your COPY used NULL '' (render NULL as empty), an all-NULL row becomes a genuinely all-empty row and IS removed. Decide whether you want all-NULL records dropped before exporting this way.
Input (COPY ... WITH (FORMAT CSV, NULL '')): id,email,phone 1,sue@x.com,555-0100 ,, 3,jon@x.com, Output (all-empty row removed; row 3 kept — has id+email): id,email,phone 1,sue@x.com,555-0100 3,jon@x.com,
Tab-delimited mysql --batch export
Examplemysql --batch writes tab-separated output with NULL as a literal. The tool auto-detects the tab delimiter and removes only fully-empty lines; NULL rows are kept.
Input (tabs shown as →): id→name→note 1→Alpha→ok →→ 2→Beta→NULL Output (blank line removed, NULL kept): id→name→note 1→Alpha→ok 2→Beta→NULL
Row with only a primary key is preserved
ExampleA record that is NULL in every column except its primary key is still a real row. Because the key column is populated, the tool keeps it.
Input: id,name,email 10,, 11,Mia,mia@x.com ,, Output (id-only row kept; all-empty row removed): id,name,email 10,, 11,Mia,mia@x.com
Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Row of NULL sentinels (\N or NULL)
PreservedMySQL's \N and the literal word NULL are non-empty strings, so an all-NULL row is kept. The tool removes blank rows, not NULL records. If you want all-NULL rows gone, either render NULL as empty in the export, or use csv-column-filter on a key column afterwards.
Trailing-newline phantom row
RemovedA dangling final newline produces one all-empty row, which is removed. This usually accounts for the off-by-one between your source SELECT COUNT(*) and a naive load.
Row with only the primary key populated
PreservedIf every column is NULL/empty except the primary key, the row has a populated cell and is kept. Only rows where 100% of cells are empty are removed.
Cell containing 0
PreservedA 0 is meaningful data (a count, a flag, a balance), not a blank. A row of zeros is kept.
Quoted field with an embedded newline
PreservedA text column with a quoted multi-line value (e.g. an address or a comment) is parsed as one cell by PapaParse's RFC-4180 handling. The embedded newline does not create a phantom blank row, and the value is preserved exactly.
Output over 500 rows on free tier
Blocked (upgrade)The free tier caps the result at 500 output rows. Database exports easily exceed this — the tool shows an upgrade prompt. Pro raises the cap to 100,000 rows and 100 MB; Pro + Media and Developer tiers go to 500,000 and unlimited.
Export larger than 2 MB on free tier
Blocked (upgrade)The free file-size cap is 2 MB, which most production extracts exceed. Pro raises it to 100 MB. For genuinely large exports, consider chunking with a LIMIT/OFFSET query, or use the API/runner path on a paid tier.
Output has no UTF-8 BOM
ExpectedThe cleaned file is written without a BOM, which is correct for warehouse loaders (most expect plain UTF-8 and a BOM can corrupt the first column name). If your loader needs a BOM, add it in a downstream step.
Frequently asked questions
Why does a database CSV export have blank rows?
The most common cause is a trailing newline at end-of-file that a parser reads as an empty row. Others: batch-separator blank lines between result sets, and all-NULL records when the exporter renders NULL as an empty string. The tool removes only rows where every cell is empty.
Will this remove my NULL rows?
Only if the NULLs were exported as empty strings. If your export renders NULL as \N (MySQL OUTFILE default) or the literal NULL (mysql --batch, SSMS), those are non-empty strings and the rows are kept. The tool distinguishes empty from NULL sentinels.
Will it remove a row where only the primary key is populated?
No. A row is removed only if every cell is empty. A record that is NULL in most columns but has a populated key (or any one column) is kept.
Is this safe for large row-count exports?
Free handles up to 2 MB and 500 output rows — fine for samples and small tables but not production dumps. Pro raises this to 100 MB / 100,000 rows; Pro + Media to 500 MB / 500,000 rows; Developer is unlimited. The 500-row check applies to the output (post-removal) count.
Does it preserve a 0 value?
Yes. 0 is non-empty, so cells and rows containing zeros are preserved exactly. The tool never coerces zeros to blank.
Which delimiter does it expect?
It auto-detects comma, semicolon, or tab from the file, so PostgreSQL COPY (comma), mysql --batch (tab), and EU-locale exports (semicolon) all work without a setting. The output preserves the detected delimiter.
Will the output mess up my warehouse load?
No — the output is plain UTF-8 CSV with no BOM, which is what most warehouse loaders (Snowflake, BigQuery, Redshift) expect. A BOM can otherwise corrupt the first column name, so the absence of one is correct here.
Is my production data uploaded anywhere?
No. Parsing and row removal run entirely in your browser tab via PapaParse. Export contents never reach a server. Only an anonymous usage counter is recorded when signed in.
How do I drop all-NULL records, not just empty rows?
If NULL exports as a sentinel like \N, this tool won't remove those rows. Use csv-column-filter with an is_not_empty check on a key column, or csv-find-replace to turn \N into an empty string first, then run this tool.
Can I automate this in a pipeline?
Yes. The tool is exposed in the public CSV API; GET /api/v1/tools/csv-empty-row-remover returns its schema (it takes no options), and you can POST the CSV content to run it. Pair the @jadapps/runner to keep data local. A common pipeline is: nightly export → empty-row-remover → staging load.
What's a good follow-up tool?
After compacting, run csv-validator to confirm column counts are consistent before load, or csv-deduplicator if your join produced duplicate rows. Use csv-row-splitter to chunk a large export into load-sized files.
Will a quoted multi-line text column break the row count?
No. A properly quoted field containing a newline (a comment, an address) is parsed as a single cell via RFC-4180 rules, so it does not create a phantom blank row and the value is preserved exactly. Only genuinely all-empty lines between records are removed.
Privacy first
Processing runs locally in your browser with PapaParse. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.