DEV Community

Cover image for Convertendo Tabelas Web em Instruções SQL INSERT
circobit
circobit

Posted on

Convertendo Tabelas Web em Instruções SQL INSERT

Você tem uma tabela em uma página web. Precisa dela no seu banco de dados.

A abordagem manual: copiar para o Excel, limpar, exportar CSV, escrever CREATE TABLE à mão, usar LOAD DATA ou COPY, debugar os erros.

A abordagem melhor: gerar SQL completo diretamente — CREATE TABLE com tipos inferidos, instruções INSERT com escape adequado.

Veja como construir um conversor de tabela web para SQL.

O Formato de Saída

Uma exportação SQL completa deve incluir:

-- Exportado pelo HTML Table Exporter PRO

CREATE TABLE produtos (
  produto_id INTEGER,
  nome TEXT,
  preco REAL,
  em_estoque TEXT
);

INSERT INTO produtos (produto_id, nome, preco, em_estoque) VALUES
  (1, 'Widget', 29.99, 'true'),
  (2, 'Gadget', 49.99, 'false'),
  (3, 'Peça do O''Brien', 19.99, 'true');
Enter fullscreen mode Exit fullscreen mode

Requisitos-chave:

  1. Nome de tabela válido (identificador SQL seguro)
  2. Nomes de colunas válidos (sem espaços, sem caracteres especiais)
  3. Tipos de coluna apropriados (INTEGER, REAL, TEXT)
  4. Valores com escape adequado (aspas simples duplicadas)
  5. Tratamento de NULL

Passo 1: Sanitizando Identificadores

Identificadores SQL (nomes de tabelas e colunas) têm regras estritas:

  • Sem espaços ou caracteres especiais
  • Não pode começar com dígito
  • Deve ser minúsculo para portabilidade
function sanitizeSqlIdentifier(name, fallback) {
  let id = (name || "").toString().trim();

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

  id = id
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")  // Remover acentos (café → cafe)
    .toLowerCase()
    .replace(/[^a-z0-9_]+/g, "_")     // Substituir chars inválidos por underscore
    .replace(/^_+|_+$/g, "");          // Remover underscores iniciais/finais

  // Identificadores SQL não podem começar com dígito
  if (/^[0-9]/.test(id)) {
    id = "_" + id;
  }

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

Exemplos:

  • "Nome do Produto" → "nome_do_produto"
  • "Preço (R$)" → "preco_r"
  • "2024 Receita" → "_2024_receita"
  • "Préço" → "preco"

Passo 2: Gerando Nomes de Colunas Únicos

Tabelas podem ter cabeçalhos duplicados. SQL não pode ter nomes de colunas duplicados.

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

Exemplos:

  • ["Nome", "Nome", "Valor"] → ["nome", "nome_1", "valor"]
  • ["", "", "Dados"] → ["col_1", "col_2", "dados"]

Passo 3: Inferindo Tipos de Colunas

SQL tem três tipos principais que nos interessam:

  • INTEGER: Números inteiros
  • REAL: Números decimais
  • TEXT: Todo o resto

A inferência amostra valores da coluna e escolhe o tipo mais específico que serve para todos os valores:

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++) {
    // Amostrar até 50 valores não vazios
    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;
    }

    // Verificar se todos os valores são inteiros
    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

Decisões-chave:

  • Amostrar apenas 50 valores para performance em tabelas grandes
  • INTEGER é mais restrito que REAL (sem decimais)
  • Se QUALQUER valor não corresponder, cai para TEXT
  • Colunas vazias recebem TEXT por padrão

Passo 4: Escapando Valores

Strings SQL requerem escape de aspas simples duplicando-as:

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

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

  // Para tipos numéricos, tentar retornar número sem aspas
  if (type === "INTEGER" || type === "REAL") {
    const normalized = v.replace(",", ".");  // Tratar formato decimal UE
    const num = Number(normalized);

    if (!Number.isNaN(num) && Number.isFinite(num)) {
      return normalized;
    }
    // Cai para tratamento TEXT se não for número válido
  }

  // TEXT ou fallback: escapar aspas simples
  const escaped = v.replace(/'/g, "''");
  return `'${escaped}'`;
}
Enter fullscreen mode Exit fullscreen mode

Exemplos:

  • "Olá"'Olá'
  • "O'Brien"'O''Brien'
  • "Está \"entre aspas\""'Está "entre aspas"'
  • 123 (INTEGER) → 123
  • 45.67 (REAL) → 45.67
  • ""NULL
  • nullNULL

Passo 5: Gerando o SQL Completo

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

  // Gerar nomes de colunas seguros
  const columnNames = generateColumnNames(headerRow);

  // Inferir tipos de colunas
  const types = inferSqlColumnTypes(rows, headerRowIndex);

  // Gerar nome de tabela seguro
  const rawTableName = tableInfo.slug || tableInfo.name || "tabela";
  let tableName = sanitizeSqlIdentifier(rawTableName, "tabela");
  if (!tableName) tableName = "tabela_export";

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

  // CONSTRUIR INSTRUÇÕES 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(", ")})`;
  });

  // COMBINAR
  let sql = `-- Exportado pelo 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

Exemplo de Saída

Tabela de entrada:

Produto Preço Qtd
Widget 29.99 100
O'Brien's 19.99 50

Saída:

-- Exportado pelo HTML Table Exporter PRO

CREATE TABLE produtos (
  produto TEXT,
  preco REAL,
  qtd INTEGER
);

INSERT INTO produtos (produto, preco, qtd) VALUES
  ('Widget', 29.99, 100),
  ('O''Brien''s', 19.99, 50);
Enter fullscreen mode Exit fullscreen mode

Tratando Casos Especiais

Tabelas Vazias

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

Retornar string vazia em vez de SQL inválido.

Colunas Todas NULL

Colunas com apenas valores vazios/null recebem tipo TEXT. A função de inferência trata isso:

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

Formatos Numéricos Mistos

O escaper normaliza decimais com vírgula:

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

"1,234.56" permanece como está. "1.234,56" (formato UE) deve ser normalizado antes de chegar ao gerador SQL — esse é o trabalho dos presets de limpeza.

Valores Muito Longos

Colunas TEXT no SQLite/PostgreSQL lidam com tamanho arbitrário. Nenhuma truncação necessária. Para limites de VARCHAR do MySQL, você precisaria especificar comprimento ou usar TEXT explicitamente.

Compatibilidade entre Bancos de Dados

O SQL gerado é intencionalmente simples:

Recurso SQLite PostgreSQL MySQL
CREATE TABLE
INTEGER/REAL/TEXT
INSERT multi-linhas
Escape aspas simples

Para recursos específicos de banco (constraints, índices, schemas), você estenderia o gerador. Para importação básica de dados, funciona em qualquer lugar.

Usando a Exportação

# SQLite
sqlite3 meubanco.db < exportacao.sql

# PostgreSQL
psql -d meubanco -f exportacao.sql

# MySQL
mysql meubanco < exportacao.sql
Enter fullscreen mode Exit fullscreen mode

Ou cole diretamente no seu cliente de banco de dados.

Para saber mais sobre como exportar tabelas web para diferentes formatos, veja nosso guia sobre a melhor extensão Chrome para copiar tabelas para Excel.


Precisa de exportações SQL sem escrever código? Saiba mais em gauchogrid.com/pt-br/html-table-exporter ou experimente gratuitamente na Chrome Web Store.

Top comments (0)