php

Importar datos de Excel a MySQL con PHPSpreadSheet, PDO y PHP

Importar datos de Excel a base de datos de MySQL usando PHP

Ya vimos cómo exportar datos de MySQL a un libro de Excel. Ahora haremos el proceso inverso: a partir de un archivo de Excel leer algunas hojas y poner sus datos dentro de nuestra base de datos de MySQL.

Digo muy en serio que vamos a hacer el proceso inverso, porque usaré la misma base de datos que en el post anterior así que recomiendo leerlo antes.

Descripción del proyecto

Vamos a leer un archivo de Excel con el lenguaje de programación PHP y una librería llamada PHPSpreadSheet; leeremos las hojas del archivo (que deben seguir un estilo) e insertaremos cada fila.

La estructura de la base de datos es esta:

create database if not exists sistema_ventas;
use sistema_ventas;
create table if not exists productos(
    id bigint unsigned not null auto_increment,
    codigo varchar(255) not null,
    descripcion varchar(255) not null,
    precioCompra decimal(9, 2) not null,
    precioVenta decimal(9, 2) not null,
    existencia decimal(9, 2) not null,
    primary key(id)
);

create table if not exists clientes(
    id bigint unsigned not null auto_increment,
    nombre varchar(255) not null,
    correo varchar(255) not null,
    primary key(id)
);

El documento que vamos a importar es el que se ve en la captura, y que puedes analizar a gusto en mi Google Drive.

Archivo de Excel que vamos a importar hacia MySQL usando PHP

Sí sí, ya sé que se parece muchísimo al que hicimos en el de exportar los datos, pero el punto no es ese. Además, aquí está formateado y creado con el software propietario de Microsoft.

Nota: ya sé que la existencia no se debería formatear como dinero, se me pasó esa parte pero igualmente no afecta al post.

Repositorio en GitHub

Puedes explorar el código o hacerle un fork desde el repositorio oficial. Si hay actualizaciones igualmente las pondré ahí.

Posts que recomiendo que leas

Aquí suponemos que el archivo está en la ruta en donde se ejecuta el script, pero si deseas que el usuario proporcione el Excel (yo no lo recomendaría, pero allá tú, sobre todo por eso de la seguridad) mira cómo subir un archivo con PHP.

También dejo la lista de posts que de alguna manera son la base de esto que expongo, o tienen alguna relación:

Conectar a la base de datos

Usaré el fragmento que se ve en el tutorial de MySQL:

<?php

function obtenerBD(){
    # Mira a esquema.sql y también confgura tus credenciales
    # Recomiendo: https://parzibyte.me/blog/2018/02/12/mysql-php-pdo-crud/
    $nombre_base_de_datos = "sistema_ventas";
    $usuario = "root";
    $contraseña = "";
    try {

        $base_de_datos = new PDO('mysql:host=localhost;dbname=' . $nombre_base_de_datos, $usuario, $contraseña);
        $base_de_datos->query("set names utf8;");
        $base_de_datos->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
        $base_de_datos->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $base_de_datos->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
        return $base_de_datos;
    } catch (Exception $e) {
        # Nota: ¡en la vida real no imprimas errores!
        exit("Error obteniendo BD: " . $e->getMessage());
        return null;
    }
}

Vamos a hacer múltiples inserts, así que la sentencia de PDO será preparada antes del ciclo. También se hará uso de las transacciones para que el rendimiento se vea mejorado.

<?php
# Preparar base de datos para que los inserts sean rápidos
$bd->beginTransaction();

