Funciones almacenadas en MySQL: CREATE FUNCTION, RETURN y diferencias con PROCEDURE

Una función almacenada es un bloque de lógica guardado en MySQL que recibe parámetros y devuelve un valor.

¿Qué problema resuelve una función?

Una función encapsula un cálculo que produce un valor. Ese cálculo puede reutilizarse en SELECT, WHERE, ORDER BY u otras expresiones.

Ejemplos típicos: calcular un impuesto, aplicar una regla matemática o devolver una clasificación.

¿En qué se diferencia de un procedimiento?

Una función se usa dentro de expresiones SQL y devuelve un único valor. Un procedimiento se ejecuta con CALL y está más orientado a procesos.

La forma de pensarla es similar a una función de programación tradicional: recibe argumentos y produce un resultado.

Primera función

DELIMITER //
CREATE FUNCTION sumar(p_a INT, p_b INT)
RETURNS INT
DETERMINISTIC
BEGIN
    RETURN p_a + p_b;
END //
DELIMITER ;

Parámetros de una función

Los parámetros aparecen entre paréntesis después del nombre. Cada uno necesita un nombre y un tipo.

En sumar(p_a INT, p_b INT), la función recibe dos enteros.

RETURNS

Declara el tipo de dato que devuelve la función.

Ese tipo debe ser compatible con el valor que finalmente entregue RETURN.

RETURNS DECIMAL(10,2)

RETURN

Entrega el valor final.

RETURN p_a + p_b;

RETURN termina la función

Cuando MySQL alcanza RETURN, obtiene el valor de salida de esa ejecución.

DETERMINISTIC

Indica que, con los mismos valores de entrada, la función devuelve siempre el mismo resultado.

Por ejemplo, una función que suma 10 + 5 siempre devuelve 15. Una función basada en la fecha actual no tendría esa característica.

Usar una función en SELECT

SELECT sumar(10, 5);

Función para calcular IVA

CREATE FUNCTION calcular_iva(
    p_importe DECIMAL(10,2),
    p_porcentaje DECIMAL(5,2)
)
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
    RETURN p_importe * p_porcentaje / 100;
END

Aplicar una función a cada fila

SELECT nombre,
       precio,
       calcular_iva(precio, 21) AS iva
FROM productos;

La función se ejecuta para cada fila procesada.

Si la consulta devuelve 100.000 productos, la función podría evaluarse 100.000 veces. Por eso una función cómoda desde el punto de vista del código puede tener un costo importante.

Función con IF

IF p_edad >= 18 THEN
    RETURN 'Mayor de edad';
ELSE
    RETURN 'Menor de edad';
END IF;

Variables locales

DECLARE v_descuento DECIMAL(10,2);

Función que consulta una tabla

CREATE FUNCTION cantidad_alumnos_curso(p_curso_id INT)
RETURNS INT
READS SQL DATA
BEGIN
    DECLARE v_total INT;
    SELECT COUNT(*)
    INTO v_total
    FROM alumnos
    WHERE curso_id = p_curso_id;
    RETURN v_total;
END

Usar funciones en WHERE

Es posible, pero puede afectar el rendimiento o impedir aprovechar índices.

Funciones y efectos secundarios

Conceptualmente una función es más fácil de mantener cuando se comporta como un cálculo: recibe datos y devuelve un valor. Para procesos que modifican muchas tablas o ejecutan flujos complejos suele ser más claro usar procedimientos o la lógica de aplicación.

Función vs procedimiento

  • Función: devuelve un valor y puede participar en SELECT.
  • Procedimiento: se ejecuta con CALL y puede representar procesos con varias operaciones.

Eliminar una función

DROP FUNCTION IF EXISTS sumar;

Rendimiento

Una función aplicada a millones de filas puede ejecutarse millones de veces. Conviene medir su costo.

Errores comunes

  • olvidar RETURNS;
  • devolver un tipo incompatible;
  • usar una función para un proceso complejo;
  • aplicar funciones sobre columnas indexadas sin analizar impacto.

Ejercicios

  1. función para duplicar un número;
  2. calcular IVA;
  3. aplicar descuento;
  4. clasificar nota;
  5. contar alumnos de un curso.

¿Qué sigue?

Triggers en MySQL.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← Procedimientos almacenados

Triggers en MySQL →

Procedimientos almacenados en MySQL: CREATE PROCEDURE, parámetros y ejemplos prácticos

Un procedimiento almacenado es un bloque de instrucciones SQL guardado dentro de MySQL para poder ejecutarlo cuando sea necesario.

¿Dónde vive un procedimiento almacenado?

El procedimiento se guarda dentro del servidor MySQL, no dentro del código PHP, JavaScript o Python de la aplicación.

Esto permite que distintas aplicaciones puedan invocar la misma operación, aunque también significa que parte de la lógica queda fuera del repositorio de código si no se versiona adecuadamente.

¿Qué problema resuelve?

Si una misma operación SQL compleja se repite en distintos lugares, un procedimiento permite centralizarla y reutilizarla.

También puede agrupar varias sentencias, recibir parámetros, usar variables, tomar decisiones y devolver resultados.

Primer procedimiento

DELIMITER //

CREATE PROCEDURE listar_alumnos()
BEGIN
    SELECT *
    FROM alumnos;
END //

DELIMITER ;

¿Qué significa DELIMITER?

MySQL usa normalmente el punto y coma para terminar una sentencia. Como dentro de un procedimiento hay varios puntos y coma, cambiamos temporalmente el delimitador.

BEGIN y END

Marcan el inicio y el final del bloque de instrucciones.

Crear no significa ejecutar

CREATE PROCEDURE registra el procedimiento en MySQL. Su contenido no se ejecuta en ese momento.

La ejecución ocurre cuando lo invocamos con CALL.

CALL

Un procedimiento se ejecuta con CALL.

CALL listar_alumnos();

Parámetros: entrada y salida

Los parámetros permiten que un mismo procedimiento trabaje con distintos valores sin tener que reescribirlo.

  • IN: entra un valor;
  • OUT: sale un valor;
  • INOUT: entra y puede salir modificado.

Parámetros IN

Los parámetros IN reciben valores desde afuera.

DELIMITER //
CREATE PROCEDURE alumnos_por_edad(
    IN p_edad INT
)
BEGIN
    SELECT nombre, apellido, edad
    FROM alumnos
    WHERE edad = p_edad;
END //
DELIMITER ;

