CREATE TABLE en Perl

Crear tablas desde un script Perl te permite automatizar la configuración de bases de datos, construir instaladores para tus aplicaciones y mantener un control de versiones del esquema de la base de datos. En este artículo aprenderás a ejecutar sentencias DDL (Data Definition Language) desde Perl usando el método do() de DBI, verificar si las tablas ya existen y manejar los errores que pueden surgir.

Requisitos previos

Necesitas una conexión DBI configurada como se describió en el artículo anterior, con un usuario que tenga permisos para crear tablas. Asegúrate de tener la base de datos tienda creada.

Código completo

Este ejemplo crea una tabla de productos con varias columnas y restricciones:

#!/usr/bin/perl
use strict;
use warnings;
use DBI;
 
my $dbh = DBI->connect(
    "DBI:mysql:database=tienda;host=localhost",
    "root", "tu_contraseña",
    { RaiseError => 1, AutoCommit => 1, mysql_enable_utf8mb4 => 1 }
);
 
$dbh->do("CREATE TABLE IF NOT EXISTS productos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    categoria VARCHAR(50) NOT NULL,
    precio DECIMAL(10, 2) NOT NULL,
    stock INT DEFAULT 0,
    activo TINYINT(1) DEFAULT 1,
    fecha_creacion DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
 
print "Tabla 'productos' creada exitosamente\n";
 
$dbh->disconnect();

Salida esperada:

Tabla 'productos' creada exitosamente

Explicación paso a paso

El método do() ejecuta una sentencia SQL que no devuelve resultados, como CREATE TABLE, ALTER TABLE o DROP TABLE. Este método es un atajo para la secuencia prepare() + execute() cuando no necesitas manejar resultados ni parámetros.

La cláusula IF NOT EXISTS es fundamental cuando automatizas la creación de tablas, ya que evita errores si el script se ejecuta más de una vez. Sin esta cláusula, intentar crear una tabla que ya existe provocará un error.

Crear múltiples tablas relacionadas

En una base de datos real, las tablas suelen estar relacionadas entre sí. Es importante crearlas en el orden correcto para que las claves foráneas puedan establecerse:

#!/usr/bin/perl
use strict;
use warnings;
use DBI;
 
my $dbh = DBI->connect(
    "DBI:mysql:database=tienda;host=localhost",
    "root", "tu_contraseña",
    { RaiseError => 1, AutoCommit => 1, mysql_enable_utf8mb4 => 1 }
);
 
# Tabla de categorías (debe crearse primero por la relación)
$dbh->do("CREATE TABLE IF NOT EXISTS categorias (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(50) NOT NULL UNIQUE,
    descripcion TEXT,
    activa TINYINT(1) DEFAULT 1,
    fecha_creacion DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
print "Tabla 'categorias' creada\n";
 
# Tabla de productos con clave foránea
$dbh->do("CREATE TABLE IF NOT EXISTS productos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    categoria_id INT,
    precio DECIMAL(10, 2) NOT NULL,
    stock INT DEFAULT 0,
    activo TINYINT(1) DEFAULT 1,
    fecha_creacion DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (categoria_id) REFERENCES categorias(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
print "Tabla 'productos' creada\n";
 
# Tabla de clientes
$dbh->do("CREATE TABLE IF NOT EXISTS clientes (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    telefono VARCHAR(20),
    fecha_registro DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
print "Tabla 'clientes' creada\n";
 
# Tabla de pedidos
$dbh->do("CREATE TABLE IF NOT EXISTS pedidos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    cliente_id INT NOT NULL,
    fecha DATETIME DEFAULT CURRENT_TIMESTAMP,
    estado ENUM('pendiente', 'procesando', 'enviado', 'entregado', 'cancelado') DEFAULT 'pendiente',
    total DECIMAL(12, 2) DEFAULT 0,
    FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
print "Tabla 'pedidos' creada\n";
 
print "\nTodas las tablas creadas exitosamente\n";
$dbh->disconnect();

Salida esperada:

Tabla 'categorias' creada
Tabla 'productos' creada
Tabla 'clientes' creada
Tabla 'pedidos' creada

Todas las tablas creadas exitosamente

Verificar si una tabla existe

Puedes consultar el esquema de información de MySQL para verificar la existencia de una tabla antes de intentar crearla o modificarla:

sub tabla_existe {
    my ($dbh, $tabla) = @_;
    my $sth = $dbh->prepare(
        "SELECT COUNT(*) FROM information_schema.TABLES
         WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?"
    );
    $sth->execute($tabla);
    my ($existe) = $sth->fetchrow_array();
    $sth->finish();
    return $existe;
}
 
if (tabla_existe($dbh, 'productos')) {
    print "La tabla 'productos' ya existe\n";
} else {
    print "La tabla 'productos' no existe, creándola...\n";
    # Crear la tabla aquí
}

Obtener la estructura de una tabla

sub mostrar_estructura {
    my ($dbh, $tabla) = @_;
    my $sth = $dbh->prepare("DESCRIBE $tabla");
    $sth->execute();
 
    printf("%-20s %-30s %-5s %-5s %-10s %s\n",
           "Campo", "Tipo", "Null", "Key", "Default", "Extra");
    print "-" x 90, "\n";
 
    while (my $col = $sth->fetchrow_hashref()) {
        printf("%-20s %-30s %-5s %-5s %-10s %s\n",
               $col->{Field}, $col->{Type},
               $col->{Null}, $col->{Key} || '',
               $col->{Default} || 'NULL', $col->{Extra} || '');
    }
    $sth->finish();
}
 
mostrar_estructura($dbh, 'productos');

Caso práctico

Veamos un script de migración que crea o actualiza el esquema de la base de datos:

#!/usr/bin/perl
use strict;
use warnings;
use DBI;
 
my $dbh = DBI->connect(
    "DBI:mysql:database=tienda;host=localhost",
    "root", "tu_contraseña",
    { RaiseError => 1, AutoCommit => 0, mysql_enable_utf8mb4 => 1 }
);
 
my @migraciones = (
    {
        version => 1,
        descripcion => "Crear tabla de categorías",
        sql => "CREATE TABLE IF NOT EXISTS categorias (
            id INT AUTO_INCREMENT PRIMARY KEY,
            nombre VARCHAR(50) NOT NULL UNIQUE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
    },
    {
        version => 2,
        descripcion => "Crear tabla de productos",
        sql => "CREATE TABLE IF NOT EXISTS productos (
            id INT AUTO_INCREMENT PRIMARY KEY,
            nombre VARCHAR(100) NOT NULL,
            categoria_id INT,
            precio DECIMAL(10, 2) NOT NULL,
            stock INT DEFAULT 0,
            FOREIGN KEY (categoria_id) REFERENCES categorias(id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
    },
    {
        version => 3,
        descripcion => "Agregar columna activo a productos",
        sql => "ALTER TABLE productos ADD COLUMN IF NOT EXISTS activo TINYINT(1) DEFAULT 1"
    },
);
 
eval {
    # Crear tabla de control de migraciones
    $dbh->do("CREATE TABLE IF NOT EXISTS migraciones (
        version INT PRIMARY KEY,
        descripcion VARCHAR(200),
        ejecutada_en DATETIME DEFAULT CURRENT_TIMESTAMP
    ) ENGINE=InnoDB");
 
    for my $mig (@migraciones) {
        # Verificar si ya se ejecutó
        my $sth = $dbh->prepare("SELECT COUNT(*) FROM migraciones WHERE version = ?");
        $sth->execute($mig->{version});
        my ($ya_ejecutada) = $sth->fetchrow_array();
        $sth->finish();
 
        if ($ya_ejecutada) {
            print "Migración v$mig->{version}: ya ejecutada, saltando\n";
            next;
        }
 
        print "Migración v$mig->{version}: $mig->{descripcion}... ";
        $dbh->do($mig->{sql});
 
        $dbh->do("INSERT INTO migraciones (version, descripcion) VALUES (?, ?)",
                 undef, $mig->{version}, $mig->{descripcion});
        print "OK\n";
    }
 
    $dbh->commit();
    print "\nMigraciones completadas exitosamente\n";
};
 
if ($@) {
    $dbh->rollback();
    print "Error en migración: $@\n";
}
 
$dbh->disconnect();

Manejo de errores

Los errores al crear tablas pueden deberse a sintaxis incorrecta, permisos insuficientes o conflictos con tablas existentes:

sub crear_tabla_segura {
    my ($dbh, $nombre, $sql) = @_;
 
    eval { $dbh->do($sql); };
 
    if ($@) {
        my $error = $@;
        if ($error =~ /already exists/) {
            print "La tabla '$nombre' ya existe\n";
        } elsif ($error =~ /Access denied/) {
            print "Sin permisos para crear la tabla '$nombre'\n";
        } elsif ($error =~ /errno: 150/) {
            print "Error de clave foránea al crear '$nombre'. Verifica que las tablas referenciadas existen\n";
        } elsif ($error =~ /You have an error in your SQL syntax/) {
            print "Error de sintaxis SQL en la definición de '$nombre'\n";
        } else {
            print "Error al crear '$nombre': $error\n";
        }
        return 0;
    }
 
    print "Tabla '$nombre' creada exitosamente\n";
    return 1;
}

Ahora que sabes cómo crear tablas desde Perl, en el siguiente artículo aprenderás a insertar datos en MySQL.

Escrito por Eduardo Lázaro