# Preparar sentencia de productos
$sentencia = $bd->prepare("insert into productos
(codigo, descripcion, precioCompra, precioVenta, existencia) values
(?, ?, ? ,?, ? )");

Abrir archivo y acceder a las hojas

El archivo de Excel se abre con el siguiente código:

<?php
# Indicar que usaremos el IOFactory
use PhpOffice\PhpSpreadsheet\IOFactory;

# Obtener conexión o salir en caso de error, mira bd.php
$bd = obtenerBD();

# El archivo a importar
# Recomiendo poner la ruta absoluta si no está junto al script
$rutaArchivo = "Ventas.xlsx";
$documento = IOFactory::load($rutaArchivo);

Para obtener determinada hoja se accede a ella a través de $documento->getSheet($numero). En este caso la primer hoja guarda los productos y la segunda los clientes.

Leer el contenido de cada hoja

Una vez que tenemos la hoja, podemos leer todo su contenido e ir insertando cada valor en la base de datos.

<?php
# Calcular el máximo valor de la fila como entero, es decir, el
# límite de nuestro ciclo
$numeroMayorDeFila = $hojaDeProductos->getHighestRow(); // Numérico
$letraMayorDeColumna = $hojaDeProductos->getHighestColumn(); // Letra
# Convertir la letra al número de columna correspondiente
$numeroMayorDeColumna = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($letraMayorDeColumna);

// Recorrer filas; comenzar en la fila 2 porque omitimos el encabezado
for ($indiceFila = 2; $indiceFila <= $numeroMayorDeFila; $indiceFila++) {
  # Aquí obtener el valor con:
  $hojaDeProductos->getCellByColumnAndRow(1, $indiceFila);
}

Ya dentro del ciclo podemos obtener el valor dependiendo de la fila en la que vamos, obteniendo cada columna por separado. Ahí mismo ejecutamos la sentencia y le pasamos los valores; esto es para prevenir inyecciones SQL.

<?php
# Las columnas están en este orden:
# Código de barras, Descripción, Precio de Compra, Precio de Venta, Existencia
$codigoDeBarras = $hojaDeProductos->getCellByColumnAndRow(1, $indiceFila);
$descripcion = $hojaDeProductos->getCellByColumnAndRow(2, $indiceFila);
$precioCompra = $hojaDeProductos->getCellByColumnAndRow(3, $indiceFila);
$precioVenta = $hojaDeProductos->getCellByColumnAndRow(4, $indiceFila);
$existencia = $hojaDeProductos->getCellByColumnAndRow(5, $indiceFila);
$sentencia->execute([$codigoDeBarras, $descripcion, $precioCompra, $precioVenta, $existencia]);

Cuando el ciclo termine (bueno, cuando los ciclos para importar ambas hojas terminen) hacemos el commit de la base de datos. Este paso es muy importante, puesto que si no, todo lo que insertamos no se guardará.

$bd->commit();

Con eso aseguramos que los insert sean rápidos, sin afectar el rendimiento ni la seguridad. Oh, por cierto, comenzamos en la fila 2 porque la 1 tiene el encabezado.

Poniendo todo junto

Una vez que ya explicamos paso por paso el código para copiar datos de Excel a MySQL es hora de ver cómo queda el script de PHP final:

<?php
/**
 * Ejemplo de cómo usar PDO y PHPSpreadSheet para
 * importar datos de Excel a MySQL de manera
 * fácil, rápida y segura
 *
 * @author parzibyte
 * @see https://parzibyte.me/blog/2019/02/14/leer-archivo-excel-php-phpspreadsheet/
 * @see https://parzibyte.me/blog/2018/02/12/mysql-php-pdo-crud/
 * @see https://parzibyte.me/blog/2019/02/16/php-pdo-parte-2-iterar-cursor-comprobar-si-elemento-existe/
 * @see https://parzibyte.me/blog/2018/11/08/crear-archivo-excel-php-phpspreadsheet/
 * @see https://parzibyte.me/blog/2018/10/11/sintaxis-corta-array-php/
 *
 */
# Cargar clases instaladas por Composer
require_once "vendor/autoload.php";

# Nuestra base de datos
require_once "bd.php";

# Indicar que usaremos el IOFactory
use PhpOffice\PhpSpreadsheet\IOFactory;

# Obtener conexión o salir en caso de error, mira bd.php
$bd = obtenerBD();

# El archivo a importar
# Recomiendo poner la ruta absoluta si no está junto al script
$rutaArchivo = "Ventas.xlsx";
$documento = IOFactory::load($rutaArchivo);

# Se espera que en la primera hoja estén los productos
$hojaDeProductos = $documento->getSheet(0);

# Preparar base de datos para que los inserts sean rápidos
$bd->beginTransaction();

# Preparar sentencia de productos
$sentencia = $bd->prepare("insert into productos
(codigo, descripcion, precioCompra, precioVenta, existencia) values
(?, ?, ? ,?, ? )");

# Calcular el máximo valor de la fila como entero, es decir, el
# límite de nuestro ciclo
$numeroMayorDeFila = $hojaDeProductos->getHighestRow(); // Numérico
$letraMayorDeColumna = $hojaDeProductos->getHighestColumn(); // Letra
# Convertir la letra al número de columna correspondiente
$numeroMayorDeColumna = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($letraMayorDeColumna);

// Recorrer filas; comenzar en la fila 2 porque omitimos el encabezado
for ($indiceFila = 2; $indiceFila <= $numeroMayorDeFila; $indiceFila++) {

    # Las columnas están en este orden:
    # Código de barras, Descripción, Precio de Compra, Precio de Venta, Existencia
    $codigoDeBarras = $hojaDeProductos->getCellByColumnAndRow(1, $indiceFila);
    $descripcion = $hojaDeProductos->getCellByColumnAndRow(2, $indiceFila);
    $precioCompra = $hojaDeProductos->getCellByColumnAndRow(3, $indiceFila);
    $precioVenta = $hojaDeProductos->getCellByColumnAndRow(4, $indiceFila);
    $existencia = $hojaDeProductos->getCellByColumnAndRow(5, $indiceFila);
    $sentencia->execute([$codigoDeBarras, $descripcion, $precioCompra, $precioVenta, $existencia]);
}

# Ahora vamos con los clientes
$sentencia = $bd->prepare("insert into clientes
(nombre, correo) values (?, ?)");

# Se espera que en la segunda hoja estén los clientes
$hojaDeClientes = $documento->getSheet(1);

# Calcular el máximo valor de la fila como entero, es decir, el
# límite de nuestro ciclo
$numeroMayorDeFila = $hojaDeClientes->getHighestRow(); // Numérico
$letraMayorDeColumna = $hojaDeClientes->getHighestColumn(); // Letra
# Convertir la letra al número de columna correspondiente
$numeroMayorDeColumna = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($letraMayorDeColumna);

// Recorrer filas; comenzar en la fila 2 porque omitimos el encabezado
for ($indiceFila = 2; $indiceFila <= $numeroMayorDeFila; $indiceFila++) {

    # Las columnas están en este orden:
    # Nombre, Correo electrónico
    $nombre = $hojaDeClientes->getCellByColumnAndRow(1, $indiceFila);
    $correoElectronico = $hojaDeClientes->getCellByColumnAndRow(2, $indiceFila);
    $sentencia->execute([$nombre, $correoElectronico]);
}

# Hacer commit para guardar cambios de la base de datos
$bd->commit();

El script de la base de datos lo dejé más arriba. Recuerda que la parte de la instalación de PHPSpreadSheet está en el post que dejé arriba.

Demostración: importar desde Excel a MySQL

Adjunto una imagen GIF para demostrar que el proceso realmente funciona.

Importar archivo de Excel a base de datos de MySQL con PHP, PHPSpreadSheet y PDO

¿Por qué no importar cualquier archivo?

Si te das cuenta, estamos dependiendo mucho de la estructura del archivo Excel. Es decir, no leemos todas las hojas ni importamos a ciegas.

Esto es porque no se debería confiar en datos proporcionados por usuario, si dejamos que importe cualquier número de hojas que se convierten a tablas podríamos tener una seria vulnerabilidad.

Conclusiones

Podríamos hacer miles de cosas más con PHPSpreadSheet, espero tener el tiempo para escribir más sobre el tema.

Como referencia dejo esta página que tiene que ver con la documentación oficial del paquete.

Te invito a leer más sobre:

  1. PHP
  2. MySQL
  3. Bases de datos

No olvides seguirme para estar al tanto de mis actualizaciones 😉

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

2 días hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

1 semana hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

1 semana hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

1 semana hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

1 semana hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

1 semana hace

Esta web usa cookies.