Lo ejecutamos así:

CALL alumnos_por_edad(25);

Parámetros OUT

Un parámetro OUT permite devolver un valor.

DELIMITER //
CREATE PROCEDURE contar_alumnos(
    OUT p_total INT
)
BEGIN
    SELECT COUNT(*)
    INTO p_total
    FROM alumnos;
END //
DELIMITER ;
CALL contar_alumnos(@total);
SELECT @total;

INOUT

INOUT recibe un valor y también puede devolverlo modificado.

CREATE PROCEDURE incrementar(INOUT p_numero INT)
BEGIN
    SET p_numero = p_numero + 1;
END

Parámetros y variables no son lo mismo

Un parámetro conecta el procedimiento con quien lo llama. Una variable local existe solo mientras el procedimiento se está ejecutando.

Variables locales

Se crean con DECLARE.

DECLARE v_promedio DECIMAL(10,2);

SELECT … INTO

Permite guardar el resultado de una consulta en una variable.

SELECT AVG(edad)
INTO v_promedio
FROM alumnos;

IF, ELSEIF y ELSE

Los procedimientos pueden tomar decisiones.

IF p_nota >= 8 THEN
    SET v_estado = 'Promocionado';
ELSEIF p_nota >= 6 THEN
    SET v_estado = 'Aprobado';
ELSE
    SET v_estado = 'Desaprobado';
END IF;

Procedimiento con INSERT

CREATE PROCEDURE crear_alumno(
    IN p_nombre VARCHAR(100),
    IN p_apellido VARCHAR(100),
    IN p_edad INT
)
BEGIN
    INSERT INTO alumnos(nombre, apellido, edad)
    VALUES(p_nombre, p_apellido, p_edad);
END

Procedimientos con varias operaciones

Un procedimiento puede insertar una cabecera, crear detalles, actualizar stock y validar condiciones. Cuando esas acciones forman una sola operación de negocio, normalmente conviene combinarlas con una transacción.

Procedimientos y transacciones

Un procedimiento puede formar parte de una transacción cuando varias operaciones deben ejecutarse juntas.

Eliminar procedimiento

DROP PROCEDURE IF EXISTS listar_alumnos;

Ver procedimientos existentes

SHOW PROCEDURE STATUS;

Procedimientos vs lógica de aplicación

No toda lógica debe ir a la base. Los procedimientos son útiles para operaciones fuertemente relacionadas con datos o reutilizadas por distintos sistemas.

Una desventaja es que la lógica queda más repartida: parte en la aplicación y parte en MySQL. En proyectos grandes eso puede dificultar pruebas, despliegues y mantenimiento si no existe una estrategia clara.

Errores comunes

  • olvidar DELIMITER;
  • confundir IN, OUT e INOUT;
  • crear procedimientos demasiado grandes;
  • duplicar lógica que ya existe en la aplicación;
  • no validar parámetros.

Ejercicios

  1. listar productos;
  2. buscar por categoría;
  3. crear producto;
  4. actualizar precio;
  5. devolver cantidad mediante OUT.

¿Qué sigue?

Funciones almacenadas.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← Vistas en SQL

Funciones almacenadas →

Vistas en SQL: CREATE VIEW, cómo usarlas y cuándo convienen

Una vista permite guardar una consulta con un nombre y consultarla después como si fuera una tabla virtual.

¿Qué problema resuelve una vista?

Si una consulta compleja se repite en distintos reportes, podemos centralizarla y reutilizarla.

Una vista no es una copia de la tabla

Una vista guarda la definición de una consulta. Cuando la consultamos, MySQL vuelve a ejecutar esa lógica sobre las tablas originales.

Eso significa que, si cambian los datos de las tablas base, el resultado de la vista también cambia.

CREATE VIEW

CREATE VIEW vista_alumnos_cursos AS
SELECT a.id, a.nombre, a.apellido,
       c.nombre AS curso
FROM alumnos AS a
LEFT JOIN cursos AS c
    ON a.curso_id = c.id;

La vista guarda la definición de la consulta.

Desde ese momento podemos usar vista_alumnos_cursos en SELECT como si fuera una tabla, aunque realmente dependa de alumnos y cursos.

Consultar una vista

SELECT *
FROM vista_alumnos_cursos;

¿Por qué simplifica el trabajo?

Sin la vista, cada reporte debería repetir el JOIN completo. Con la vista, el consumidor solo necesita consultar una estructura ya preparada.

Esto ayuda a evitar duplicación de SQL y reduce el riesgo de que distintos reportes implementen la misma lógica de manera diferente.

Filtrar una vista

SELECT *
FROM vista_alumnos_cursos
WHERE curso = 'Programación';

Filtrar al consultar vs filtrar dentro de la vista

Podemos crear una vista general y aplicar WHERE cada vez, o crear una vista que ya represente un subconjunto concreto.

La segunda opción puede ser útil cuando esa regla se reutiliza constantemente.

Vista con filtros incorporados

CREATE VIEW alumnos_activos AS
SELECT id, nombre, apellido, email
FROM alumnos
WHERE activo = TRUE;

Vista con agregaciones

CREATE VIEW resumen_cursos AS
SELECT c.id, c.nombre, COUNT(a.id) AS cantidad_alumnos
FROM cursos AS c
LEFT JOIN alumnos AS a
    ON c.id = a.curso_id
GROUP BY c.id, c.nombre;

CREATE OR REPLACE VIEW

Reemplaza la definición de una vista existente.

CREATE OR REPLACE VIEW alumnos_activos AS
SELECT id, nombre, apellido, email, fecha_ingreso
FROM alumnos
WHERE activo = TRUE;

ALTER VIEW

Otra forma de modificar la definición.

DROP VIEW

DROP VIEW IF EXISTS alumnos_activos;

Dependencias de una vista

Como la vista depende de tablas y columnas concretas, ciertos cambios estructurales en esas tablas pueden romperla. Por ejemplo, eliminar una columna utilizada por la vista obliga a revisar su definición.

¿Una vista guarda datos?

Una vista tradicional normalmente no almacena una copia independiente de las filas: consulta las tablas originales.

Vistas y seguridad

Podemos exponer solo ciertas columnas y ocultar información sensible.

Por ejemplo, una vista pública de clientes podría mostrar nombre y ciudad, pero excluir documento, teléfono o email.

Esto no reemplaza una política de permisos correcta, pero puede formar parte de ella.

