DEV Community

Cover image for Webtabellen in SQL INSERT Statements umwandeln
circobit
circobit

Posted on

Webtabellen in SQL INSERT Statements umwandeln

Man hat eine Tabelle auf einer Webseite. Man braucht sie in der Datenbank.

Der manuelle Weg: nach Excel kopieren, bereinigen, als CSV exportieren, CREATE TABLE von Hand schreiben, LOAD DATA oder COPY verwenden, Fehler debuggen.

Der bessere Weg: komplettes SQL direkt generieren — CREATE TABLE mit erkannten Typen, INSERT-Anweisungen mit korrektem Escaping.

So baut man einen Web-Tabelle-zu-SQL-Konverter.

Das Ausgabeformat

Ein vollständiger SQL-Export sollte enthalten:

-- Exportiert mit HTML Table Exporter PRO

CREATE TABLE produkte (
  produkt_id INTEGER,
  name TEXT,
  preis REAL,
  auf_lager TEXT
);

INSERT INTO produkte (produkt_id, name, preis, auf_lager) VALUES
  (1, 'Widget', 29.99, 'true'),
  (2, 'Gadget', 49.99, 'false'),
  (3, 'O''Brien''s Spezial', 19.99, 'true');
Enter fullscreen mode Exit fullscreen mode

Anforderungen:

  1. Gültiger Tabellenname (SQL-sicherer Bezeichner)
  2. Gültige Spaltennamen (keine Leerzeichen, keine Sonderzeichen)
  3. Passende Spaltentypen (INTEGER, REAL, TEXT)
  4. Korrekt escapte Werte (einfache Anführungszeichen verdoppelt)
  5. NULL-Behandlung

Schritt 1: Bezeichner bereinigen

SQL-Bezeichner (Tabellen- und Spaltennamen) haben strenge Regeln:

  • Keine Leerzeichen oder Sonderzeichen
  • Darf nicht mit einer Ziffer beginnen
  • Sollte für Portabilität kleingeschrieben sein
function sanitizeSqlIdentifier(name, fallback) {
  let id = (name || "").toString().trim();

  if (!id) id = fallback || "col";

  id = id
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")  // Akzente entfernen (café → cafe)
    .toLowerCase()
    .replace(/[^a-z0-9_]+/g, "_")     // Ungültige Zeichen durch Unterstrich ersetzen
    .replace(/^_+|_+$/g, "");          // Führende/nachfolgende Unterstriche entfernen

  // SQL-Bezeichner dürfen nicht mit einer Ziffer beginnen
  if (/^[0-9]/.test(id)) {
    id = "_" + id;
  }

  if (!id) id = fallback || "col";
  return id;
}
Enter fullscreen mode Exit fullscreen mode

Beispiele:

  • „Produktname" → „produktname"
  • „Preis (€)" → „preis"
  • „2024 Umsatz" → „_2024_umsatz"
  • „Préço" → „preco"

Schritt 2: Eindeutige Spaltennamen generieren

Tabellen können doppelte Header haben. SQL kann keine doppelten Spaltennamen haben.

function generateColumnNames(headerRow) {
  const usedNames = new Set();

  return headerRow.map((header, index) => {
    const base = sanitizeSqlIdentifier(header, `col_${index + 1}`);
    let candidate = base;
    let counter = 1;

    while (usedNames.has(candidate)) {
      candidate = `${base}_${counter}`;
      counter++;
    }

    usedNames.add(candidate);
    return candidate;
  });
}
Enter fullscreen mode Exit fullscreen mode

Beispiele:

  • ["Name", "Name", "Wert"] → ["name", "name_1", "wert"]
  • ["", "", "Daten"] → ["col_1", "col_2", "daten"]

Schritt 3: Spaltentypen erkennen

SQL hat drei Haupttypen, die uns interessieren:

  • INTEGER: Ganzzahlen
  • REAL: Dezimalzahlen
  • TEXT: Alles andere

Die Erkennung prüft Stichproben aus der Spalte und wählt den spezifischsten Typ, der zu allen Werten passt:

function inferSqlColumnTypes(rows, headerRowIndex = 0) {
  const headerRow = rows[headerRowIndex] || [];
  const dataRows = rows.slice(headerRowIndex + 1);

  const colCount = headerRow.length;
  const types = new Array(colCount).fill("TEXT");

  for (let col = 0; col < colCount; col++) {
    // Bis zu 50 nicht-leere Werte als Stichprobe
    const values = [];

    for (let r = 0; r < dataRows.length && values.length < 50; r++) {
      const cell = dataRows[r][col];
      const v = cell != null ? String(cell).trim() : "";
      if (v !== "") values.push(v);
    }

    if (values.length === 0) {
      types[col] = "TEXT";
      continue;
    }

    // Prüfen ob alle Werte ganzzahlig sind
    let allInt = true;
    let allNumeric = true;

    for (const v of values) {
      if (!/^[-+]?\d+$/.test(v)) {
        allInt = false;
      }
      if (!/^[-+]?\d+([.,]\d+)?$/.test(v)) {
        allNumeric = false;
      }
    }

    if (allInt) {
      types[col] = "INTEGER";
    } else if (allNumeric) {
      types[col] = "REAL";
    } else {
      types[col] = "TEXT";
    }
  }

  return types;
}
Enter fullscreen mode Exit fullscreen mode

Wichtige Entscheidungen:

  • Nur 50 Werte als Stichprobe für Performance bei großen Tabellen
  • INTEGER ist strenger als REAL (keine Dezimalstellen erlaubt)
  • Wenn IRGENDEIN Wert nicht passt, Fallback auf TEXT
  • Leere Spalten bekommen standardmäßig TEXT

Schritt 4: Werte escapen

SQL-Strings erfordern das Escaping einfacher Anführungszeichen durch Verdopplung:

function sqlEscapeValue(raw, type) {
  // NULL-Behandlung
  if (raw == null) return "NULL";

  const v = String(raw).trim();
  if (v === "") return "NULL";

  // Für numerische Typen unquotierte Zahl zurückgeben
  if (type === "INTEGER" || type === "REAL") {
    const normalized = v.replace(",", ".");  // EU-Dezimalformat behandeln
    const num = Number(normalized);

    if (!Number.isNaN(num) && Number.isFinite(num)) {
      return normalized;
    }
    // Durchfallen zu TEXT-Behandlung wenn keine gültige Zahl
  }

  // TEXT oder Fallback: einfache Anführungszeichen escapen
  const escaped = v.replace(/'/g, "''");
  return `'${escaped}'`;
}
Enter fullscreen mode Exit fullscreen mode

Beispiele:

  • "Hallo"'Hallo'
  • "O'Brien"'O''Brien'
  • "Er sagte 'ja'"'Er sagte ''ja'''
  • 123 (INTEGER) → 123
  • 45.67 (REAL) → 45.67
  • ""NULL
  • nullNULL

Schritt 5: Das vollständige SQL generieren

function tableToSqlString(tableInfo) {
  const rows = tableInfo.rows || [];
  if (!rows.length) return "";

  const headerRowIndex = tableInfo.headerRowIndex || 0;
  const headerRow = rows[headerRowIndex];
  const dataRows = rows.slice(headerRowIndex + 1);

  if (!headerRow) return "";

  // Sichere Spaltennamen generieren
  const columnNames = generateColumnNames(headerRow);

  // Spaltentypen erkennen
  const types = inferSqlColumnTypes(rows, headerRowIndex);

  // Sicheren Tabellennamen generieren
  const rawTableName = tableInfo.slug || tableInfo.name || "table";
  let tableName = sanitizeSqlIdentifier(rawTableName, "table");
  if (!tableName) tableName = "table_export";

  // CREATE TABLE BAUEN
  const createLines = columnNames.map((col, i) => 
    `  ${col} ${types[i] || "TEXT"}`
  );
  const createStmt = `CREATE TABLE ${tableName} (\n${createLines.join(",\n")}\n);`;

  // INSERT-ANWEISUNGEN BAUEN
  const insertHeader = `INSERT INTO ${tableName} (${columnNames.join(", ")}) VALUES`;

  const valueLines = dataRows.map(row => {
    const values = columnNames.map((_, i) => {
      const cell = row[i];
      const type = types[i] || "TEXT";
      return sqlEscapeValue(cell, type);
    });
    return `  (${values.join(", ")})`;
  });

  // KOMBINIEREN
  let sql = `-- Exportiert mit HTML Table Exporter PRO\n\n${createStmt}\n\n`;

  if (valueLines.length) {
    sql += `${insertHeader}\n${valueLines.join(",\n")};\n`;
  }

  return sql;
}
Enter fullscreen mode Exit fullscreen mode

Beispielausgabe

Eingabetabelle:

Produkt Preis Menge
Widget 29.99 100
O'Brien's 19.99 50

Ausgabe:

-- Exportiert mit HTML Table Exporter PRO

CREATE TABLE produkte (
  produkt TEXT,
  preis REAL,
  menge INTEGER
);

INSERT INTO produkte (produkt, preis, menge) VALUES
  ('Widget', 29.99, 100),
  ('O''Brien''s', 19.99, 50);
Enter fullscreen mode Exit fullscreen mode

Sonderfälle behandeln

Leere Tabellen

if (!rows.length) return "";
if (!headerRow) return "";
Enter fullscreen mode Exit fullscreen mode

Leeren String statt ungültigem SQL zurückgeben.

Komplett-NULL-Spalten

Spalten mit nur leeren/null-Werten bekommen den Typ TEXT. Die Erkennungsfunktion behandelt das:

if (values.length === 0) {
  types[col] = "TEXT";
  continue;
}
Enter fullscreen mode Exit fullscreen mode

Gemischte Zahlenformate

Der Escaper normalisiert Komma-Dezimalzeichen:

const normalized = v.replace(",", ".");
Enter fullscreen mode Exit fullscreen mode

„1,234.56" bleibt unverändert. „1.234,56" (EU-Format) sollte vor Erreichen des SQL-Generators normalisiert werden — das ist Aufgabe der Bereinigungsprofile.

Sehr lange Werte

TEXT-Spalten in SQLite/PostgreSQL verarbeiten beliebige Länge. Kein Abschneiden nötig. Für MySQLs VARCHAR-Limits müsste man die Länge angeben oder explizit TEXT verwenden.

Datenbank-Kompatibilität

Das generierte SQL ist bewusst einfach gehalten:

Feature SQLite PostgreSQL MySQL
CREATE TABLE
INTEGER/REAL/TEXT
Multi-Row INSERT
Einfache-Anführungszeichen-Escaping

Für datenbankspezifische Features (Constraints, Indizes, Schemas) würde man den Generator erweitern. Für den einfachen Datenimport funktioniert das überall.

Den Export verwenden

# SQLite
sqlite3 mydb.db < export.sql

# PostgreSQL
psql -d mydb -f export.sql

# MySQL
mysql mydb < export.sql
Enter fullscreen mode Exit fullscreen mode

Oder direkt in den Datenbank-Client einfügen.

Für mehr zum CSV-Export-Weg (wenn kein SQL benötigt wird), siehe unseren Leitfaden zu den besten Chrome-Erweiterungen zum Exportieren von Tabellen.


SQL-Exporte ohne Code? Erfahren Sie mehr auf gauchogrid.com/de/html-table-exporter oder probieren Sie es kostenlos im Chrome Web Store.

Top comments (0)