{"id":45864,"date":"2026-11-10T06:09:19","date_gmt":"2026-11-10T06:09:19","guid":{"rendered":"https:\/\/www.withdata.com\/blog\/?p=45864"},"modified":"2026-11-10T06:25:38","modified_gmt":"2026-11-10T06:25:38","slug":"csv-import-four-failures","status":"publish","type":"post","link":"https:\/\/www.withdata.com\/blog\/filetodb\/csv-import-four-failures.html","title":{"rendered":"CSV Import: The Zip Code That Becomes an Integer, and the Text That Becomes Question Marks"},"content":{"rendered":"<p>Importing a CSV looks like it should be the easy half of data work: no server, no schema, just text and commas. It is the half that produces the strangest support threads \u2014 a file that imports &#8220;successfully&#8221; and a column that quietly lost its leading zeros, a name that arrived as <code>Caf??<\/code> <code>Z??rich<\/code>, a record that turned into two rows.<\/p>\n<p>There are four distinct failures behind almost all of those threads, and they are told apart by what you see afterwards, not by the error message \u2014 most of them do not raise one.<\/p>\n<table>\n<thead>\n<tr>\n<th>What you see<\/th>\n<th>What actually happened<\/th>\n<th>How to confirm it<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>01234<\/code> became <code>1234<\/code>, <code>0001234567<\/code> became <code>1234567<\/code><\/td>\n<td>the column was created as a number<\/td>\n<td>compare the first rows with the file<\/td>\n<\/tr>\n<tr>\n<td>the run finished, row count is too high<\/td>\n<td>a line break inside a quoted value split the record<\/td>\n<td>open the file in a text editor that shows control characters<\/td>\n<\/tr>\n<tr>\n<td><code>Caf?? Z??rich<\/code>, <code>?and? S.A.<\/code><\/td>\n<td>the text was decoded with the wrong codepage<\/td>\n<td>look for a non-ASCII character before and after<\/td>\n<\/tr>\n<tr>\n<td>&#8220;value too long for type character varying(n)&#8221;<\/td>\n<td>the target column is narrower than the data<\/td>\n<td>check the DDL of the created table<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>1. When a zip code becomes a number<\/h2>\n<p>The classic: a column of five-digit codes. <code>01234<\/code> is a valid <em>number<\/em> in every database on earth, so if the target column is an integer the import succeeds and the zero is gone \u2014 no error, no warning, and the data is wrong.<\/p>\n<p>Whether that happens is decided in one place: the types of the table you create. In <a href=\"https:\/\/www.withdata.com\/file-to-db\/\" target=\"_blank\" rel=\"noopener noreferrer\">FileToDB<\/a> that table is generated from the file&#8217;s own content, and the types are visible and editable before anything moves:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.withdata.com\/wp\/wp-content\/uploads\/2026\/11\/csvimport-03-create-table-focus.png\" alt=\"Create Table: the zip row, and the Data Type column where zip and part_no are varchar(255), amount is numeric, shipped is timestamp\" width=\"726\"><\/p>\n<p><code>zip<\/code> and <code>part_no<\/code> come out as <code>varchar(255)<\/code>, <code>amount<\/code> as <code>numeric<\/code>, <code>shipped<\/code> as <code>timestamp<\/code>. Leading zeros survive because nobody decided the column was a number.<\/p>\n<p>The failure is what happens when a human &#8220;cleans that up&#8221; \u2014 the same dialog lets you type any type, and <code>integer<\/code> looks tidier:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.withdata.com\/wp\/wp-content\/uploads\/2026\/11\/csvimport-04-create-table-integer-focus.png\" alt=\"The same dialog with zip changed to integer - part_no stays varchar(255) and the import then stores 1234 instead of 01234\" width=\"726\"><\/p>\n<p>Measured on the same four-row file, after that change:<\/p>\n<pre><code>order_id |  zip  |  part_no\n     1001 |  1234 | 0001234567\n     1002 |   501 | 0009988776\n     1004 |    12 | 0000000001<\/code><\/pre>\n<p>The import reported <code>4 records<\/code>, and not one row errored. Part numbers survived only because the type was left alone (<code>varchar(255)<\/code>); had they been &#8220;cleaned up&#8221; too, the same thing would have happened to them. Barcodes, employee numbers and account numbers are all this failure.<\/p>\n<p>The rule: look at the type column before the run, and give text-looking identifiers <code>varchar<\/code>. A value that keeps its leading zeros is text, whatever it is made of.<\/p>\n<h2>2. A comma, a quote, and a line break<\/h2>\n<p>A CSV is only simple until a field contains the delimiter. The standard answer is quoting \u2014 and it works, as long as the reader is told what the quote character is. The quoting settings in <a href=\"https:\/\/www.withdata.com\/file-to-db\/\" target=\"_blank\" rel=\"noopener noreferrer\">FileToDB<\/a> default to <code>\"<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.withdata.com\/wp\/wp-content\/uploads\/2026\/11\/csvimport-01-open-source.png\" alt=\"Open Source: file, encoding UTF-8, 5 rows, preview showing 01234 and a field with commas and quotes\" width=\"776\"><\/p>\n<p>The second row of that preview contains <code>has, comma and \"quotes\" inside<\/code> and arrives as <strong>one<\/strong> column, not four.<\/p>\n<p>The line break is the same problem with a worse default. A value with a newline inside is legal CSV; a reader that does not know it is possible treats the end of the line as the end of the record:<\/p>\n<pre><code>id,note\n1,\"line one\nline two\"\n2,plain<\/code><\/pre>\n<p>Two records. Import that without the multi-line option and you get <strong>three<\/strong>, the second one being <code>line two<\/code> in the <code>id<\/code> column and nothing in <code>note<\/code>. The switch is <code>Has Multi-Line Record<\/code> on the Open Source page (off by default, because most files do not need it), and the same setting exists for the command line:<\/p>\n<pre><code>FileToDB.exe import --from=export.csv --to=\"PostgreSQL:user:pass@127.0.0.1:5432\/db\" --table=notes ^\n                    --set=Load.SourceMultiLine=True<\/code><\/pre>\n<h2>3. Text that arrives as question marks<\/h2>\n<p>A <code>.csv<\/code> has no declared encoding. The bytes are one character each, and which character depends on the codepage the producing application used \u2014 cp1252 for a lot of Windows exports, UTF-8 for anything modern, cp936\/cp1251 for machines set up in another region.<\/p>\n<p>Get it wrong and the run still succeeds; you find out by reading the data:<\/p>\n<pre><code>name,city\nCaf?? Z??rich,Z??rich\nFran?ois Fr?res,Besan?on<\/code><\/pre>\n<p>Measured on a cp1252 file imported on a machine whose ANSI codepage is 936: two records, no error, and the accented characters replaced. The import does not stay silent about it any more \u2014 the log now starts with:<\/p>\n<pre><code>WARNING: source encoding - no BOM in \"cp1252.csv\" and the first 131072 bytes are not valid UTF-8\n(first bad byte at offset 16); assuming ANSI, the system codepage. If the text looks wrong, set the\nencoding explicitly.<\/code><\/pre>\n<p>That warning is the point: the last two failures cannot be seen in a row count, so the tool says which assumption it made. The fix is the <code>Encoding<\/code> field on the Open Source page \u2014 a list of named codepages (<code>UTF-8<\/code>, <code>UTF-8BOM<\/code>, <code>ANSI<\/code>, <code>CP1252 (Western European)<\/code>, <code>CP1251 (Cyrillic)<\/code>, <code>CP936 (Chinese Simplified)<\/code>, <code>ISO-8859-15<\/code>, \u2026). Pick the one the file was written in and the same bytes decode correctly; a file with a byte-order mark is read correctly without being asked.<\/p>\n<h2>4. Two smaller decisions worth making on purpose<\/h2>\n<p><strong>Empty is not always NULL.<\/strong> An empty field in a text column arrives as an empty string; an empty field in a numeric column arrives as <code>NULL<\/code>. Both are reasonable, and they are not the same thing in a query (<code>where note = ''<\/code> versus <code>where note is null<\/code>).<\/p>\n<p><strong>Money is not a float.<\/strong> On PostgreSQL the amount column above is <code>numeric<\/code>, which is exact. On targets without a decimal type the same column becomes a floating-point type, where <code>0.10<\/code> may come back as <code>0.1<\/code> and sums stop matching to the cent. If the target has a fixed-precision type, set it.<\/p>\n<h2>5. The whole import, in three steps<\/h2>\n<p><a href=\"https:\/\/www.withdata.com\/file-to-db\/\" target=\"_blank\" rel=\"noopener noreferrer\">FileToDB<\/a> runs the whole import as one session of three screens:<\/p>\n<ol>\n<li><strong>Open Source<\/strong> \u2014 pick the file, check the encoding, tick <code>Has Multi-Line Record<\/code> if the file has<\/li>\n<\/ol>\n<p>line breaks inside values, and read the preview: it shows the first 100 rows and the total, so the zip codes and the quoted commas are visible before anything is written.<\/p>\n<ol>\n<li><strong>Set Target<\/strong> \u2014 schema and table, <code>Load Type<\/code> (<code>Append<\/code> for a new table), and the <code>Columns Map<\/code>:<\/li>\n<\/ol>\n<p><img decoding=\"async\" src=\"https:\/\/www.withdata.com\/wp\/wp-content\/uploads\/2026\/11\/csvimport-02-set-target.png\" alt=\"Set Target: table orders, Append, all eight columns mapped, date format for shipped\" width=\"776\"><\/p>\n<ol>\n<li><strong>Create Table<\/strong> \u2014 the generated types (screenshot in \u00a71), edited where the data needs it; then<\/li>\n<\/ol>\n<p><code>Convert Data<\/code>, which reports the row count and the elapsed time.<\/p>\n<p>The command line runs the same session:<\/p>\n<pre><code>FileToDB.exe import --from=orders.csv --to=\"PostgreSQL:postgres@127.0.0.1:5432\/shop\" --table=orders<\/code><\/pre>\n<h2>FAQ<\/h2>\n<h3>Why did my leading zeros disappear?<\/h3>\n<p>The target column was created as a number. <code>01234<\/code> is a valid integer, so nothing fails \u2014 the zero is simply not part of the value. Create the column as <code>varchar<\/code> (or <code>text<\/code>) and re-import.<\/p>\n<h3>The import said 4 records but my file has 3. Why?<\/h3>\n<p>A line break inside a quoted value: the reader treated the second line as a new record. Turn on <code>Has Multi-Line Record<\/code> (or <code>--set=Load.SourceMultiLine=True<\/code>) and re-import.<\/p>\n<h3>Why do accented letters come out as question marks?<\/h3>\n<p>The file&#8217;s bytes were decoded with a different codepage than the one it was written in. The import warns when it has to assume; pick the matching encoding on the Open Source page. Files that declare their encoding with a byte-order mark are read correctly without a setting.<\/p>\n<h3>Can I import a file whose header row is missing?<\/h3>\n<p>Yes \u2014 leave <code>Field Name In File Header<\/code> unticked and the columns are numbered; rename them in <code>Create Table<\/code>, or map them on the Set Target page.<\/p>\n<h3>Is an empty CSV field NULL or an empty string?<\/h3>\n<p>In a text column, empty; in a numeric column, <code>NULL<\/code>. Decide before you write queries that compare against either one.<\/p>\n<h3>Can the import be scheduled?<\/h3>\n<p>Yes: save the task once in the GUI, then run the same session from the command line or the scheduler.<\/p>\n<h2>Trying it on your own file<\/h2>\n<blockquote>\n<p><strong>Import a CSV, see the types and the text before the rows land, and fix them in the dialog instead of in SQL.<\/strong> <a href=\"https:\/\/www.withdata.com\/file-to-db\/\" target=\"_blank\" rel=\"noopener noreferrer\">Try FileToDB<\/a> \u2014 Windows \/ macOS \/ Linux, one-time price. Moving a whole database rather than a file is <a href=\"https:\/\/www.withdata.com\/db-copier\/\" target=\"_blank\" rel=\"noopener noreferrer\">DBCopier<\/a>&#8216;s job.<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Importing a CSV looks like it should be the easy half of data work: no server, no schema, just text and commas. It is the half that produces the strangest support threads \u2014 a file that imports &#8220;successfully&#8221; and a column that quietly lost its leading zeros, a name that arrived as Caf?? Z??rich, a [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2772],"tags":[13864,13863,219,2475,13774],"class_list":["post-45864","post","type-post","status-publish","format-standard","hentry","category-filetodb","tag-csv-encoding","tag-csv-import","tag-csv-to-mysql","tag-csv-to-postgresql","tag-filetodb"],"_links":{"self":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts\/45864","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/comments?post=45864"}],"version-history":[{"count":5,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts\/45864\/revisions"}],"predecessor-version":[{"id":45873,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/posts\/45864\/revisions\/45873"}],"wp:attachment":[{"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/media?parent=45864"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/categories?post=45864"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.withdata.com\/blog\/wp-json\/wp\/v2\/tags?post=45864"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}