¿Se puede hacer INSERT o UPDATE sobre una vista?

Algunas vistas simples son actualizables. Las que incluyen agregaciones, GROUP BY, DISTINCT o lógica compleja pueden no serlo.

Antes de tratar una vista como si fuera una tabla editable conviene verificar sus características. Una vista de reporte suele pensarse principalmente para lectura.

WITH CHECK OPTION

Evita que una modificación realizada a través de la vista haga que el registro deje de cumplir la condición de esa vista.

CREATE VIEW alumnos_activos AS
SELECT *
FROM alumnos
WHERE activo = TRUE
WITH CHECK OPTION;

Vistas y rendimiento

Una vista no acelera automáticamente una consulta. El rendimiento sigue dependiendo de índices, joins, filtros y plan de ejecución.

Errores comunes

  • crear vistas para consultas que no se reutilizan;
  • suponer que almacenan una copia independiente;
  • actualizar vistas complejas sin verificar si son actualizables;
  • crear demasiadas capas de vistas.

Ejercicios

  1. crear vista de alumnos activos;
  2. crear vista alumnos+cursos;
  3. crear resumen por curso;
  4. modificar una vista;
  5. crear una vista que oculte columnas sensibles.

¿Qué sigue?

Procedimientos almacenados.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← Índices en MySQL

Procedimientos almacenados →

Índices en MySQL: qué son, cómo funcionan y cuándo usarlos

Una consulta puede funcionar perfecto con 100 filas y volverse lenta con 10 millones. Los índices ayudan a encontrar registros más eficientemente.

¿Qué es un índice?

Es una estructura auxiliar que organiza ciertos valores para que el motor pueda localizar filas sin recorrer necesariamente toda la tabla.

Es parecido al índice de un libro: no reemplaza el contenido, pero ayuda a encontrarlo.

¿Cómo busca MySQL sin un índice útil?

Una posibilidad es recorrer fila por fila y comprobar la condición. Esto se conoce como table scan o recorrido completo.

Con 50 filas puede ser irrelevante. Con millones de filas, puede ser costoso.

Consulta sin índice

SELECT *
FROM clientes
WHERE email = 'ana@email.com';

Si no existe un índice útil, MySQL puede tener que revisar muchas filas.

Qué cambia al crear un índice

El índice agrega una estructura ordenada que permite al motor reducir drásticamente la cantidad de filas candidatas en ciertas búsquedas.

No modifica los datos de la tabla ni cambia el resultado de la consulta: cambia la forma en que MySQL puede llegar a ellos.

CREATE INDEX

CREATE INDEX idx_clientes_email
ON clientes(email);

PRIMARY KEY también es un índice

Las claves primarias tienen un índice asociado.

Por eso una búsqueda por id suele ser eficiente incluso si nunca creamos manualmente un índice llamado idx_id.

UNIQUE INDEX

Además de acelerar búsquedas, obliga a que los valores sean únicos.

CREATE UNIQUE INDEX uq_clientes_email
ON clientes(email);

Índices compuestos

Pueden incluir varias columnas.

CREATE INDEX idx_clientes_ciudad_apellido
ON clientes(ciudad, apellido);

Índice simple vs índice compuesto

Un índice simple tiene una sola columna. Un índice compuesto combina varias.

La combinación debería reflejar consultas reales. No conviene crear índices compuestos al azar.

El orden importa

En (ciudad, apellido), MySQL puede aprovechar el índice para ciudad o ciudad+apellido, pero no necesariamente igual para apellido solamente.

Esto se conoce como regla del prefijo izquierdo.

Por ejemplo, (cliente_id, fecha) puede ser excelente para “ventas de un cliente entre fechas”, pero no necesariamente para consultas que solo filtran por fecha.

Índices y ORDER BY

SELECT *
FROM clientes
WHERE ciudad = 'Santa Rosa'
ORDER BY apellido;

Índices y JOIN

Las columnas utilizadas para relacionar tablas suelen ser candidatas importantes para indexación.

No asumir: medir

La presencia de un índice no garantiza que MySQL vaya a usarlo. El optimizador compara alternativas y elige el plan que estima más conveniente.

Por eso debemos mirar el plan de ejecución.

EXPLAIN

EXPLAIN muestra cómo MySQL planea ejecutar una consulta.

EXPLAIN SELECT *
FROM clientes
WHERE email = 'ana@email.com';

Qué mirar en EXPLAIN

Algunos campos útiles son:

  • type: tipo de acceso;
  • possible_keys: índices candidatos;
  • key: índice elegido;
  • rows: cantidad estimada de filas a examinar.

possible_keys y key

  • possible_keys: índices que podrían utilizarse;
  • key: índice elegido.

Table scan

Un acceso tipo ALL puede indicar un recorrido completo. No siempre es malo: en tablas pequeñas puede ser razonable.

Selectividad

Un índice suele ser más útil cuando reduce mucho la cantidad de filas candidatas.

Emails, documentos o códigos únicos suelen tener alta selectividad.

Índices en columnas con pocos valores

Un índice sobre una columna booleana puede aportar poco si casi todos los registros tienen el mismo valor.

¿Por qué no indexar todo?

Cada índice debe mantenerse cuando insertamos, modificamos o eliminamos datos. Además ocupa espacio.

En una tabla con muchísimas escrituras, un exceso de índices puede empeorar el rendimiento.

Costo de los índices

Los índices aceleran lecturas, pero agregan trabajo en:

  • INSERT;
  • UPDATE;
  • DELETE;
  • almacenamiento.

SHOW INDEX

SHOW INDEX FROM clientes;

DROP INDEX

DROP INDEX idx_clientes_email ON clientes;

Índices duplicados o redundantes

Antes de crear un nuevo índice conviene revisar los existentes. Dos índices muy similares pueden aportar poco y aumentar el costo de mantenimiento.

Ejemplo práctico

CREATE INDEX idx_ventas_cliente_fecha
ON ventas(cliente_id, fecha);

Este índice puede ayudar a consultar ventas de un cliente ordenadas por fecha.

Errores comunes

  • crear índices para todas las columnas;
  • ignorar el orden en índices compuestos;
  • no usar EXPLAIN;
  • crear índices redundantes;
  • optimizar sin datos representativos.

Ejercicios

  1. crear índice sobre email;
  2. crear índice único sobre código;
  3. crear índice compuesto ciudad+apellido;
  4. comparar EXPLAIN antes y después.

¿Qué sigue?

