DEV Community

Cover image for Convertir des Tableaux Web en Requêtes SQL INSERT
circobit
circobit

Posted on

Convertir des Tableaux Web en Requêtes SQL INSERT

Vous avez un tableau sur une page web. Vous en avez besoin dans votre base de données.

L'approche manuelle : copier dans Excel, nettoyer, exporter en CSV, écrire le CREATE TABLE à la main, utiliser LOAD DATA ou COPY, déboguer les erreurs.

La meilleure approche : générer du SQL complet directement — CREATE TABLE avec types inférés, requêtes INSERT avec échappement correct.

Voici comment construire un convertisseur de tableaux web vers SQL.

Le Format de Sortie

Un export SQL complet devrait inclure :

-- Exporté depuis HTML Table Exporter PRO

CREATE TABLE products (
  product_id INTEGER,
  name TEXT,
  price REAL,
  in_stock TEXT
);

INSERT INTO products (product_id, name, price, in_stock) VALUES
  (1, 'Widget', 29.99, 'true'),
  (2, 'Gadget', 49.99, 'false'),
  (3, 'L''article d''O''Brien', 19.99, 'true');
Enter fullscreen mode Exit fullscreen mode

Exigences clés :

  1. Nom de table valide (identifiant SQL sûr)
  2. Noms de colonnes valides (pas d'espaces, pas de caractères spéciaux)
  3. Types de colonnes appropriés (INTEGER, REAL, TEXT)
  4. Valeurs correctement échappées (apostrophes doublées)
  5. Gestion des NULL

Étape 1 : Assainir les Identifiants

Les identifiants SQL (noms de tables et de colonnes) ont des règles strictes :

  • Pas d'espaces ni de caractères spéciaux
  • Ne peuvent pas commencer par un chiffre
  • Devraient être en minuscules pour la portabilité
function sanitizeSqlIdentifier(name, fallback) {
  let id = (name || "").toString().trim();

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

  id = id
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")  // Supprimer les accents (café → cafe)
    .toLowerCase()
    .replace(/[^a-z0-9_]+/g, "_")     // Remplacer les car. invalides par underscore
    .replace(/^_+|_+$/g, "");          // Supprimer les underscores en début/fin

  // Les identifiants SQL ne peuvent pas commencer par un chiffre
  if (/^[0-9]/.test(id)) {
    id = "_" + id;
  }

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

Exemples :

  • « Nom du Produit » → « nom_du_produit »
  • « Prix (€) » → « prix »
  • « 2024 Revenus » → « _2024_revenus »
  • « Préço » → « preco »

Étape 2 : Générer des Noms de Colonnes Uniques

Les tableaux peuvent avoir des en-têtes dupliqués. SQL ne peut pas avoir de noms de colonnes dupliqués.

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

Exemples :

  • ["Nom", "Nom", "Valeur"] → ["nom", "nom_1", "valeur"]
  • ["", "", "Données"] → ["col_1", "col_2", "donnees"]

Étape 3 : Inférer les Types de Colonnes

SQL a trois types principaux qui nous intéressent :

  • INTEGER : Nombres entiers
  • REAL : Nombres décimaux
  • TEXT : Tout le reste

L'inférence échantillonne les valeurs de la colonne et choisit le type le plus spécifique qui correspond à toutes les valeurs :

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++) {
    // Échantillonner jusqu'à 50 valeurs non vides
    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;
    }

    // Vérifier si toutes les valeurs sont des entiers
    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

Décisions clés :

  • Échantillonner seulement 50 valeurs pour la performance sur les grands tableaux
  • INTEGER est plus strict que REAL (pas de décimales autorisées)
  • Si UNE SEULE valeur ne correspond pas, retomber sur TEXT
  • Les colonnes vides sont TEXT par défaut

Étape 4 : Échapper les Valeurs

Les chaînes SQL nécessitent d'échapper les apostrophes en les doublant :

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

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

  // Pour les types numériques, essayer de retourner un nombre sans guillemets
  if (type === "INTEGER" || type === "REAL") {
    const normalized = v.replace(",", ".");  // Gérer le format décimal EU
    const num = Number(normalized);

    if (!Number.isNaN(num) && Number.isFinite(num)) {
      return normalized;
    }
    // Retomber sur le traitement TEXT si ce n'est pas un nombre valide
  }

  // TEXT ou fallback : échapper les apostrophes
  const escaped = v.replace(/'/g, "''");
  return `'${escaped}'`;
}
Enter fullscreen mode Exit fullscreen mode

Exemples :

  • "Bonjour"'Bonjour'
  • "L'article"'L''article'
  • "C'est \"cité\""'C''est "cité"'
  • 123 (INTEGER) → 123
  • 45.67 (REAL) → 45.67
  • ""NULL
  • nullNULL

Étape 5 : Générer le SQL Complet

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 "";

  // Générer des noms de colonnes sûrs
  const columnNames = generateColumnNames(headerRow);

  // Inférer les types de colonnes
  const types = inferSqlColumnTypes(rows, headerRowIndex);

  // Générer un nom de table sûr
  const rawTableName = tableInfo.slug || tableInfo.name || "table";
  let tableName = sanitizeSqlIdentifier(rawTableName, "table");
  if (!tableName) tableName = "table_export";

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

  // CONSTRUIRE LES INSERT
  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(", ")})`;
  });

  // COMBINER
  let sql = `-- Exporté depuis 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

