Conforme tus aplicaciones crecen, empiezas a encontrar operaciones que se repiten constantemente y que implican varias sentencias SQL seguidas: registrar una venta y descontar el stock al mismo tiempo, validar datos antes de insertarlos, calcular estadísticas complejas que se consultan desde distintos puntos del sistema.
Los procedimientos almacenados (stored procedures) son la herramienta de SQL que te permite guardar ese conjunto de instrucciones en la base de datos con un nombre, y ejecutarlas todas de una sola llamada. Son como funciones, pero que viven dentro del motor de base de datos.
En este artículo aprenderás qué son, cómo crearlos paso a paso en MySQL, cómo usar parámetros de entrada y salida, estructuras de control, manejo de errores y cuándo tiene sentido usarlos.
Qué es un procedimiento almacenado
Un procedimiento almacenado es un bloque de código SQL con un nombre que se guarda en la base de datos y se puede ejecutar cuando sea necesario. A diferencia de una consulta normal que se envía desde la aplicación cada vez que se necesita, el procedimiento ya está compilado y optimizado dentro del servidor de base de datos.
Sus características principales son:
- Se guarda en el servidor de base de datos, no en el código de la aplicación.
- Se puede llamar desde cualquier cliente: aplicación Java, Python, PHP, o directamente desde un cliente SQL.
- Acepta parámetros de entrada, de salida o ambos.
- Puede contener lógica condicional, bucles, variables y manejo de errores.
- Reduce el tráfico de red: en lugar de enviar múltiples sentencias SQL, se envía una sola llamada.
- Centraliza la lógica de negocio en la base de datos.
Diferencia entre procedimiento y función
En MySQL existen los procedimientos almacenados y las funciones almacenadas. Aunque son parecidos, tienen diferencias importantes:
| Característica | Procedimiento | Función |
|---|---|---|
| Devuelve valores | A través de parámetros OUT | Con RETURN (un solo valor) |
| Se puede usar en SELECT | No | Sí |
| Cómo se llama | Con CALL | Como parte de una expresión |
| Puede modificar datos | Sí | Generalmente no (depende del tipo) |
| Manejo de transacciones | Sí | No |
En este artículo nos enfocamos en los procedimientos. Las funciones almacenadas siguen una lógica similar pero con las restricciones de la tabla anterior.
El delimitador: por qué es necesario
Antes de crear tu primer procedimiento, necesitas entender el concepto de delimitador. Normalmente MySQL usa el punto y coma ; para separar sentencias. Pero dentro de un procedimiento hay muchas sentencias con punto y coma, y MySQL las ejecutaría antes de terminar de leer el procedimiento completo.
La solución es cambiar temporalmente el delimitador a otro símbolo, definir el procedimiento completo, y luego restaurar el delimitador original.
-- Cambiar el delimitador a //
DELIMITER //
CREATE PROCEDURE mi_procedimiento()
BEGIN
SELECT 'Hola desde el procedimiento';
SELECT CURDATE() AS fecha_actual;
END //
-- Restaurar el delimitador original
DELIMITER ;El símbolo // es el más común, aunque también verás $$ en muchos ejemplos. Lo importante es que sea diferente al punto y coma.
Tu primer procedimiento almacenado
Usaremos el mismo esquema de tienda online del artículo anterior para todos los ejemplos:
-- Tablas: clientes, productos, pedidos, detalle_pedidosEmpecemos con un procedimiento simple que lista todos los productos:
DELIMITER //
CREATE PROCEDURE sp_listar_productos()
BEGIN
SELECT
id,
nombre,
precio,
stock,
categoria
FROM productos
ORDER BY nombre;
END //
DELIMITER ;Para ejecutarlo se usa la sentencia CALL:
CALL sp_listar_productos();Nota la convención de nombrar los procedimientos con el prefijo sp_ (stored procedure). Es una práctica común que hace fácil identificarlos en el catálogo de la base de datos.
Parámetros de entrada: IN
Los parámetros IN permiten pasar valores al procedimiento desde fuera. Son los más comunes y funcionan como los argumentos de una función.
DELIMITER //
CREATE PROCEDURE sp_buscar_por_categoria(
IN p_categoria VARCHAR(50)
)
BEGIN
SELECT
id,
nombre,
precio,
stock
FROM productos
WHERE categoria = p_categoria
ORDER BY precio ASC;
END //
DELIMITER ;-- Llamar con distintos valores
CALL sp_buscar_por_categoria('Electrónica');
CALL sp_buscar_por_categoria('Periféricos');
CALL sp_buscar_por_categoria('Audio');Es buena práctica usar un prefijo para los parámetros (como p_) para distinguirlos de los nombres de columnas y evitar ambigüedades.
Múltiples parámetros de entrada
DELIMITER //
CREATE PROCEDURE sp_productos_por_rango_precio(
IN p_precio_min DECIMAL(10, 2),
IN p_precio_max DECIMAL(10, 2),
IN p_categoria VARCHAR(50)
)
BEGIN
SELECT
id,
nombre,
precio,
stock,
categoria
FROM productos
WHERE precio BETWEEN p_precio_min AND p_precio_max
AND (p_categoria IS NULL OR categoria = p_categoria)
ORDER BY precio;
END //
DELIMITER ;-- Buscar productos entre 50 y 200 dólares en todas las categorías
CALL sp_productos_por_rango_precio(50.00, 200.00, NULL);
-- Buscar solo electrónica entre 300 y 1500 dólares
CALL sp_productos_por_rango_precio(300.00, 1500.00, 'Electrónica');Parámetros de salida: OUT
Los parámetros OUT permiten que el procedimiento devuelva valores a quien lo llamó. Se definen con la palabra clave OUT y dentro del procedimiento se les asigna un valor.
DELIMITER //
CREATE PROCEDURE sp_contar_productos(
IN p_categoria VARCHAR(50),
OUT p_total INT,
OUT p_valor_inventario DECIMAL(10, 2)
)
BEGIN
SELECT
COUNT(*),
SUM(precio * stock)
INTO p_total, p_valor_inventario
FROM productos
WHERE p_categoria IS NULL OR categoria = p_categoria;
END //
DELIMITER ;-- Llamar y capturar los valores de salida en variables de sesión
CALL sp_contar_productos('Electrónica', @total, @valor);
-- Leer los valores retornados
SELECT @total AS productos_electronicos, @valor AS valor_inventario;
-- Para todas las categorías
CALL sp_contar_productos(NULL, @total, @valor);
SELECT @total AS total_productos, @valor AS valor_total_inventario;Parámetros de entrada y salida: INOUT
Un parámetro INOUT funciona en ambas direcciones: entra con un valor y puede modificarse para devolver un resultado diferente.
DELIMITER //
CREATE PROCEDURE sp_aplicar_descuento(
INOUT p_precio DECIMAL(10, 2),
IN p_porcentaje DECIMAL(5, 2)
)
BEGIN
SET p_precio = p_precio - (p_precio * p_porcentaje / 100);
SET p_precio = ROUND(p_precio, 2);
END //
DELIMITER ;-- El precio entra como 1299.99 y sale con el descuento aplicado
SET @precio = 1299.99;
CALL sp_aplicar_descuento(@precio, 15);
SELECT @precio AS precio_con_descuento; -- 1104.99Variables locales
Dentro de un procedimiento puedes declarar variables locales con DECLARE. Solo existen durante la ejecución del procedimiento y no son visibles desde fuera.
DELIMITER //
CREATE PROCEDURE sp_resumen_cliente(
IN p_id_cliente INT
)
BEGIN
-- Declarar variables locales
DECLARE v_nombre VARCHAR(100);
DECLARE v_total_pedidos INT;
DECLARE v_gasto_total DECIMAL(10, 2);
DECLARE v_ultimo_pedido DATE;
-- Obtener datos del cliente
SELECT nombre INTO v_nombre
FROM clientes
WHERE id = p_id_cliente;
-- Calcular estadísticas
SELECT
COUNT(DISTINCT p.id),
SUM(dp.cantidad * dp.precio_unit),
MAX(p.fecha)
INTO
v_total_pedidos,
v_gasto_total,
v_ultimo_pedido
FROM pedidos p
JOIN detalle_pedidos dp ON dp.id_pedido = p.id
WHERE p.id_cliente = p_id_cliente;
-- Mostrar el resumen
SELECT
v_nombre AS cliente,
COALESCE(v_total_pedidos, 0) AS total_pedidos,
COALESCE(v_gasto_total, 0.00) AS gasto_total,
COALESCE(v_ultimo_pedido, 'Sin pedidos') AS ultimo_pedido;
END //
DELIMITER ;
CALL sp_resumen_cliente(1);
CALL sp_resumen_cliente(3);Estructuras de control: IF y CASE
Los procedimientos pueden contener lógica condicional igual que cualquier lenguaje de programación.
IF / ELSEIF / ELSE
DELIMITER //
CREATE PROCEDURE sp_clasificar_cliente(
IN p_id_cliente INT,
OUT p_segmento VARCHAR(20)
)
BEGIN
DECLARE v_gasto DECIMAL(10, 2);
SELECT COALESCE(SUM(dp.cantidad * dp.precio_unit), 0)
INTO v_gasto
FROM pedidos p
JOIN detalle_pedidos dp ON dp.id_pedido = p.id
WHERE p.id_cliente = p_id_cliente
AND p.estado = 'completado';
IF v_gasto >= 3000 THEN
SET p_segmento = 'VIP';
ELSEIF v_gasto >= 1000 THEN
SET p_segmento = 'Premium';
ELSEIF v_gasto > 0 THEN
SET p_segmento = 'Estándar';
ELSE
SET p_segmento = 'Inactivo';
END IF;
END //
DELIMITER ;
CALL sp_clasificar_cliente(1, @segmento);
SELECT @segmento;CASE
DELIMITER //
CREATE PROCEDURE sp_estado_stock(
IN p_id_producto INT,
OUT p_estado VARCHAR(30)
)
BEGIN
DECLARE v_stock INT;
SELECT stock INTO v_stock
FROM productos
WHERE id = p_id_producto;
CASE
WHEN v_stock = 0 THEN SET p_estado = 'Agotado';
WHEN v_stock < 10 THEN SET p_estado = 'Crítico';
WHEN v_stock < 50 THEN SET p_estado = 'Bajo';
WHEN v_stock < 100 THEN SET p_estado = 'Normal';
ELSE SET p_estado = 'Abundante';
END CASE;
END //
DELIMITER ;
CALL sp_estado_stock(1, @estado);
SELECT @estado;Bucles: WHILE, REPEAT y LOOP
Los procedimientos admiten tres tipos de bucles para repetir instrucciones.
WHILE
DELIMITER //
CREATE PROCEDURE sp_generar_serie(
IN p_inicio INT,
IN p_fin INT
)
BEGIN
DECLARE v_actual INT DEFAULT p_inicio;
-- Tabla temporal para guardar los resultados
CREATE TEMPORARY TABLE IF NOT EXISTS serie (numero INT);
TRUNCATE TABLE serie;
WHILE v_actual <= p_fin DO
INSERT INTO serie VALUES (v_actual);
SET v_actual = v_actual + 1;
END WHILE;
SELECT numero FROM serie;
DROP TEMPORARY TABLE serie;
END //
DELIMITER ;
CALL sp_generar_serie(1, 10);REPEAT (equivalente a do-while)
DELIMITER //
CREATE PROCEDURE sp_sumar_hasta(
IN p_limite INT,
OUT p_suma INT
)
BEGIN
DECLARE v_contador INT DEFAULT 1;
SET p_suma = 0;
REPEAT
SET p_suma = p_suma + v_contador;
SET v_contador = v_contador + 1;
UNTIL v_contador > p_limite
END REPEAT;
END //
DELIMITER ;
CALL sp_sumar_hasta(100, @resultado);
SELECT @resultado; -- 5050LOOP con LEAVE (salida controlada)
DELIMITER //
CREATE PROCEDURE sp_buscar_primer_disponible(
IN p_categoria VARCHAR(50),
OUT p_id_producto INT,
OUT p_nombre VARCHAR(100)
)
BEGIN
DECLARE v_done INT DEFAULT 0;
DECLARE v_id INT;
DECLARE v_nombre VARCHAR(100);
DECLARE v_stock INT;
DECLARE cur CURSOR FOR
SELECT id, nombre, stock
FROM productos
WHERE categoria = p_categoria
ORDER BY stock DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
SET p_id_producto = NULL;
SET p_nombre = NULL;
OPEN cur;
busqueda: LOOP
FETCH cur INTO v_id, v_nombre, v_stock;
IF v_done = 1 THEN
LEAVE busqueda;
END IF;
IF v_stock > 0 THEN
SET p_id_producto = v_id;
SET p_nombre = v_nombre;
LEAVE busqueda;
END IF;
END LOOP busqueda;
CLOSE cur;
END //
DELIMITER ;
CALL sp_buscar_primer_disponible('Electrónica', @id, @nombre);
SELECT @id, @nombre;Cursores: recorrer resultados fila por fila
Un cursor te permite recorrer el resultado de una consulta fila por fila dentro de un procedimiento. Son útiles cuando necesitas procesar cada registro de forma individual.
DELIMITER //
CREATE PROCEDURE sp_actualizar_precios_categoria(
IN p_categoria VARCHAR(50),
IN p_porcentaje DECIMAL(5, 2)
)
BEGIN
DECLARE v_done INT DEFAULT 0;
DECLARE v_id INT;
DECLARE v_precio DECIMAL(10, 2);
DECLARE v_nuevo DECIMAL(10, 2);
DECLARE v_contador INT DEFAULT 0;
-- Declarar el cursor
DECLARE cur CURSOR FOR
SELECT id, precio
FROM productos
WHERE categoria = p_categoria;
-- Handler para cuando no hay más filas
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
OPEN cur;
proceso: LOOP
FETCH cur INTO v_id, v_precio;
IF v_done THEN
LEAVE proceso;
END IF;
SET v_nuevo = ROUND(v_precio * (1 + p_porcentaje / 100), 2);
UPDATE productos SET precio = v_nuevo WHERE id = v_id;
SET v_contador = v_contador + 1;
END LOOP proceso;
CLOSE cur;
SELECT CONCAT(v_contador, ' productos actualizados en categoría: ', p_categoria) AS resultado;
END //
DELIMITER ;
-- Aumentar un 10% los precios de Electrónica
CALL sp_actualizar_precios_categoria('Electrónica', 10);
-- Reducir un 5% los precios de Periféricos
CALL sp_actualizar_precios_categoria('Periféricos', -5);Manejo de errores con DECLARE HANDLER
Los procedimientos pueden capturar errores y reaccionar ante ellos de forma controlada usando DECLARE HANDLER.
DELIMITER //
CREATE PROCEDURE sp_registrar_pedido(
IN p_id_cliente INT,
IN p_id_producto INT,
IN p_cantidad INT,
OUT p_resultado VARCHAR(100)
)
BEGIN
DECLARE v_stock INT;
DECLARE v_precio DECIMAL(10, 2);
DECLARE v_id_pedido INT;
DECLARE v_error INT DEFAULT 0;
-- Capturar cualquier error SQL
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET v_error = 1;
-- Iniciar transacción
START TRANSACTION;
-- Verificar stock disponible
SELECT stock, precio
INTO v_stock, v_precio
FROM productos
WHERE id = p_id_producto;
IF v_stock IS NULL THEN
SET p_resultado = 'Error: el producto no existe';
ROLLBACK;
ELSEIF v_stock < p_cantidad THEN
SET p_resultado = CONCAT('Error: stock insuficiente. Disponible: ', v_stock);
ROLLBACK;
ELSE
-- Crear el pedido
INSERT INTO pedidos (id_cliente, fecha, estado)
VALUES (p_id_cliente, CURDATE(), 'pendiente');
SET v_id_pedido = LAST_INSERT_ID();
-- Agregar el detalle
INSERT INTO detalle_pedidos (id_pedido, id_producto, cantidad, precio_unit)
VALUES (v_id_pedido, p_id_producto, p_cantidad, v_precio);
-- Descontar el stock
UPDATE productos
SET stock = stock - p_cantidad
WHERE id = p_id_producto;
IF v_error THEN
ROLLBACK;
SET p_resultado = 'Error inesperado al registrar el pedido';
ELSE
COMMIT;
SET p_resultado = CONCAT('Pedido #', v_id_pedido, ' registrado correctamente');
END IF;
END IF;
END //
DELIMITER ;
-- Registrar un pedido válido
CALL sp_registrar_pedido(2, 2, 3, @resultado);
SELECT @resultado;
-- Intentar pedir más de lo que hay en stock
CALL sp_registrar_pedido(1, 1, 9999, @resultado);
SELECT @resultado;Procedimiento completo de ejemplo: proceso de ventas
Un procedimiento que integra todo lo aprendido: validaciones, transacciones, variables, condicionales y manejo de errores.
DELIMITER //
CREATE PROCEDURE sp_procesar_venta(
IN p_id_cliente INT,
IN p_productos JSON,
OUT p_id_pedido INT,
OUT p_total DECIMAL(10, 2),
OUT p_mensaje VARCHAR(200)
)
BEGIN
DECLARE v_error INT DEFAULT 0;
DECLARE v_i INT DEFAULT 0;
DECLARE v_count INT;
DECLARE v_id_prod INT;
DECLARE v_cantidad INT;
DECLARE v_precio DECIMAL(10, 2);
DECLARE v_stock INT;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
SET v_error = 1;
END;
SET p_id_pedido = 0;
SET p_total = 0.00;
SET p_mensaje = '';
-- Verificar que el cliente existe
IF NOT EXISTS (SELECT 1 FROM clientes WHERE id = p_id_cliente) THEN
SET p_mensaje = 'Error: el cliente no existe';
LEAVE sp_procesar_venta;
END IF;
START TRANSACTION;
-- Crear el pedido
INSERT INTO pedidos (id_cliente, fecha, estado)
VALUES (p_id_cliente, CURDATE(), 'pendiente');
SET p_id_pedido = LAST_INSERT_ID();
-- Procesar cada producto del JSON
SET v_count = JSON_LENGTH(p_productos);
WHILE v_i < v_count DO
SET v_id_prod = JSON_UNQUOTE(JSON_EXTRACT(p_productos, CONCAT('$[', v_i, '].id')));
SET v_cantidad = JSON_UNQUOTE(JSON_EXTRACT(p_productos, CONCAT('$[', v_i, '].cantidad')));
-- Obtener precio y stock actuales
SELECT precio, stock
INTO v_precio, v_stock
FROM productos
WHERE id = v_id_prod;
IF v_stock < v_cantidad THEN
ROLLBACK;
SET p_mensaje = CONCAT('Stock insuficiente para el producto ID: ', v_id_prod);
SET p_id_pedido = 0;
LEAVE sp_procesar_venta;
END IF;
-- Insertar detalle
INSERT INTO detalle_pedidos (id_pedido, id_producto, cantidad, precio_unit)
VALUES (p_id_pedido, v_id_prod, v_cantidad, v_precio);
-- Actualizar stock
UPDATE productos SET stock = stock - v_cantidad WHERE id = v_id_prod;
-- Acumular total
SET p_total = p_total + (v_precio * v_cantidad);
SET v_i = v_i + 1;
END WHILE;
IF v_error THEN
ROLLBACK;
SET p_mensaje = 'Error inesperado durante el procesamiento';
SET p_id_pedido = 0;
SET p_total = 0.00;
ELSE
-- Actualizar estado del pedido
UPDATE pedidos SET estado = 'completado' WHERE id = p_id_pedido;
COMMIT;
SET p_mensaje = CONCAT('Venta procesada. Pedido #', p_id_pedido,
'. Total: $', FORMAT(p_total, 2));
END IF;
END //
DELIMITER ;
-- Llamada con un JSON de productos
CALL sp_procesar_venta(
1,
'[{"id": 2, "cantidad": 2}, {"id": 4, "cantidad": 1}]',
@pedido, @total, @mensaje
);
SELECT @pedido AS id_pedido, @total AS total, @mensaje AS resultado;Gestionar procedimientos almacenados
Ver los procedimientos existentes
-- Listar todos los procedimientos de la base de datos actual
SHOW PROCEDURE STATUS WHERE Db = 'tienda_java';
-- Ver el código fuente de un procedimiento
SHOW CREATE PROCEDURE sp_listar_productos;
-- Consultar desde el catálogo del sistema
SELECT routine_name, created, last_altered
FROM information_schema.routines
WHERE routine_type = 'PROCEDURE'
AND routine_schema = 'tienda_java';Modificar un procedimiento
MySQL no tiene ALTER PROCEDURE para cambiar el cuerpo del procedimiento. Para modificarlo hay que eliminarlo y recrearlo:
DROP PROCEDURE IF EXISTS sp_listar_productos;
DELIMITER //
CREATE PROCEDURE sp_listar_productos()
BEGIN
SELECT id, nombre, precio, stock, categoria
FROM productos
WHERE stock > 0 -- nueva condición agregada
ORDER BY nombre;
END //
DELIMITER ;Eliminar un procedimiento
-- Eliminar un procedimiento
DROP PROCEDURE sp_listar_productos;
-- Eliminar solo si existe (evita error si no existe)
DROP PROCEDURE IF EXISTS sp_listar_productos;Llamar procedimientos desde Java
Desde JDBC, los procedimientos almacenados se llaman usando CallableStatement:
import java.sql.*;
public class LlamarProcedimiento {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/tienda_java?useSSL=false&serverTimezone=UTC";
try (Connection con = DriverManager.getConnection(url, "root", "tu_password")) {
// Llamar procedimiento con parámetros IN y OUT
String sql = "{CALL sp_contar_productos(?, ?, ?)}";
try (CallableStatement stmt = con.prepareCall(sql)) {
// Parámetro IN
stmt.setString(1, "Electrónica");
// Registrar parámetros OUT
stmt.registerOutParameter(2, Types.INTEGER);
stmt.registerOutParameter(3, Types.DECIMAL);
stmt.execute();
// Leer los valores OUT
int total = stmt.getInt(2);
double valor = stmt.getDouble(3);
System.out.println("Productos electrónicos: " + total);
System.out.printf("Valor del inventario: $%.2f%n", valor);
}
} catch (SQLException e) {
System.out.println("Error: " + e.getMessage());
}
}
}Cuándo usar procedimientos almacenados
Los procedimientos almacenados son una herramienta poderosa, pero no son siempre la mejor opción. Úsalos cuando:
- Necesitas ejecutar varias sentencias SQL como una unidad atómica y quieres centralizar esa lógica en la base de datos.
- La misma lógica se llama desde múltiples aplicaciones o lenguajes distintos. Un procedimiento en la base de datos es neutral al lenguaje.
- Quieres reducir el tráfico de red entre la aplicación y el servidor de base de datos en operaciones que implican muchas sentencias.
- Necesitas controlar el acceso a los datos sin exponer la estructura real de las tablas.
- Tienes operaciones de mantenimiento periódico como limpiar registros antiguos, recalcular estadísticas o archivar datos.
Evítalos cuando:
- La lógica de negocio cambia frecuentemente y prefieres que esté en el código de la aplicación donde es más fácil versionar y desplegar.
- Tu equipo no tiene experiencia sólida en SQL procedural y el mantenimiento se vuelve costoso.
- Usas un ORM como Hibernate o JPA, que gestiona el acceso a datos de forma más abstracta.
- Necesitas escalar horizontalmente la base de datos, ya que los procedimientos almacenados dificultan algunas estrategias de escalabilidad.
Buenas prácticas
- Usa el prefijo
sp_en todos los nombres para identificarlos fácilmente. - Usa el prefijo
p_en parámetros yv_en variables locales para evitar conflictos con nombres de columnas. - Siempre usa transacciones cuando el procedimiento ejecuta más de una operación de escritura que debe ser atómica.
- Documenta el propósito, los parámetros y el comportamiento esperado con comentarios dentro del procedimiento.
- Maneja los errores explícitamente con
DECLARE HANDLERen lugar de dejar que los errores propaguen de forma silenciosa. - Prueba cada procedimiento de forma aislada antes de integrarlo con la aplicación, usando distintos valores de entrada incluyendo casos límite y errores esperados.
Conclusión
Los procedimientos almacenados son una pieza clave del desarrollo con bases de datos relacionales. Te permiten encapsular lógica compleja en la capa de datos, reutilizarla desde cualquier aplicación y ejecutarla de forma eficiente y segura.
Dominar su sintaxis, los tipos de parámetros, las estructuras de control, los cursores y el manejo de errores te da un nivel de control sobre la base de datos que va mucho más allá de simples consultas SELECT. Son especialmente valiosos en sistemas donde la integridad de los datos es crítica y varias aplicaciones comparten el mismo motor de base de datos.
Para seguir profundizando en SQL, te recomendamos leer nuestros artículos sobre qué son las vistas en SQL y cuándo usarlas y sobre transacciones en SQL: qué son ACID y cómo usarlas correctamente, que complementan perfectamente lo que aprendiste aquí.
No hay comentarios todavía. Sé el primero en compartir tu opinión.