Vistas en SQL.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← Transacciones en SQL

Vistas en SQL →

Transacciones en SQL: START TRANSACTION, COMMIT y ROLLBACK con ejemplos

Una transacción permite agrupar varias operaciones para que se comporten como una sola unidad. Es fundamental cuando un proceso no puede quedar “a mitad de camino”.

El problema de las operaciones parciales

En una transferencia bancaria hay dos pasos:

  1. restar dinero de una cuenta;
  2. sumarlo en otra.

Si el primer paso ocurre y el segundo falla, los datos quedan inconsistentes.

La idea de “todo o nada”

Una transacción agrupa instrucciones que pertenecen al mismo proceso de negocio. Si una falla, podemos evitar que las demás queden confirmadas.

El objetivo no es solo “poder deshacer”, sino mantener la base en un estado coherente.

START TRANSACTION

Marca el inicio de una transacción.

START TRANSACTION;

COMMIT

Confirma los cambios de forma definitiva.

Después de COMMIT, un ROLLBACK de esa misma transacción ya no puede revertirlos.

COMMIT;

ROLLBACK

Revierte los cambios realizados desde el inicio de la transacción.

ROLLBACK es útil cuando detectamos un error de validación, falta de stock, saldo insuficiente o cualquier condición que impida completar el proceso.

ROLLBACK;

Ejemplo completo

START TRANSACTION;

UPDATE cuentas
SET saldo = saldo - 1000
WHERE id = 1;

UPDATE cuentas
SET saldo = saldo + 1000
WHERE id = 2;

COMMIT;

¿Qué pasaría si la segunda operación falla?

En el ejemplo de transferencia, si restamos 1000 de la cuenta 1 y luego falla la suma en la cuenta 2, debemos ejecutar ROLLBACK. Así el descuento inicial también se deshace.

Ese comportamiento es la base de la atomicidad.

Propiedades ACID

Atomicidad

Todo se ejecuta o nada se ejecuta.

Consistencia

La base debe pasar de un estado válido a otro estado válido.

Aislamiento

Transacciones concurrentes no deberían interferir incorrectamente.

Por ejemplo, dos ventas simultáneas no deberían descontar el mismo último producto sin coordinación.

Durabilidad

Una vez hecho COMMIT, los cambios deben persistir.

SAVEPOINT

Permite crear puntos intermedios dentro de una transacción.

START TRANSACTION;
UPDATE productos SET stock = stock - 1 WHERE id = 10;
SAVEPOINT despues_stock;
UPDATE ventas SET estado = 'procesada' WHERE id = 50;

Podemos volver solo hasta ese punto:

ROLLBACK TO despues_stock;

RELEASE SAVEPOINT

Cuando ya no necesitamos un punto intermedio podemos liberarlo.

RELEASE SAVEPOINT despues_stock;

Autocommit

MySQL suele confirmar automáticamente cada sentencia individual cuando no hay una transacción explícita.

Eso significa que un UPDATE ejecutado fuera de una transacción puede quedar confirmado inmediatamente.

SELECT @@autocommit;

Ejemplo: venta y stock

START TRANSACTION;
INSERT INTO ventas (cliente_id, total)
VALUES (15, 25000);
UPDATE productos
SET stock = stock - 1
WHERE id = 3;
COMMIT;

Transacciones y errores de aplicación

En una aplicación real, el código normalmente hace algo así:

  1. inicia la transacción;
  2. ejecuta las operaciones;
  3. si todo sale bien, hace COMMIT;
  4. si ocurre una excepción o validación fallida, hace ROLLBACK.

Cuándo usar transacciones

  • pagos;
  • transferencias;
  • stock;
  • facturas con detalles;
  • procesos que modifican varias tablas.

Errores comunes

  • olvidar COMMIT;
  • no ejecutar ROLLBACK ante error;
  • mantener transacciones abiertas demasiado tiempo;
  • incluir tareas innecesarias dentro de la transacción.

Ejercicios

  1. crear una transferencia;
  2. simular un error y hacer ROLLBACK;
  3. registrar venta y descontar stock;
  4. crear y usar un SAVEPOINT.

¿Qué sigue?

Índices y optimización.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← INSERT, UPDATE y DELETE

Índices en MySQL →

INSERT, UPDATE y DELETE en SQL: modificar datos de forma segura

Una base de datos no solo se consulta: también necesitamos crear, modificar y eliminar registros. Para eso usamos INSERT, UPDATE y DELETE.

Antes de modificar datos: pensar en el efecto

Las sentencias de escritura cambian el estado de la base. A diferencia de SELECT, un error puede modificar o eliminar información.

Por eso conviene pensar siempre:

  • qué filas van a cambiar;
  • qué restricciones pueden impedir la operación;
  • si hace falta una transacción;
  • si debemos probar primero la condición con SELECT.

INSERT: agregar datos

INSERT INTO agrega nuevas filas.

INSERT INTO alumnos (nombre, apellido, edad, email)
VALUES ('Ana', 'Pérez', 22, 'ana@email.com');

El orden de los valores debe coincidir con el orden de las columnas indicadas.

Es recomendable escribir siempre la lista de columnas. Así la consulta queda más clara y no depende del orden físico de la tabla.

Insertar varios registros

INSERT INTO alumnos (nombre, apellido, edad)
VALUES
('Juan', 'Gómez', 25),
('Lucía', 'Martínez', 21),
('Pedro', 'López', 30);

Omitir AUTO_INCREMENT

Si id es AUTO_INCREMENT, normalmente no lo enviamos. MySQL lo genera.

¿Qué pasa si falta una columna?

Depende de su definición:

  • si tiene DEFAULT, se usa ese valor;
  • si acepta NULL, puede quedar NULL;
  • si es NOT NULL y no tiene valor por defecto, el INSERT puede fallar.

DEFAULT

Cuando una columna tiene un valor predeterminado podemos omitirla.

NULL

NULL significa ausencia de valor. No es lo mismo que cero ni que una cadena vacía.

INSERT INTO alumnos (nombre, apellido, edad)
VALUES ('Pedro', 'López', NULL);

UPDATE: modificar registros

UPDATE alumnos
SET edad = 23
WHERE id = 1;

SET indica qué columnas cambian y WHERE cuáles filas serán afectadas.

Si WHERE identifica una única fila, normalmente modificamos un registro. Si la condición coincide con 200 filas, se modifican las 200.

Actualizar varias columnas

