Von der HTML-Tabelle zu SQL-INSERT-Statements mit einem Klick
Sie haben Daten auf einer Website gefunden. Sie brauchen sie in Ihrer Datenbank. Der übliche Workflow: Export nach CSV, Import ins SQL-Tool, Typfehler beheben, Encoding-Probleme lösen. Was wäre, wenn man direkt von der
Sie haben Daten auf einer Website gefunden. Sie brauchen sie in Ihrer Datenbank. Der übliche Workflow: Export nach CSV, Import ins SQL-Tool, Typfehler beheben, Encoding-Probleme lösen.
Was wäre, wenn man direkt von der HTML-Tabelle zu validem SQL kommen könnte?
Diese Anleitung zeigt, wie man CREATE TABLE- und INSERT INTO-Statements aus jeder Webtabelle generiert — mit Typinferenz, Identifier-Bereinigung und korrektem Escaping. Diesen Ansatz verwende ich im HTML Table Exporter.
Das Endergebnis
Aus einer Tabelle wie dieser:
| Produktname | Preis | Auf Lager |
|-----------------|--------|-----------|
| Widget Pro | 29,99 | Ja |
| Gadget Basic | 14,50 | Nein |
Wird generiert:
-- Exportiert mit HTML Table Exporter PRO
CREATE TABLE produkte (
produktname TEXT,
preis REAL,
auf_lager TEXT
);
INSERT INTO produkte (produktname, preis, auf_lager) VALUES
('Widget Pro', 29.99, 'Ja'),
('Gadget Basic', 14.50, 'Nein');
Bauen wir das Schritt für Schritt auf.
Schritt 1: Die Tabellenmatrix extrahieren
Zunächst die Rohdaten aus der HTML-Tabelle extrahieren:
function extractTableMatrix(table) {
const rows = Array.from(table.rows);
const grid = [];
rows.forEach((rowEl, rowIndex) => {
if (!grid[rowIndex]) grid[rowIndex] = [];
let colIndex = 0;
Array.from(rowEl.cells).forEach(cell => {
while (grid[rowIndex][colIndex] !== undefined) colIndex++;
const text = cell.textContent.trim();
const rowSpan = parseInt(cell.rowSpan) || 1;
const colSpan = parseInt(cell.colSpan) || 1;
for (let r = 0; r < rowSpan; r++) {
if (!grid[rowIndex + r]) grid[rowIndex + r] = [];
for (let c = 0; c < colSpan; c++) {
grid[rowIndex + r][colIndex + c] = text;
}
}
colIndex += colSpan;
});
});
return grid;
}
Ausgabe: Ein 2D-Array, wobei rows[0] die Header und rows[1+] die Daten sind.
Schritt 2: Spaltennamen bereinigen
SQL-Bezeichner haben strenge Regeln. Header wie „Produktname" oder „Preis (€)" müssen bereinigt werden:
function sanitizeSqlIdentifier(header, fallbackIndex) {
let id = (header || "").toString().trim();
if (!id) {
return `col_${fallbackIndex + 1}`;
}
// Unicode normalisieren (Akzente entfernen)
id = id.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
// In Kleinbuchstaben mit Unterstrich konvertieren
id = id
.toLowerCase()
.replace(/[^a-z0-9]+/g, "_")
.replace(/^_+|_+$/g, "");
// Sicherstellen, dass es nicht mit einer Zahl beginnt
if (/^\d/.test(id)) {
id = `col_${id}`;
}
// Fallback wenn nach Bereinigung leer
if (!id) {
return `col_${fallbackIndex + 1}`;
}
return id;
}
// Beispiele:
// "Produktname" → "produktname"
// "Preis (€)" → "preis"
// "2024 Umsatz" → "col_2024_umsatz"
// "" → "col_1"
Schritt 3: Doppelte Spaltennamen behandeln
Tabellen haben manchmal doppelte Header. SQL erfordert eindeutige Spaltennamen:
function makeUniqueColumnNames(headers) {
const used = new Set();
return headers.map((header, idx) => {
let name = sanitizeSqlIdentifier(header, idx);
let candidate = name;
let counter = 1;
while (used.has(candidate)) {
candidate = `${name}_${counter}`;
counter++;
}
used.add(candidate);
return candidate;
});
}
// ["Name", "Name", "Wert"] → ["name", "name_1", "wert"]
Schritt 4: SQL-Typen inferieren
Daten analysieren, um passende SQL-Typen zu bestimmen:
function inferSqlColumnTypes(rows, headerRowIndex = 0) {
const headerRow = rows[headerRowIndex] || [];
const dataRows = rows.slice(headerRowIndex + 1);
const types = new Array(headerRow.length).fill("TEXT");
for (let col = 0; col < headerRow.length; col++) {
// Bis zu 50 Werte pro Spalte samplen
const values = [];
for (let r = 0; r < Math.min(dataRows.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 einem Typ entsprechen
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;
}
Typpriorität: INTEGER > REAL > TEXT
Wenn 100% der nicht-leeren Werte Integer sind, wird INTEGER verwendet. Wenn alle numerisch sind (einschließlich Dezimalzahlen), wird REAL verwendet. Ansonsten TEXT.
Schritt 5: Werte korrekt escapen
SQL-Injection ist nicht nur ein Sicherheitsproblem — es beschädigt die Daten. Korrektes Escaping:
function sqlEscapeValue(raw, type) {
// NULL-Behandlung
if (raw == null) return "NULL";
const v = String(raw).trim();
if (v === "") return "NULL";
// Numerische Typen
if (type === "INTEGER" || type === "REAL") {
// Dezimaltrennzeichen normalisieren
const normalized = v.replace(",", ".");
const num = Number(normalized);
if (!Number.isNaN(num) && Number.isFinite(num)) {
return normalized;
}
// Bei ungültiger Zahl zu TEXT zurückfallen
}
// TEXT: Einfache Anführungszeichen durch Verdopplung escapen
const escaped = v.replace(/'/g, "''");
return `'${escaped}'`;
}
// Beispiele:
// sqlEscapeValue("Hallo", "TEXT") → "'Hallo'"
// sqlEscapeValue("Das ist's", "TEXT") → "'Das ist''s'"
// sqlEscapeValue("29.99", "REAL") → "29.99"
// sqlEscapeValue("", "TEXT") → "NULL"
// sqlEscapeValue(null, "TEXT") → "NULL"
Schritt 6: Das SQL generieren
Alles zusammenfügen:
function tableToSql(tableInfo) {
const rows = tableInfo.rows || [];
if (rows.length < 2) return "";
const headerRowIndex = tableInfo.headerRowIndex || 0;
const headerRow = rows[headerRowIndex];
const dataRows = rows.slice(headerRowIndex + 1);
if (!headerRow || headerRow.length === 0) return "";
// Spaltennamen generieren
const columnNames = makeUniqueColumnNames(headerRow);
// Typen inferieren
const types = inferSqlColumnTypes(rows, headerRowIndex);
// Tabellennamen generieren
const rawTableName = tableInfo.name || tableInfo.slug || "table";
const tableName = sanitizeSqlIdentifier(rawTableName, 0) || "table_export";
// CREATE TABLE Statement
const createLines = columnNames.map((col, i) =>
` ${col} ${types[i] || "TEXT"}`
);
const createStmt = `CREATE TABLE ${tableName} (\n${createLines.join(",\n")}\n);`;
// INSERT Statements
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\n\n${createStmt}\n\n`;
if (valueLines.length > 0) {
sql += `${insertHeader}\n${valueLines.join(",\n")};\n`;
}
return sql;
}
Vollständiges Beispiel
Eingabetabelle:
const tableInfo = {
name: "Q1 Verkaufsdaten",
rows: [
["Produkt", "Verkaufte Einheiten", "Umsatz (€)", "Profitabel"],
["Widget A", "1.234", "45.678,90", "Ja"],
["Widget B", "567", "12.345,67", "Nein"],
["Gadget X", "890", "23.456,78", "Ja"]
]
};
Ausgabe:
-- Exportiert mit HTML Table Exporter
CREATE TABLE q1_verkaufsdaten (
produkt TEXT,
verkaufte_einheiten INTEGER,
umsatz REAL,
profitabel TEXT
);
INSERT INTO q1_verkaufsdaten (produkt, verkaufte_einheiten, umsatz, profitabel) VALUES
('Widget A', 1234, 45678.90, 'Ja'),
('Widget B', 567, 12345.67, 'Nein'),
('Gadget X', 890, 23456.78, 'Ja');
Zu beachten:
- Spaltennamen bereinigt (
Umsatz (€)→umsatz) - Zahlen erkannt und ohne Anführungszeichen
- Tausendertrennzeichen in Zahlen behandelt
- Tabellenname aus Metadaten
Edge Cases behandeln
NULL-Werte
Viele Webtabellen stellen fehlende Daten unterschiedlich dar:
const NULL_PATTERNS = ["N/A", "n/a", "-", "--", "null", "none", "."];
function sqlEscapeValue(raw, type, nullPatterns = NULL_PATTERNS) {
if (raw == null) return "NULL";
const v = String(raw).trim();
if (v === "" || nullPatterns.includes(v.toLowerCase())) {
return "NULL";
}
// ... Rest der Funktion
}
Große Datensätze
Für Tabellen mit Tausenden von Zeilen die INSERTs batchweise verarbeiten:
function tableToSqlBatched(tableInfo, batchSize = 1000) {
// ... gleiches Setup ...
const batches = [];
for (let i = 0; i < valueLines.length; i += batchSize) {
const batch = valueLines.slice(i, i + batchSize);
batches.push(`${insertHeader}\n${batch.join(",\n")};`);
}
return `${createStmt}\n\n${batches.join("\n\n")}`;
}
Datenbankspezifische Syntax
SQLite, PostgreSQL und MySQL haben leichte Syntaxunterschiede:
function tableToSql(tableInfo, dialect = "sqlite") {
// ... gleiche Spalten-/Typlogik ...
// Dialektspezifisches Typ-Mapping
const typeMap = {
sqlite: { INTEGER: "INTEGER", REAL: "REAL", TEXT: "TEXT" },
postgresql: { INTEGER: "INTEGER", REAL: "NUMERIC", TEXT: "TEXT" },
mysql: { INTEGER: "INT", REAL: "DECIMAL(10,2)", TEXT: "VARCHAR(255)" }
};
const dialectTypes = typeMap[dialect] || typeMap.sqlite;
const createLines = columnNames.map((col, i) =>
` ${col} ${dialectTypes[types[i]] || dialectTypes.TEXT}`
);
// ... Rest der Funktion ...
}
Browser-Bookmarklet
Schnelle Lösung für gelegentlichen Gebrauch:
javascript:(function(){
const table = document.querySelector("table");
if (!table) { alert("Keine Tabelle gefunden"); return; }
const rows = Array.from(table.rows).map(r =>
Array.from(r.cells).map(c => c.textContent.trim())
);
const headers = rows[0].map((h, i) =>
(h || `col_${i+1}`).toLowerCase().replace(/[^a-z0-9]+/g, "_")
);
const values = rows.slice(1).map(row =>
"(" + row.map(v => `'${v.replace(/'/g, "''")}'`).join(", ") + ")"
).join(",\n");
const sql = `INSERT INTO table_data (${headers.join(", ")}) VALUES\n${values};`;
navigator.clipboard.writeText(sql);
alert("SQL in die Zwischenablage kopiert!");
})();
Wann man das verwenden sollte
Gut geeignet für:
- Schnelle Datenimporte in SQLite/PostgreSQL
- Test-Datenbanken befüllen
- Einmalige Datenmigrationen
- SQL anhand von Beispielen lernen
Weniger geeignet für:
- Großes ETL (dafür gibt es passende Tools)
- Komplexe Schemas (Foreign Keys, Constraints)
- Binärdaten
Für produktive Datenpipelines sind dedizierte ETL-Tools mit Validierung besser. Für „Ich brauche diese Tabelle jetzt in meiner Datenbank" ist direkte SQL-Generierung schnell.
Ohne Code ausprobieren
Wenn Sie das nicht selbst bauen möchten, generiert HTML Table Exporter PRO SQL mit einem Klick. Die kostenlose Version exportiert nach CSV/JSON/Excel; PRO fügt SQL, NDJSON und Datenbereinigung hinzu.
Einen Vergleich verschiedener Export-Ansätze finden Sie in unserem Leitfaden zu den besten Chrome-Erweiterungen zum Exportieren von Tabellen.
Erfahren Sie mehr auf gauchogrid.com/de/html-table-exporter oder probieren Sie es im Chrome Web Store aus.
In welche Datenbank importieren Sie Webdaten? Mich interessieren die Anwendungsfälle.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.