CSV in SQL, ohne die Nullen zu verlieren
Aus einer CSV wird das Skript, fertig zum Einfügen: CREATE TABLE und INSERT für MySQL, PostgreSQL, SQLite oder SQL Server. Der Inhalt bestimmt die Typen, Postleitzahlen behalten ihre führende Null und bei jeder Spalte steht, warum sie so eingeordnet wurde.
Wann du es brauchst, und warum ein Skript besser ist
Die CSV kommt aus der Verwaltungssoftware, einem Online-Formular oder einer Google-Tabelle und muss in eine Datenbank. Import-Assistenten entscheiden die Typen selbst, und wenn sie sich irren, sagen sie es nicht: Du merkst es Monate später, wenn eine Postleitzahl ihre Null verloren hat.
Hier bekommst du stattdessen ein SQL-Skript: ein CREATE TABLE mit bereits gewählten Typen und die INSERT-Anweisungen mit jeder Zeile. Es ist Text, den du vor dem Ausführen lesen, in phpMyAdmin, DBeaver oder psql einfügen und auf einem anderen Computer genauso noch einmal ausführen kannst. Und die Tabelle verlässt nie deinen Browser: kein Upload, kein Konto.
Was zur Zahl wird und was Text bleibt
Es ist die Lehre aus CSV in Excel, und in einer Datenbank wiegt sie schwerer, weil sich dort ein falscher Typ nicht mit einem Klick beheben lässt: Eine Postleitzahl wie „00185“ in einer INTEGER-Spalte wird zu 185, und die Null kommt nie wieder. Deshalb wird eine Spalte als Text behandelt, wenn sie führende Nullen hat, wenn es ganze Zahlen mit 15 oder mehr Stellen sind (eine Kartennummer, eine IMEI: das sind Codes), wenn vor einer Ziffernfolge ein + steht (eine internationale Telefonnummer), wenn sie wie IBANs oder italienische Steuernummern aussehen, wenn ein Wert schon in wissenschaftliche Notation zerschossen ist, etwa „1,2E+15“, und wenn der Spaltenname auf einen Code hinweist (etwa „Code“, „ZIP“, „Tel.“ oder „IBAN“) und nur Ziffern darin stehen.
Den Rest bestimmt der Inhalt: INTEGER wenn alles ganze Zahlen sind, DECIMAL mit den richtigen Stellen, wenn es Dezimalstellen gibt, BOOLEAN wenn nur yes und no oder true und false darin stehen. Das Dezimaltrennzeichen wird in der Datei gezählt: „1.234,56“ ist die deutsche (kontinentale) Schreibweise, „1,234.56“ die englische. Verrät es keine Zahl und lässt sich „1.250“ auf zwei Arten lesen, entscheidet das Spaltentrennzeichen (bei Semikolons ist das Dezimaltrennzeichen ein Komma, bei Kommas ein Punkt), und die Seite schreibt es dazu. Die Zahlen kommen mit den Ziffern der Datei ins SQL, ohne Umweg über Gleitkommazahlen.
Wählst du einen Typ, den ein Wert nicht aufnehmen kann, zum Beispiel „ganze Zahl“ bei einer Spalte mit „k. A.“, nennt die Seite den Wert und die Zeile und schreibt kein SQL.
Datumsangaben, nur wenn die Reihenfolge sicher ist
„03/04/2024“ ist für Europäer der 3. April und für Amerikaner der 4. März, und die Datei sagt nicht, was gemeint ist. Deshalb wird eine Spalte mit Datumsangaben im Format TT.MM.JJJJ zu DATE, umgeschrieben in Jahr-Monat-Tag, wie es jede Datenbank liest, nur wenn die ganze Spalte eindeutig ist: Mindestens ein Datum muss einen Tag über 12 haben, und keines darf der Reihenfolge widersprechen. Sonst bleibt sie Text und die Seite sagt es; kennst du die Reihenfolge, wählst du sie im Menü. Datumsangaben, die schon als Jahr-Monat-Tag geschrieben sind, gehen immer durch, zweistellige Jahre nie, denn „24“ verrät das Jahrhundert nicht.
SQLite hat keinen echten Datumstyp: Es speichert das Datum als Text, und seine Funktionen verstehen nur die Form Jahr-Monat-Tag, also genau das, was hier herauskommt. SQL Server bekommt DATE und nicht DATETIME, das denselben Text je nach Sitzungssprache unterschiedlich liest.
Vier Datenbanken, vier Arten, dieselbe Zeile zu schreiben
Namen stehen in SQLite und PostgreSQL in doppelten Anführungszeichen, in MySQL in Backticks, in SQL Server in eckigen Klammern, und der Apostroph in „O'Brien“ wird überall verdoppelt. In MySQL und MariaDB ist auch der Backslash ein Sonderzeichen: So geschrieben, wie er ist, wird aus „C:\temp“ ein „C:“, ein Tabulator und „emp“. Hier wird der Backslash verdoppelt, das Skript beginnt mit SET NAMES utf8mb4, weil das alte „utf8“ Emojis ablehnt, und Texte werden mindestens VARCHAR(100): Unter 64 Zeichen behält InnoDB die Spalte innerhalb der Zeile, und mit vierzig solchen Spalten überschreitet die Zeile die 8.126 Bytes, die sie fassen kann.
SQL Server verlangt das N vor Strings, sonst werden die Buchstaben außerhalb seiner Codepage zu Fragezeichen, nutzt BIT für ja und nein (1 und 0, wie in SQLite) und akzeptiert höchstens tausend Zeilen in einem einzigen INSERT. Deshalb kommen die Zeilen in Gruppen, fünfhundert pro Anweisung, wenn du nichts anderes wählst, und jedes INSERT bleibt unter einem Megabyte, außer wenn eine einzelne Zeile allein mehr wiegt. Die INSERT-Anweisungen stehen in einer Transaktion, und in SQL Server mit SET XACT_ABORT ON, ohne das ein Fehler nur seine eigene Anweisung abbricht: Wenn die Ausführung stoppt, bleibt keine halb gefüllte Tabelle zurück. Das DROP TABLE IF EXISTS gibt es nur zusammen mit dem CREATE TABLE, und in SQL Server existiert es seit Version 2016.
Aus Spaltennamen werden echte Namen
„Preis netto (€)“, „Straße“ und „Größe“ werden zu preis_netto, strasse und grosse: Buchstaben ohne Umlaute und Akzente, Ziffern und Unterstriche, die man in einer Abfrage ohne Anführungszeichen schreiben kann. Ein Wort, das die Datenbank für sich reserviert, etwa order, group, user oder key, bekommt einen Unterstrich am Ende, sonst würde das erste von Hand geschriebene SELECT einen unverständlichen Fehler werfen. Zwei Spalten mit demselben Namen werden zu name und name_2, ein Name, der mit einer Ziffer beginnt, bekommt „c_“ davor, und über 63 Zeichen, die Grenze von PostgreSQL, wird er gekürzt. Die Zuordnung ist sichtbar, und jeder Name lässt sich von Hand umschreiben.
Leere Zellen: NULL oder leerer Text
Für eine Datenbank kann eine leere Zelle zweierlei sein: NULL, das Fehlen eines Werts, oder '', ein Text der Länge null. COUNT(phone) zählt die NULLs nicht, und WHERE phone = '' findet sie nicht. Bei Textspalten wählst du; in Spalten mit Zahlen, Datumsangaben und ja oder nein ist eine leere Zelle immer NULL, weil '' weder eine Zahl noch ein Datum ist. Dasselbe gilt für eine Zelle mit NULL oder \N wie in MySQL-Exporten: Dort kann es nichts anderes bedeuten. In einer Textspalte könnte es das Wort sein, und es bleibt so, zwischen Apostrophen, außer du setzt dafür das Häkchen.
Was das Tool nicht kann, offen gesagt
Es verbindet sich mit keiner Datenbank und führt nichts aus: Es schreibt das Skript. Es legt keine Primärschlüssel und keine Indizes an, denn die hängen davon ab, wie du die Tabelle nutzen wirst. Eine Zahl mit Euro- oder Prozentzeichen bleibt Text, ebenso Datumsangaben mit Uhrzeit; in SQLite auch Dezimalzahlen mit mehr als 15 Stellen, die seine Gleitkommadarstellung nicht hält. Es liest keine Excel-Dateien: Dafür ist der erste Schritt Excel (XLSX) in CSV. Ist die CSV unordentlich, lohnt sich vorher ein Durchgang durch CSV bereinigen. Und brauchst du sie für ein Programm und nicht für eine Datenbank, ist der richtige Weg CSV in JSON.
Und ganz ehrlich: Der Test für dieses Tool führt das Skript wirklich auf SQLite, PostgreSQL und MariaDB aus (das sich hier wie MySQL verhält) und liest die Zeilen aus der Tabelle zurück. SQL Server steht hier nicht zur Verfügung: Sein Skript wird von einem Parser geprüft, den andere geschrieben haben, aber kein echter SQL Server hat es ausgeführt. Und wenn in deinem MySQL NO_BACKSLASH_ESCAPES aktiv ist, bleiben die verdoppelten Backslashes verdoppelt.