UPDATE alumnos
SET edad = 24,
    email = 'nuevo@email.com'
WHERE id = 1;

Filas afectadas

Después de un UPDATE o DELETE, MySQL informa cuántas filas fueron afectadas. Ese dato es una verificación muy útil: si esperábamos cambiar una fila y aparecen 10.000, algo salió mal.

El peligro de UPDATE sin WHERE

UPDATE alumnos
SET activo = FALSE;

Eso modifica todas las filas.

Probar primero con SELECT

Una práctica segura es verificar primero qué filas cumplen la condición.

SELECT *
FROM alumnos
WHERE edad < 18;

UPDATE con cálculos

UPDATE productos
SET precio = precio * 1.10
WHERE categoria_id = 3;

El nuevo valor puede calcularse a partir del anterior.

UPDATE no cambia la estructura

UPDATE modifica los valores almacenados. No agrega ni elimina columnas; para cambiar la estructura usamos ALTER TABLE.

DELETE: eliminar filas

DELETE FROM alumnos
WHERE id = 5;

DELETE sin WHERE

DELETE FROM alumnos;

Elimina todas las filas de la tabla.

La tabla sigue existiendo, pero queda vacía. Por eso una omisión accidental de WHERE puede ser muy grave.

DELETE y claves foráneas

Una relación puede impedir borrar un registro padre o aplicar reglas como CASCADE o SET NULL.

DELETE vs TRUNCATE

DELETE puede filtrar con WHERE. TRUNCATE vacía toda la tabla.

TRUNCATE suele ser más rápido para vaciar completamente una tabla y normalmente reinicia el contador AUTO_INCREMENT, pero no debe usarse cuando necesitamos borrar selectivamente.

TRUNCATE TABLE alumnos;

INSERT … SELECT

Permite copiar datos desde otra consulta.

INSERT INTO alumnos_backup (nombre, apellido, edad)
SELECT nombre, apellido, edad
FROM alumnos
WHERE activo = TRUE;

UPDATE con JOIN

MySQL permite actualizar según datos relacionados.

UPDATE alumnos AS a
INNER JOIN cursos AS c
    ON a.curso_id = c.id
SET a.activo = FALSE
WHERE c.nombre = 'Curso finalizado';

Buenas prácticas

  • probar condiciones con SELECT;
  • hacer backups antes de cambios masivos;
  • usar transacciones cuando corresponda;
  • revisar filas afectadas;
  • evitar UPDATE o DELETE sin WHERE por accidente.

Ejercicios

  1. insertar cinco productos;
  2. actualizar un precio;
  3. aumentar 10% una categoría;
  4. desactivar productos sin stock;
  5. eliminar productos inactivos.

¿Qué sigue?

Transacciones para proteger operaciones compuestas.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← CREATE TABLE y ALTER TABLE

Transacciones en SQL →

CREATE TABLE y ALTER TABLE en SQL: tipos de datos, claves y restricciones

Hasta ahora trabajamos con tablas ya existentes. En esta guía vamos a entender cómo se diseña una tabla desde cero y qué significa cada parte de su definición.

Diseñar antes de escribir SQL

Antes de crear una tabla conviene pensar qué representa. Una tabla debería tener una responsabilidad clara: alumnos, cursos, productos, ventas, etc.

El diseño comienza identificando:

  • la entidad;
  • sus atributos;
  • qué dato identifica cada fila;
  • qué campos son obligatorios;
  • qué reglas deben cumplirse;
  • con qué otras entidades se relaciona.

¿Qué hace CREATE TABLE?

CREATE TABLE crea una nueva estructura donde luego podremos guardar registros.

CREATE TABLE alumnos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100),
    apellido VARCHAR(100),
    edad INT,
    email VARCHAR(150)
);

Dentro de los paréntesis definimos las columnas, su tipo y las reglas que deben cumplir.

En otras palabras, CREATE TABLE no crea “datos”: crea el molde que determina cómo podrán almacenarse esos datos.

¿Qué es un tipo de dato?

El tipo de dato define qué clase de valor puede guardarse en una columna y cuánto espacio o precisión necesita.

INT

Se usa para números enteros. Es apropiado para edades, cantidades, identificadores o contadores, pero no para teléfonos o documentos si no vamos a hacer operaciones matemáticas con ellos.

edad INT

VARCHAR

Texto de longitud variable. El número entre paréntesis indica la longitud máxima permitida, no la cantidad fija de espacio que necesariamente ocupará cada valor.

nombre VARCHAR(100)

TEXT

Texto largo. Es útil para descripciones extensas, observaciones o contenido donde VARCHAR puede quedarse corto.

descripcion TEXT

DECIMAL

Valores decimales exactos, ideal para dinero.

precio DECIMAL(10,2)

El 10 indica la cantidad total de dígitos y el 2 la cantidad de decimales.

DATE y DATETIME

fecha_nacimiento DATE
fecha_creacion DATETIME

BOOLEAN

Representa valores lógicos verdadero/falso. En MySQL suele mapearse internamente a un entero pequeño.

activo BOOLEAN

PRIMARY KEY

Identifica de forma única cada fila.

id INT PRIMARY KEY

Una buena clave primaria debe ser única y estable.

Además, no debería depender de un dato que pueda cambiar frecuentemente. Por eso muchas tablas utilizan un identificador numérico independiente del negocio.

AUTO_INCREMENT

Hace que MySQL genere el siguiente número automáticamente.

id INT AUTO_INCREMENT PRIMARY KEY

NOT NULL

Indica que el campo es obligatorio.

Sin NOT NULL, la columna puede contener NULL, es decir, ausencia de valor. Eso no es lo mismo que una cadena vacía ni que cero.

nombre VARCHAR(100) NOT NULL

UNIQUE

Impide valores duplicados.

Es apropiado para datos que deben ser exclusivos, como un email, número de matrícula o código interno, siempre que las reglas del negocio realmente exijan unicidad.

email VARCHAR(150) UNIQUE

DEFAULT

Define un valor predeterminado cuando no enviamos uno.

activo BOOLEAN DEFAULT TRUE

CHECK

Permite imponer una condición.

Por ejemplo, podemos impedir edades negativas o porcentajes fuera del rango esperado. Esto mueve parte de la validación al propio motor de base de datos.

edad INT CHECK (edad >= 0)

FOREIGN KEY

Una clave foránea conecta una tabla con otra.