Exemple de Sortie

Tableau d'entrée :

Produit Prix Qté
Widget 29.99 100
L'article d'O'Brien 19.99 50

Sortie :

-- Exporté depuis HTML Table Exporter PRO

CREATE TABLE products (
  produit TEXT,
  prix REAL,
  qte INTEGER
);

INSERT INTO products (produit, prix, qte) VALUES
  ('Widget', 29.99, 100),
  ('L''article d''O''Brien', 19.99, 50);
Enter fullscreen mode Exit fullscreen mode

Gérer les Cas Limites

Tableaux Vides

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

Retourner une chaîne vide plutôt que du SQL invalide.

Colonnes Entièrement NULL

Les colonnes avec uniquement des valeurs vides/nulles obtiennent le type TEXT. La fonction d'inférence gère cela :

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

Formats de Nombres Mixtes

L'échappeur normalise les décimales avec virgule :

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

« 1,234.56 » reste tel quel. « 1.234,56 » (format EU) devrait être normalisé avant d'atteindre le générateur SQL — c'est le rôle des préréglages de nettoyage.

Valeurs Très Longues

Les colonnes TEXT dans SQLite/PostgreSQL gèrent des longueurs arbitraires. Pas de troncature nécessaire. Pour les limites VARCHAR de MySQL, il faudrait spécifier la longueur ou utiliser TEXT explicitement.

Compatibilité entre Bases de Données

Le SQL généré est intentionnellement simple :

Fonctionnalité SQLite PostgreSQL MySQL
CREATE TABLE
INTEGER/REAL/TEXT
INSERT multi-lignes
Échappement apostrophe

Pour des fonctionnalités spécifiques aux bases de données (contraintes, index, schémas), il faudrait étendre le générateur. Pour un import basique de données, cela fonctionne partout.

Utiliser l'Export

# SQLite
sqlite3 mydb.db < export.sql

# PostgreSQL
psql -d mydb -f export.sql

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

Ou collez directement dans votre client de base de données.

Pour en savoir plus sur le chemin d'export CSV (quand vous n'avez pas besoin de SQL), consultez notre guide sur l'export de toutes les lignes d'un tableau web paginé.


Besoin d'exports SQL sans écrire de code ? En savoir plus sur gauchogrid.com/fr/html-table-exporter ou essayez-le gratuitement sur le Chrome Web Store.

Top comments (0)