Funciones en SQL
En esta lección
Los datos no siempre están guardados como los quieres mostrar. Puede que necesites el nombre en mayúsculas, solo el apellido, el precio redondeado, el mes de un pedido o cuántos días han pasado desde una fecha. Para eso SQL tiene funciones: reciben uno o varios valores y devuelven otro. Se usan en el SELECT, en el WHERE, en el ORDER BY… en cualquier sitio donde iría un valor.
En esta lección verás las más útiles, agrupadas en texto, números y fechas. Es la parte de SQL que más cambia entre sistemas, así que te indicamos las diferencias entre MySQL, PostgreSQL y SQLite.
Nota: estas funciones trabajan fila a fila: devuelven un resultado por cada fila. Las que resumen muchas filas en un valor (
COUNT,SUM,AVG…) son las funciones de agregación.
Funciones de texto
Mayúsculas y minúsculas: UPPER y LOWER
SELECT nombre, UPPER(ciudad) AS mayus, LOWER(ciudad) AS minus
FROM clientes;
| nombre | mayus | minus |
|---|---|---|
| Ana García | SANTANDER | santander |
| Luis Pérez | BILBAO | bilbao |
| Marta Ruiz | SANTANDER | santander |
| Pablo Díaz | MADRID | madrid |
| Eva Martín | MADRID | madrid |
Un uso muy habitual es comparar sin que importen las mayúsculas: WHERE LOWER(email) = 'ana@correo.es'.
Cuidado: SQLite solo cambia las letras sin tilde:
UPPER('García')devuelve'GARCíA'. MySQL y PostgreSQL devuelven'GARCÍA'.
Longitud: LENGTH y CHAR_LENGTH
SELECT ciudad, CHAR_LENGTH(ciudad) AS letras
FROM clientes;
| ciudad | letras |
|---|---|
| Santander | 9 |
| Bilbao | 6 |
| Santander | 9 |
| Madrid | 6 |
| Madrid | 6 |
Aquí hay una trampa. En MySQL, LENGTH cuenta bytes, no letras, y con utf8mb4 una letra con tilde ocupa 2 bytes: LENGTH('Ana García') da 11, y CHAR_LENGTH('Ana García'), 10. En PostgreSQL y SQLite, LENGTH ya cuenta caracteres y da 10. Si trabajas con MySQL, usa CHAR_LENGTH para contar letras.
Extraer una parte: SUBSTRING y SUBSTR
SUBSTRING(texto, inicio, cantidad) devuelve cantidad caracteres a partir de la posición inicio. Las posiciones empiezan en 1, no en 0 como en muchos lenguajes de programación:
SELECT nombre, SUBSTRING(nombre, 1, 3) AS inicio
FROM clientes;
| nombre | inicio |
|---|---|
| Ana García | Ana |
| Luis Pérez | Lui |
| Marta Ruiz | Mar |
| Pablo Díaz | Pab |
| Eva Martín | Eva |
- Si omites la cantidad, llega hasta el final:
SUBSTRING('Santander', 4)→'tander'. - Con un inicio negativo cuenta desde el final (MySQL y SQLite):
SUBSTRING('Santander', -3)→'der'. SUBSTRes un sinónimo en MySQL, SQLite, PostgreSQL y Oracle.LEFT(texto, n)yRIGHT(texto, n)existen en MySQL y PostgreSQL, pero no en SQLite.
Buscar dentro de un texto: INSTR y LOCATE
INSTR(texto, buscado) devuelve la posición donde aparece buscado, o 0 si no está:
SELECT nombre, INSTR(nombre, ' ') AS espacio
FROM clientes;
| nombre | espacio |
|---|---|
| Ana García | 4 |
| Luis Pérez | 5 |
| Marta Ruiz | 6 |
| Pablo Díaz | 6 |
| Eva Martín | 4 |
Combinada con SUBSTRING, permite separar el nombre del apellido: todo lo que va antes del espacio y todo lo que va después.
SELECT nombre,
SUBSTRING(nombre, 1, INSTR(nombre, ' ') - 1) AS nombre_pila,
SUBSTRING(nombre, INSTR(nombre, ' ') + 1) AS apellido
FROM clientes;
| nombre | nombre_pila | apellido |
|---|---|---|
| Ana García | Ana | García |
| Luis Pérez | Luis | Pérez |
| Marta Ruiz | Marta | Ruiz |
| Pablo Díaz | Pablo | Díaz |
| Eva Martín | Eva | Martín |
| Sistema | Posición de ' ' en nombre |
|---|---|
| MySQL | INSTR(nombre, ' ') o LOCATE(' ', nombre) (ojo: al revés) |
| PostgreSQL | POSITION(' ' IN nombre) o STRPOS(nombre, ' ') |
| SQLite | INSTR(nombre, ' ') |
POSITION(' ' IN nombre) es la forma estándar y también funciona en MySQL.
Limpiar espacios: TRIM
TRIM quita los espacios del principio y del final; LTRIM, solo los de la izquierda; y RTRIM, solo los de la derecha. Es imprescindible con datos que vienen de formularios, donde un espacio de más hace que 'Madrid ' no sea igual a 'Madrid':
SELECT '[' || TRIM(' hola ') || ']' AS t,
'[' || LTRIM(' hola ') || ']' AS l,
'[' || RTRIM(' hola ') || ']' AS r;
| t | l | r |
|---|---|---|
| [hola] | [hola ] | [ hola] |
(Los corchetes solo están para que veas dónde quedan los espacios. En MySQL, usa CONCAT en lugar de ||).
Reemplazar: REPLACE
REPLACE(texto, buscado, nuevo) cambia todas las apariciones:
SELECT REPLACE('600-111-222', '-', '') AS telefono; -- 600111222
Juntar textos: CONCAT y ||
Ya lo viste en SELECT: CONCAT(a, b, ...) en MySQL y a || b en PostgreSQL y SQLite. Combinando varias funciones puedes generar, por ejemplo, un nombre de usuario con la inicial y el apellido:
SELECT nombre,
LOWER(CONCAT(SUBSTRING(nombre, 1, 1),
SUBSTRING(nombre, INSTR(nombre, ' ') + 1))) AS usuario
FROM clientes;
| nombre | usuario |
|---|---|
| Ana García | agarcía |
| Luis Pérez | lpérez |
| Marta Ruiz | mruiz |
| Pablo Díaz | pdíaz |
| Eva Martín | emartín |
Las funciones se leen de dentro hacia fuera: primero se extraen las partes, luego se juntan y al final se pasan a minúsculas.
Funciones numéricas
Redondear: ROUND, CEIL y FLOOR
ROUND(x, d)redondea addecimales (0 si no lo indicas). Con un 5, redondea hacia arriba:ROUND(12.5)→ 13.CEIL(x)(oCEILING) redondea hacia arriba al entero siguiente.FLOOR(x)redondea hacia abajo.
SELECT id, importe,
ROUND(importe * 0.9, 2) AS con_descuento,
CEIL(importe) AS arriba,
FLOOR(importe) AS abajo
FROM pedidos;
| id | importe | con_descuento | arriba | abajo |
|---|---|---|---|---|
| 1 | 45.00 | 40.50 | 45 | 45 |
| 2 | 120.50 | 108.45 | 121 | 120 |
| 3 | 12.99 | 11.69 | 13 | 12 |
| 4 | 60.00 | 54.00 | 60 | 60 |
| 5 | 33.25 | 29.93 | 34 | 33 |
CEIL es útil para calcular cosas que no pueden ir en fracciones: si caben 6 botellas por caja y hay 20, necesitas CEIL(20 / 6.0) = 4 cajas.
Valor absoluto: ABS
ABS(x) quita el signo: ABS(-7) → 7. Sirve para medir distancias, como en la lección de ORDER BY: ORDER BY ABS(edad - 30).
Resto de una división: MOD y %
MOD(a, b) o a % b devuelven el resto de dividir a entre b. El caso típico es saber si un número es par:
SELECT nombre, edad, edad % 2 AS resto
FROM clientes;
| nombre | edad | resto |
|---|---|---|
| Ana García | 34 | 0 |
| Luis Pérez | 22 | 0 |
| Marta Ruiz | 45 | 1 |
| Pablo Díaz | 17 | 1 |
| Eva Martín | 29 | 1 |
Resto 0: par. Resto 1: impar.
La división entera: una diferencia importante
¿Cuánto es 17 / 5? Depende del sistema:
| Operación | MySQL | PostgreSQL | SQLite |
|---|---|---|---|
17 / 5 | 3.4000 | 3 | 3 |
17 / 5.0 | 3.4000 | 3.4 | 3.4 |
| División entera | 17 DIV 5 → 3 | 17 / 5 → 3 | 17 / 5 → 3 |
En PostgreSQL y SQLite, entero entre entero da entero (se pierden los decimales). MySQL siempre da decimales con / y usa DIV para la división entera. Si quieres decimales en todos los sistemas, haz que uno de los números tenga decimales (17 / 5.0) o usa CAST.
Esto afecta a cálculos como la media de dos columnas enteras o un porcentaje: aprobados / total * 100 puede dar 0 en PostgreSQL. Escribe aprobados * 100.0 / total.
Funciones de fecha
Aquí es donde más cambian los sistemas. Esta tabla recoge las operaciones más comunes:
| Operación | MySQL | PostgreSQL | SQLite |
|---|---|---|---|
| Fecha de hoy | CURDATE() | CURRENT_DATE | date('now') |
| Fecha y hora actual | NOW() | NOW() | datetime('now') |
| Año | YEAR(fecha) | EXTRACT(YEAR FROM fecha) | strftime('%Y', fecha) |
| Mes | MONTH(fecha) | EXTRACT(MONTH FROM fecha) | strftime('%m', fecha) |
| Sumar 30 días | DATE_ADD(fecha, INTERVAL 30 DAY) | fecha + INTERVAL '30 days' | date(fecha, '+30 days') |
| Días entre dos fechas | DATEDIFF(fin, inicio) | fin - inicio | julianday(fin) - julianday(inicio) |
| Formatear | DATE_FORMAT(fecha, '%d/%m/%Y') | TO_CHAR(fecha, 'DD/MM/YYYY') | strftime('%d/%m/%Y', fecha) |
Consejo:
CURRENT_DATEyCURRENT_TIMESTAMPson estándar y funcionan en los tres.EXTRACT(YEAR FROM fecha)también es estándar y MySQL lo admite.
Año, mes y día
SELECT id, fecha, YEAR(fecha) AS anio, MONTH(fecha) AS mes, DAY(fecha) AS dia
FROM pedidos;
| id | fecha | anio | mes | dia |
|---|---|---|---|---|
| 1 | 2026-09-01 | 2026 | 9 | 1 |
| 2 | 2026-09-03 | 2026 | 9 | 3 |
| 3 | 2026-09-10 | 2026 | 9 | 10 |
| 4 | 2026-09-12 | 2026 | 9 | 12 |
| 5 | 2026-09-15 | 2026 | 9 | 15 |
En SQLite, strftime('%m', fecha) devuelve texto con cero delante ('09'), no el número 9. Tenlo en cuenta al comparar: strftime('%m', fecha) = '09'.
Estas funciones son la base para agrupar ventas por mes, como verás en GROUP BY.
Sumar días y calcular diferencias
Supón que cada pedido se puede devolver durante 30 días. ¿Hasta cuándo?
SELECT id, fecha, DATE_ADD(fecha, INTERVAL 30 DAY) AS devolver_hasta
FROM pedidos;
| id | fecha | devolver_hasta |
|---|---|---|
| 1 | 2026-09-01 | 2026-10-01 |
| 2 | 2026-09-03 | 2026-10-03 |
| 3 | 2026-09-10 | 2026-10-10 |
| 4 | 2026-09-12 | 2026-10-12 |
| 5 | 2026-09-15 | 2026-10-15 |
Y ¿cuántos días han pasado desde cada pedido hasta el 30 de septiembre?
SELECT id, fecha, DATEDIFF('2026-09-30', fecha) AS dias
FROM pedidos;
| id | fecha | dias |
|---|---|---|
| 1 | 2026-09-01 | 29 |
| 2 | 2026-09-03 | 27 |
| 3 | 2026-09-10 | 20 |
| 4 | 2026-09-12 | 18 |
| 5 | 2026-09-15 | 15 |
En una aplicación real usarías CURDATE() en lugar de una fecha fija, y el resultado cambiaría cada día. DATEDIFF recibe primero la fecha final: si las cambias de orden, obtienes números negativos.
Formatear fechas
La base de datos guarda las fechas como AAAA-MM-DD. Para mostrarlas al estilo español:
SELECT id, DATE_FORMAT(fecha, '%d/%m/%Y') AS fecha_es
FROM pedidos;
| id | fecha_es |
|---|---|
| 1 | 01/09/2026 |
| 2 | 03/09/2026 |
| 3 | 10/09/2026 |
| 4 | 12/09/2026 |
| 5 | 15/09/2026 |
El resultado ya es texto: sirve para mostrarlo, pero no para ordenar ni comparar fechas ('10/09/2026' va antes que '15/08/2026' alfabéticamente). Ordena siempre por la columna original.
Convertir tipos: CAST
CAST(valor AS tipo) convierte un valor a otro tipo. Sirve para forzar una división con decimales, convertir un texto en número o un número en texto:
SELECT CAST('42' AS DECIMAL(10, 2)) + 1; -- 43.00
SELECT CAST(17 AS DECIMAL(10, 2)) / 5; -- 3.4 en todos los sistemas
SELECT CONCAT(CAST(edad AS CHAR), ' años') FROM clientes; -- '34 años', ...
Los nombres de los tipos cambian un poco: para convertir a entero, MySQL usa SIGNED y PostgreSQL y SQLite INTEGER; para texto, MySQL usa CHAR y los otros TEXT. DECIMAL(p, s) funciona en todos.
Cuidado: al pasar un decimal a entero, MySQL y PostgreSQL redondean (
12.99→ 13) y SQLite trunca (12.99→ 12). Si te importa el resultado, usaROUNDoFLOOR, que hacen lo mismo en todas partes.
Funciones en el WHERE: un aviso de rendimiento
Puedes usar funciones para filtrar:
SELECT id, fecha FROM pedidos
WHERE DAY(fecha) > 10; -- pedidos 4 y 5
Funciona, pero hay un coste: si aplicas una función a una columna del WHERE, la base de datos no puede usar el índice de esa columna y tiene que calcularla fila a fila. Para rangos de fechas, es mejor comparar la columna tal cual: WHERE fecha >= '2026-09-01' AND fecha < '2026-10-01' en lugar de WHERE MONTH(fecha) = 9.
Errores frecuentes
- Empezar a contar posiciones en 0: en SQL, el primer carácter es el 1.
- Usar
LENGTHen MySQL para contar letras: con tildes y eñes cuenta bytes. UsaCHAR_LENGTH. - Dividir enteros en PostgreSQL o SQLite y perder los decimales:
5 / 2da 2. - Ordenar por una fecha formateada (
DATE_FORMAT): se ordena como texto. - Copiar funciones de un sistema a otro:
DATE_ADD,DATEDIFFoIFNULLno existen en PostgreSQL. Mira la tabla de equivalencias. - Olvidar que una función con
NULLsuele devolverNULL:UPPER(NULL)esNULL. Repasa valores NULL.
Resumen
| Quieres… | MySQL | Estándar / otros |
|---|---|---|
| Mayúsculas / minúsculas | UPPER(x), LOWER(x) | Igual |
| Número de letras | CHAR_LENGTH(x) | LENGTH(x) |
| Parte de un texto | SUBSTRING(x, inicio, n) | Igual (SUBSTR en SQLite) |
| Posición de un texto | INSTR(x, 'a') | POSITION('a' IN x) |
| Quitar espacios | TRIM(x) | Igual |
| Reemplazar | REPLACE(x, 'a', 'b') | Igual |
| Redondear | ROUND(x, 2), CEIL(x), FLOOR(x) | Igual |
| Resto | x % 2, MOD(x, 2) | Igual |
| División entera | a DIV b | a / b con enteros |
| Año de una fecha | YEAR(f) | EXTRACT(YEAR FROM f) |
| Sumar días | DATE_ADD(f, INTERVAL 30 DAY) | Ver tabla de fechas |
| Diferencia en días | DATEDIFF(fin, inicio) | Ver tabla de fechas |
| Convertir tipo | CAST(x AS DECIMAL(10, 2)) | Igual |
En la siguiente lección aprenderás a escribir condiciones dentro de las consultas con CASE.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.