FOREIGN KEY (curso_id)
REFERENCES cursos(id)

Esto significa que curso_id debe apuntar a un curso válido.

La columna relacionada debería tener un tipo compatible con la clave referenciada. Si cursos.id es INT, normalmente alumnos.curso_id también debe ser INT.

Integridad referencial

Gracias a las claves foráneas, la base puede impedir relaciones inválidas.

Qué ocurre si borramos el registro relacionado

Imaginemos que un alumno apunta al curso 5 y queremos eliminar ese curso. MySQL necesita saber qué hacer con esa relación.

Las opciones típicas son:

  • RESTRICT o comportamiento equivalente: impedir la eliminación;
  • CASCADE: propagar la eliminación;
  • SET NULL: mantener el registro hijo pero quitar la referencia.

ON DELETE y ON UPDATE

Definen qué ocurre si cambia o se elimina el registro relacionado.

FOREIGN KEY (curso_id)
REFERENCES cursos(id)
ON DELETE SET NULL
ON UPDATE CASCADE

Modificar estructura no es lo mismo que modificar datos

UPDATE cambia valores dentro de filas. ALTER TABLE cambia la estructura de la tabla: columnas, tipos o restricciones.

ALTER TABLE

ALTER TABLE modifica una tabla ya creada.

Agregar una columna

ALTER TABLE alumnos
ADD telefono VARCHAR(30);

Modificar una columna

ALTER TABLE alumnos
MODIFY telefono VARCHAR(50) NOT NULL;

Renombrar una columna

ALTER TABLE alumnos
RENAME COLUMN telefono TO celular;

Eliminar una columna

ALTER TABLE alumnos
DROP COLUMN celular;

DROP TABLE

Elimina la tabla completa: estructura y datos.

DROP TABLE alumnos;

TRUNCATE TABLE

Vacía la tabla, pero conserva su estructura.

Es una operación mucho más drástica que un DELETE con WHERE: no sirve para eliminar selectivamente algunas filas.

TRUNCATE TABLE alumnos;

IF NOT EXISTS

Evita error si la tabla ya existe.

CREATE TABLE IF NOT EXISTS cursos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL
);

Cómo diseñar una tabla correctamente

Antes de crearla conviene responder:

  • ¿qué entidad representa?
  • ¿cuál es su clave primaria?
  • ¿qué campos son obligatorios?
  • ¿qué datos deben ser únicos?
  • ¿con qué otras tablas se relaciona?

Errores comunes

  • usar VARCHAR para todo;
  • no definir PRIMARY KEY;
  • no usar NOT NULL donde corresponde;
  • crear relaciones con tipos incompatibles;
  • eliminar columnas sin revisar datos.

Ejercicio

Creá tablas categorias y productos con clave primaria, nombre obligatorio, precio, stock, activo y una clave foránea desde productos a categorías.

¿Qué sigue?

INSERT, UPDATE y DELETE para modificar datos.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← Subconsultas en SQL

INSERT, UPDATE y DELETE →

Subconsultas en SQL: IN, EXISTS, consultas anidadas y ejemplos prácticos

Una subconsulta es una consulta que aparece dentro de otra consulta. Se utiliza cuando necesitamos calcular un valor o conjunto intermedio para resolver una consulta principal.

¿Qué problema resuelve?

Supongamos que queremos mostrar alumnos mayores que la edad promedio. Primero necesitamos conocer el promedio y luego compararlo con cada alumno.

Cómo se ejecuta mentalmente una subconsulta

Una buena forma de entenderla es leer desde adentro hacia afuera.

  1. ejecutamos primero la consulta interna;
  2. observamos qué devuelve;
  3. ese resultado se utiliza en la consulta externa.

Subconsulta escalar

Una subconsulta escalar devuelve un único valor.

SELECT nombre, edad
FROM alumnos
WHERE edad > (
    SELECT AVG(edad)
    FROM alumnos
);

Primero se calcula el promedio. Después la consulta externa compara cada edad contra ese valor.

Si el promedio fuera 22, la consulta externa se comportaría conceptualmente como si hubiéramos escrito WHERE edad > 22.

Subconsulta con IN

IN se usa cuando la subconsulta devuelve varios valores.

Si la consulta interna devuelve los ids 1, 3, 5, la condición externa equivale conceptualmente a curso_id IN (1, 3, 5).

SELECT nombre, apellido
FROM alumnos
WHERE curso_id IN (
    SELECT id
    FROM cursos
    WHERE nombre LIKE '%Programación%'
);

NOT IN

Excluye los valores devueltos por la subconsulta.

SELECT nombre, apellido
FROM alumnos
WHERE curso_id NOT IN (
    SELECT id
    FROM cursos
    WHERE nombre = 'Bases de Datos'
);

Hay que tener cuidado con NULL porque puede cambiar el resultado esperado.

Si la subconsulta de un NOT IN devuelve algún NULL, la lógica de tres valores de SQL puede hacer que no obtengamos filas. Para búsquedas de ausencia, NOT EXISTS suele ser más seguro y más expresivo.

EXISTS

EXISTS no necesita devolver un valor concreto: solo pregunta si existe al menos una fila.

Por eso suele escribirse SELECT 1: el valor concreto no importa. EXISTS se fija únicamente en si la subconsulta encontró o no una fila.

SELECT c.nombre
FROM cursos AS c
WHERE EXISTS (
    SELECT 1
    FROM alumnos AS a
    WHERE a.curso_id = c.id
);

NOT EXISTS

Sirve para buscar casos en los que no existe relación.

SELECT c.nombre
FROM cursos AS c
WHERE NOT EXISTS (
    SELECT 1
    FROM alumnos AS a
    WHERE a.curso_id = c.id
);

Subconsulta correlacionada

Una subconsulta correlacionada usa valores de la fila que está procesando la consulta externa.

SELECT a.nombre, a.edad
FROM alumnos AS a
WHERE a.edad > (
    SELECT AVG(a2.edad)
    FROM alumnos AS a2
    WHERE a2.curso_id = a.curso_id
);

Aquí el promedio se calcula para el curso correspondiente a cada alumno.

Eso significa que la subconsulta depende de la fila externa. Conceptualmente puede ejecutarse muchas veces, una por cada alumno evaluado, aunque el optimizador puede aplicar estrategias internas para resolverla mejor.

Subconsulta dentro de SELECT

SELECT c.nombre,
       (
           SELECT COUNT(*)
           FROM alumnos AS a
           WHERE a.curso_id = c.id
       ) AS cantidad_alumnos
FROM cursos AS c;

Subconsulta dentro de FROM

La consulta interna se comporta como una tabla derivada.

SELECT datos.promedio
FROM (
    SELECT AVG(edad) AS promedio
    FROM alumnos
) AS datos;

Cuándo una subconsulta devuelve demasiadas filas

Si usamos un operador que espera un único valor, como =, pero la subconsulta devuelve varias filas, MySQL generará un error.

En esos casos debemos usar un operador adecuado, como IN, o reformular la consulta.

Probar la subconsulta por separado

Cuando una consulta anidada no funciona, una técnica muy útil es ejecutar primero solo la parte interna. Así comprobamos qué columnas y cuántas filas devuelve antes de integrarla.

JOIN o subconsulta

Muchas consultas pueden resolverse de ambas formas. Conviene priorizar claridad, mantenibilidad y rendimiento.

Por ejemplo, “cursos con alumnos” puede resolverse con EXISTS o con INNER JOIN. Ninguna forma es siempre mejor: depende del problema y del plan de ejecución.

Errores comunes

  • usar = cuando la subconsulta devuelve varias filas;
  • usar NOT IN sin pensar en NULL;
  • crear subconsultas innecesariamente complejas;
  • no probar primero la consulta interna por separado.

Ejercicios

  1. alumnos mayores al promedio;
  2. cursos con alumnos;
  3. cursos sin alumnos;
  4. alumnos con la edad máxima;
  5. alumnos mayores al promedio de su curso.

¿Qué sigue?

Creación y modificación de tablas.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← JOIN en SQL

CREATE TABLE y ALTER TABLE →

JOIN en SQL: INNER JOIN, LEFT JOIN y RIGHT JOIN con ejemplos claros

En una base de datos relacional, la información normalmente está separada en varias tablas. JOIN permite volver a reunirla cuando necesitamos consultar datos relacionados.

¿Por qué separar tablas?

Supongamos que cada alumno pertenece a un curso. Si guardamos el nombre del curso dentro de cada alumno, repetimos el mismo texto muchas veces.

Una mejor estructura es tener:

  • tabla alumnos;
  • tabla cursos;
  • en alumnos, una columna curso_id.

Clave primaria y clave foránea

cursos.id identifica un curso. alumnos.curso_id guarda el identificador del curso relacionado.

Eso crea una relación entre ambas tablas.

Tablas de ejemplo

CREATE TABLE cursos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100)
);
CREATE TABLE alumnos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100),
    apellido VARCHAR(100),
    curso_id INT,
    FOREIGN KEY (curso_id) REFERENCES cursos(id)
);

Una relación concreta con datos

Supongamos que tenemos estos cursos:

cursos
id | nombre
1  | Programación
2  | Bases de Datos

Y estos alumnos:

alumnos
id | nombre | curso_id
1  | Ana    | 1
2  | Juan   | 2
3  | Lucía  | NULL

El valor curso_id no es el nombre del curso: es una referencia al id de la tabla cursos. JOIN nos permite reconstruir la información legible.

¿Qué hace JOIN?

JOIN combina filas de dos tablas usando una condición de relación.

La idea mental es: “por cada fila de la tabla principal, buscá la fila relacionada en la otra tabla”.

La cláusula ON

ON alumnos.curso_id = cursos.id

ON indica qué columnas deben coincidir.

En este caso, el valor guardado en alumnos.curso_id debe ser igual al identificador cursos.id.

Si la condición de ON está mal, el resultado también estará mal, aunque la consulta no genere ningún error sintáctico.

INNER JOIN

Devuelve solo las filas que tienen coincidencia en ambas tablas.

SELECT a.nombre, a.apellido, c.nombre AS curso
FROM alumnos AS a
INNER JOIN cursos AS c
    ON a.curso_id = c.id;

Si un alumno no tiene curso asignado, no aparece.

Con los datos de ejemplo, Ana y Juan aparecerían porque tienen curso; Lucía no, porque su curso_id es NULL.

LEFT JOIN

Devuelve todos los registros de la tabla izquierda, aunque no exista coincidencia.

SELECT a.nombre, c.nombre AS curso
FROM alumnos AS a
LEFT JOIN cursos AS c
    ON a.curso_id = c.id;

Si el alumno no tiene curso, la columna curso aparece como NULL.

Por eso LEFT JOIN es muy útil cuando queremos conservar todos los registros principales aunque la relación todavía no exista.

La tabla izquierda y la tabla derecha

En SQL, “izquierda” y “derecha” dependen de cómo está escrita la consulta:

FROM alumnos
LEFT JOIN cursos ...

alumnos es la tabla izquierda y cursos la derecha.

RIGHT JOIN

Conserva todos los registros de la tabla derecha.

SELECT a.nombre, c.nombre AS curso
FROM alumnos AS a
RIGHT JOIN cursos AS c
    ON a.curso_id = c.id;

Alias

Los alias acortan nombres y hacen más legibles las consultas.

FROM alumnos AS a
INNER JOIN cursos AS c

ON y WHERE no cumplen la misma función

ON define cómo se relacionan las tablas. WHERE filtra el resultado.

Esta diferencia es especialmente importante con LEFT JOIN, porque mover una condición de ON a WHERE puede cambiar qué filas sobreviven.

JOIN con WHERE

SELECT a.nombre, c.nombre AS curso
FROM alumnos AS a
INNER JOIN cursos AS c
    ON a.curso_id = c.id
WHERE c.nombre = 'Programación';

Encontrar registros sin relación

LEFT JOIN combinado con IS NULL es muy útil.

SELECT a.nombre
FROM alumnos AS a
LEFT JOIN cursos AS c
    ON a.curso_id = c.id
WHERE c.id IS NULL;

JOIN con agregaciones

SELECT c.nombre, COUNT(a.id) AS cantidad_alumnos
FROM cursos AS c
LEFT JOIN alumnos AS a
    ON c.id = a.curso_id
GROUP BY c.id, c.nombre;

¿Por qué pueden aparecer filas repetidas?

JOIN no “pega una fila con una fila” necesariamente. En una relación uno a muchos, una fila de la tabla izquierda puede producir varias filas en el resultado.

Por ejemplo, si un curso tiene 30 alumnos, una consulta que une cursos con alumnos puede devolver 30 filas para ese curso.

Más de dos tablas

Una consulta puede encadenar varios JOIN. Por ejemplo: alumno → curso → profesor.

INNER, LEFT o RIGHT: ¿cuál usar?

  • INNER JOIN: solo coincidencias.
  • LEFT JOIN: todos los de la izquierda.
  • RIGHT JOIN: todos los de la derecha.

Errores comunes

  • olvidar ON;
  • relacionar columnas incorrectas;
  • confundir LEFT con INNER;
  • no entender los NULL;
  • usar SELECT * cuando varias tablas tienen columnas del mismo nombre.

Ejercicios

  1. mostrar alumno y curso;
  2. mostrar todos los alumnos aunque no tengan curso;
  3. mostrar cursos sin alumnos;
  4. contar alumnos por curso.

¿Qué sigue?

Subconsultas en SQL.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← Funciones de agregación

Subconsultas en SQL →

Funciones de agregación en SQL: COUNT, SUM, AVG, MIN, MAX, GROUP BY y HAVING

Hasta ahora cada fila aparecía de forma individual. Pero muchas veces queremos obtener resúmenes: cuántos registros hay, cuánto suman, cuál es el promedio o cuál es el valor máximo.

¿Qué es una función de agregación?

Una función de agregación recibe varias filas y devuelve un valor resumido.

Las más usadas son:

  • COUNT()
  • SUM()
  • AVG()
  • MIN()
  • MAX()

Una tabla de ejemplo

Imaginemos estos alumnos:

id | nombre | ciudad       | edad | cuota
1  | Ana    | Santa Rosa   | 20   | 10000
2  | Juan   | Santa Rosa   | 24   | 12000
3  | Lucía  | General Pico | 22   | 11000

Las funciones de agregación toman varias filas como estas y producen un resumen.

COUNT()

Cuenta filas. En el ejemplo anterior, COUNT(*) devuelve 3.

SELECT COUNT(*) AS total_alumnos
FROM alumnos;

El alias AS total_alumnos cambia el nombre de la columna del resultado.

COUNT(columna)

A diferencia de COUNT(*), ignora valores NULL.

SELECT COUNT(email)
FROM alumnos;

SUM()

Suma valores numéricos. Si las cuotas son 10000, 12000 y 11000, SUM(cuota) devuelve 33000.

SELECT SUM(cuota) AS total_cuotas
FROM alumnos;

AVG()

Calcula el promedio. Para edades 20, 24 y 22, AVG(edad) devuelve 22.

SELECT AVG(edad) AS edad_promedio
FROM alumnos;

MIN() y MAX()

Devuelven el menor y mayor valor.

SELECT MIN(edad) AS menor_edad,
       MAX(edad) AS mayor_edad
FROM alumnos;

AS: dar nombre al resultado agregado

Las expresiones como COUNT(*) o AVG(edad) pueden quedar con nombres poco cómodos. Un alias mejora la lectura:

SELECT AVG(edad) AS edad_promedio
FROM alumnos;

GROUP BY

GROUP BY divide los registros en grupos y aplica las funciones a cada grupo.

SELECT ciudad, COUNT(*) AS cantidad
FROM alumnos
GROUP BY ciudad;

Ahora ya no obtenemos un total general, sino un total por cada ciudad.

Con los datos del ejemplo, el resultado sería conceptualmente:

ciudad       | cantidad
Santa Rosa   | 2
General Pico | 1

La clave para entender GROUP BY es preguntarse: “¿por qué dimensión quiero resumir los datos?”. Puede ser por ciudad, curso, categoría, mes, cliente, etc.

Promedio por grupo

SELECT ciudad, AVG(edad) AS edad_promedio
FROM alumnos
GROUP BY ciudad;

Qué columnas pueden aparecer junto a GROUP BY

Cuando agrupamos, cada fila del resultado representa un grupo. Por eso, las columnas seleccionadas deberían ser:

  • columnas incluidas en GROUP BY; o
  • resultados de funciones de agregación.

Por ejemplo, esta consulta tiene sentido porque ciudad define el grupo y COUNT(*) resume sus filas.

WHERE antes de agrupar

WHERE elimina filas antes de formar los grupos.

SELECT ciudad, COUNT(*) AS cantidad
FROM alumnos
WHERE edad >= 22
GROUP BY ciudad;

HAVING

HAVING filtra grupos ya calculados.

SELECT ciudad, COUNT(*) AS cantidad
FROM alumnos
GROUP BY ciudad
HAVING COUNT(*) > 1;

El orden lógico: WHERE → GROUP BY → HAVING

Una forma sencilla de pensarlo es:

  1. WHERE decide qué filas entran al análisis;
  2. GROUP BY forma los grupos;
  3. las funciones calculan sus valores;
  4. HAVING decide qué grupos permanecen.

WHERE vs HAVING

  • WHERE: filtra filas antes de agrupar.
  • HAVING: filtra grupos después de agrupar.

COUNT(DISTINCT …)

Cuenta valores distintos.

SELECT COUNT(DISTINCT ciudad) AS cantidad_ciudades
FROM alumnos;

NULL en funciones de agregación

Funciones como AVG(), SUM(), MIN(), MAX() y COUNT(columna) normalmente ignoran NULL.

COUNT(*), en cambio, cuenta filas aunque alguna de sus columnas contenga NULL.

Esto importa mucho en promedios: un NULL no se interpreta como cero, simplemente no participa del cálculo.

Ejemplo completo

SELECT ciudad,
       COUNT(*) AS cantidad,
       AVG(edad) AS edad_promedio
FROM alumnos
WHERE edad > 20
GROUP BY ciudad
HAVING COUNT(*) >= 2
ORDER BY edad_promedio DESC;

Errores comunes

  • confundir WHERE con HAVING;
  • olvidar GROUP BY;
  • suponer que COUNT(columna) cuenta NULL;
  • usar SUM() sobre campos que no representan cantidades.

Ejercicios

  1. contar alumnos;
  2. calcular edad promedio;
  3. obtener edad mínima y máxima;
  4. contar por ciudad;
  5. mostrar ciudades con más de un alumno.

¿Qué sigue?

JOIN y relaciones entre tablas.

¿Te sirvió esta guía? ☕

Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, proyectos y guías gratuitas, podés colaborar mediante:

☕ Apoyar con Mercado Pago

🌎 Apoyar con PayPal


Ruta SQL / MySQL

Ver todos los contenidos de SQL / MySQL

← SELECT en SQL

JOIN en SQL →