# Introducción a SQL desde cero

**Categoría:** Datos y bases de datos · **Nivel:** Inicial · **Lectura:** 13 min
**Publicada:** 2026-07-27 · **Actualizada:** 2026-07-27 · **Autoría:** Underc0de
**Versión HTML (canónica):** https://underc0de.org/guias/bases-de-datos/introduccion-a-sql-desde-cero/

Las consultas que resuelven el 90 % del trabajo: SELECT, WHERE, ORDER BY, INSERT, UPDATE, DELETE, JOIN, GROUP BY y agregados, con el orden real de ejecución.

## Respuesta rápida

**SQL** es el lenguaje con el que se consultan y modifican las bases de datos relacionales. Su nombre formal como estándar es **ISO/IEC 9075**, y la revisión vigente es **SQL:2023**. Es declarativo: describís *qué* querés obtener, no cómo buscarlo. Con seis instrucciones —`SELECT`, `WHERE`, `JOIN`, `GROUP BY`, `ORDER BY` y las de escritura `INSERT`, `UPDATE` y `DELETE`— se cubre casi todo el trabajo real. La clave para dejar de pelearse con los errores es entender que **una consulta no se ejecuta en el orden en que se escribe**.

## Qué es SQL y qué tan estándar es

**SQL** —*Structured Query Language*— es el lenguaje para consultar y modificar bases de datos relacionales. Su característica más importante es que es **declarativo**: describís el resultado que querés, no los pasos para conseguirlo. Le pedís «los clientes de Córdoba ordenados por apellido» y el motor decide cómo buscarlos.

Sobre cuán estándar es, la documentación de PostgreSQL da la respuesta precisa: «el nombre formal del estándar SQL es **ISO/IEC 9075 *Database Language SQL***», y la actualización más reciente apareció en **2023**, conocida como **SQL:2023**. Las versiones anteriores fueron SQL:2016, SQL:2011, SQL:2008, SQL:2006, SQL:2003, SQL:1999 y SQL-92, y cada una reemplaza a la anterior.

Pero hay un matiz honesto: **ningún motor implementa el estándar completo**, y cada uno agrega cosas propias. Eso da lugar a los llamados *dialectos*. En la práctica, lo que vas a aprender acá funciona igual en MySQL, PostgreSQL, SQL Server, Oracle y SQLite. Lo que cambia son las funciones de fecha y de texto, la forma de limitar resultados y algunos tipos de dato.

> **Los ejemplos de esta guía** usan las tablas `clientes`, `pedidos` y `productos` que aparecen en [la guía del modelo relacional](../bases-de-datos-relacionales-y-tablas/index.md). Si no la leíste, alcanza con saber que `pedidos` tiene una columna `cliente_id` que apunta al `id` de `clientes`.

## Tu primera consulta

Toda consulta de lectura tiene la misma forma mínima: qué columnas querés y de dónde.

```sql
-- Todas las columnas de todas las filas
SELECT * FROM clientes;

-- Mejor: solo las columnas que necesitás
SELECT nombre, correo FROM clientes;

-- Con un nombre distinto en el resultado (alias)
SELECT nombre AS cliente, correo AS email FROM clientes;
```

El `SELECT *` es cómodo para explorar y una mala costumbre para el código que queda: trae columnas que no necesitás, y el día que alguien agrega una columna pesada tu consulta se vuelve lenta sin que nadie la haya tocado.

Dos convenciones que hacen el código legible: las palabras del lenguaje en **mayúsculas** y los nombres de tus tablas y columnas en minúsculas, y cada consulta termina en **punto y coma**.

## Filtrar con WHERE

`WHERE` decide qué filas entran en el resultado. Es donde vas a pasar la mayor parte del tiempo.

```sql
SELECT producto, precio FROM pedidos
WHERE  precio > 20000;

-- Combinar condiciones. Los paréntesis evitan sorpresas.
SELECT * FROM pedidos
WHERE  (precio > 20000 OR cliente_id = 1)
  AND  fecha >= '2026-01-01';

-- Operadores que conviene conocer de entrada
WHERE precio  BETWEEN 1000 AND 5000      -- rango, incluye los extremos
WHERE cliente_id IN (1, 4, 7)            -- alguno de esos valores
WHERE producto LIKE 'Teclado%'           -- empieza con «Teclado»
WHERE telefono IS NULL                   -- sin valor cargado
WHERE NOT producto = 'Mouse'             -- negación
```

> **Los dos errores clásicos del WHERE:** el primero: `= NULL` nunca se cumple, porque `NULL` no es igual a nada, ni a sí mismo. Va `IS NULL`. El segundo: mezclar `AND` y `OR` sin paréntesis, porque `AND` tiene prioridad y la consulta hace algo distinto de lo que leés. Poné los paréntesis siempre, aunque parezcan innecesarios.

Para ordenar y recortar el resultado se agregan al final:

```sql
SELECT   producto, precio FROM pedidos
ORDER BY precio DESC, producto ASC
LIMIT    10;
```

Sobre `LIMIT`: es la instrucción con más variación entre motores. MySQL, PostgreSQL y SQLite usan `LIMIT`; SQL Server usa `TOP`; Oracle usa `FETCH FIRST`. Es el ejemplo más claro de lo que significa «dialecto».

## El orden real de ejecución

Esta sección es la que más frustración ahorra, y casi nunca aparece en los tutoriales de introducción. **Una consulta SQL no se ejecuta en el orden en que se escribe.**

![Dos columnas comparadas. A la izquierda, el orden en que se escribe una consulta: SELECT, FROM, JOIN, WHERE, GROUP BY, HAVING, ORDER BY y LIMIT. A la derecha, el orden en que el motor la ejecuta: primero FROM y JOIN, después WHERE, luego GROUP BY, después HAVING, en quinto lugar SELECT, después ORDER BY y finalmente LIMIT. Debajo se explican las dos consecuencias prácticas: por qué un alias del SELECT no sirve en el WHERE, y por qué filtrar por un agregado exige HAVING.](../../assets/img/guias/introduccion-a-sql-desde-cero-orden.svg)

*El `SELECT` se ejecuta quinto, no primero. De ese solo hecho salen los dos errores que más confunden al empezar.*

La consecuencia más visible es esta: si creás un alias en el `SELECT`, no podés usarlo en el `WHERE`, porque cuando el `WHERE` corre ese nombre todavía no existe.

```sql
-- ✕ Falla: «total» no existe todavía cuando corre el WHERE
SELECT precio * cantidad AS total FROM pedido_producto
WHERE  total > 50000;

-- ✓ Funciona: se repite la expresión completa
SELECT precio * cantidad AS total FROM pedido_producto
WHERE  precio * cantidad > 50000;

-- ✓ También funciona: ORDER BY sí puede usar el alias, porque corre después
SELECT   precio * cantidad AS total FROM pedido_producto
ORDER BY total DESC;
```

Ese último ejemplo es la prueba de que el orden importa: el mismo alias falla en el `WHERE` y funciona en el `ORDER BY`, porque uno corre antes del `SELECT` y el otro después.

## Unir tablas con JOIN

Como los datos están repartidos en varias tablas, hace falta una forma de verlos juntos. Eso es `JOIN`: se indica con qué tabla unir y por qué columnas se corresponden.

```sql
SELECT c.nombre, p.producto, p.precio
FROM   pedidos  p
JOIN   clientes c ON p.cliente_id = c.id
WHERE  p.precio > 20000;
```

Las letras `p` y `c` son alias de tabla: ahorran escritura y, más importante, dejan claro de qué tabla viene cada columna cuando ambas tienen una columna con el mismo nombre.

Hay dos tipos que cubren casi todo, y elegir mal entre ellos es una causa muy frecuente de informes con datos faltantes:

| Tipo | Qué devuelve | Cuándo usarlo |
|---|---|---|
| `INNER JOIN` (o solo `JOIN`) | Solo las filas con coincidencia en las dos tablas | Cuando te interesan únicamente los casos completos: pedidos que sí tienen cliente. |
| `LEFT JOIN` | Todas las de la izquierda; las de la derecha con `NULL` si no hay coincidencia | Cuando querés incluir los casos sin pareja: todos los clientes, incluso los que nunca compraron. |

> **La prueba que revela el error:** si tu informe «tiene menos filas de las que debería», casi siempre es un `INNER JOIN` donde correspondía un `LEFT JOIN`. El `INNER` descarta silenciosamente todo lo que no tiene pareja: los clientes sin pedidos, los productos sin ventas, las categorías vacías. No da error, simplemente no aparecen.

## Agrupar, contar y sumar

Las **funciones de agregado** calculan un valor a partir de muchas filas: `COUNT` cuenta, `SUM` suma, `AVG` promedia, `MIN` y `MAX` buscan extremos.

```sql
-- Sobre toda la tabla: una sola fila de resultado
SELECT COUNT(*) AS cantidad, SUM(precio) AS facturado
FROM   pedidos;

-- Por grupo: una fila por cliente
SELECT   c.nombre,
         COUNT(p.id)    AS pedidos,
         SUM(p.precio)  AS total
FROM     clientes c
LEFT JOIN pedidos p ON p.cliente_id = c.id
GROUP BY c.id, c.nombre
HAVING   COUNT(p.id) >= 3          -- filtra grupos, no filas
ORDER BY total DESC;
```

Tres cosas de esa consulta que vale entender bien:

- **`GROUP BY` define la unidad del resultado.** Agrupando por cliente, cada fila del resultado es un cliente. Toda columna del `SELECT` que no esté dentro de una función de agregado tiene que estar en el `GROUP BY`.
- **`HAVING` filtra grupos; `WHERE` filtra filas.** Para quedarte con los clientes que tienen tres pedidos o más hace falta que los grupos ya existan, y el `WHERE` corre antes de que existan.
- **`COUNT(*)` y `COUNT(columna)` no son lo mismo.** El primero cuenta filas; el segundo cuenta solo las que tienen valor no nulo en esa columna. En el ejemplo, `COUNT(p.id)` con un `LEFT JOIN` devuelve **0** para un cliente sin pedidos, que es lo correcto. `COUNT(*)` devolvería 1, contando la fila con nulos que genera el `LEFT JOIN`.

## Insertar, actualizar y borrar

Tres instrucciones, y una precaución que vale más que las tres.

```sql
-- Insertar. Nombrá siempre las columnas: si mañana se agrega una,
-- la instrucción sigue funcionando.
INSERT INTO clientes (nombre, correo)
VALUES ('Ana López', 'ana@correo.test');

-- Actualizar. El WHERE no es opcional en la práctica.
UPDATE clientes
SET    correo = 'ana.lopez@correo.test'
WHERE  id = 1;

-- Borrar.
DELETE FROM pedidos
WHERE       id = 42;
```

> **La costumbre que evita el accidente:** si te olvidás el `WHERE`, la instrucción se aplica a **todas las filas**. Un `DELETE FROM clientes` sin `WHERE` borra la tabla entera y no hay deshacer. La práctica que conviene volver reflejo: escribí primero la consulta como `SELECT` con el mismo `WHERE`, mirá qué filas devuelve, y solo entonces cambiá `SELECT` por `UPDATE` o `DELETE`. En una base con datos reales, además, hacelo dentro de una transacción.

```sql
-- 1. Verificar qué se va a afectar
SELECT * FROM pedidos WHERE fecha < '2020-01-01';

-- 2. Recién entonces, y dentro de una transacción
START TRANSACTION;
DELETE FROM pedidos WHERE fecha < '2020-01-01';
-- Revisás el resultado y decidís:
COMMIT;    -- confirmar
-- ROLLBACK; -- o deshacer todo
```

## Dónde practicar con datos reales

Leer SQL no alcanza: hay que escribirlo sobre datos que no controlás. Y practicar con tres filas que cargaste vos es engañoso, porque los datos reales nunca son tan prolijos.

La solución son las **bases de muestra**. La más conocida del mundo MySQL es **Sakila**, que según su documentación oficial fue desarrollada inicialmente por Mike Hillyer, del equipo de documentación de MySQL AB, y está pensada «para proveer un esquema estándar que se pueda usar en ejemplos de libros, tutoriales, artículos y muestras». Modela un videoclub: películas, actores, clientes, alquileres y pagos, con la cantidad de tablas suficiente para que los `JOIN` tengan sentido.

En el foro de Underc0de, *DtxdF* la recomendó en 2021 con un argumento que sigue valiendo: es muy útil cuando estás empezando o cuando necesitás probar características del motor sobre una estructura estándar.

> **Un plan de práctica de una semana:** instalá MySQL siguiendo [la guía de esta serie](../mysql-instalacion-y-primeros-pasos/index.md), cargá Sakila, y proponete responder diez preguntas de negocio por día: cuáles son las diez películas más alquiladas, qué categoría factura más, qué clientes no alquilaron nunca. Ese último tipo de pregunta —los que *no* hicieron algo— es el que te obliga a dominar el `LEFT JOIN`.

## Errores frecuentes

- **`UPDATE` o `DELETE` sin `WHERE`.** El más caro de todos. Verificá con un `SELECT` antes, siempre.
- **Usar `= NULL`.** Nunca se cumple. Va `IS NULL`.
- **Mezclar `AND` y `OR` sin paréntesis.** `AND` tiene prioridad y la consulta hace otra cosa.
- **`INNER JOIN` donde correspondía `LEFT JOIN`.** Esconde en silencio las filas sin pareja.
- **Usar un alias del `SELECT` en el `WHERE`.** Todavía no existe: el `SELECT` corre después.
- **Filtrar por un agregado en el `WHERE`.** Va en `HAVING`, cuando los grupos ya existen.
- **Confundir `COUNT(*)` con `COUNT(columna)`.** Con un `LEFT JOIN` dan resultados distintos, y uno de los dos está mal.
- **Dejar `SELECT *` en el código que queda.** Trae columnas que no usás y se vuelve lento cuando alguien agrega una.
- **Suponer un orden sin `ORDER BY`.** Funciona hasta que deja de funcionar, sin aviso.

## Preguntas frecuentes

**¿SQL es igual en todos los motores?**
El núcleo sí, los detalles no. SQL es un estándar internacional cuyo nombre formal es ISO/IEC 9075 «Database Language SQL», y la revisión más reciente es de 2023, conocida como SQL:2023. Pero la documentación de PostgreSQL aclara que ningún motor implementa el estándar completo, así que cada uno tiene su dialecto. En la práctica, todo lo que se explica en esta guía funciona igual en MySQL, PostgreSQL, SQL Server, Oracle y SQLite. Lo que cambia son las funciones de fecha y texto, la forma de limitar resultados y algunos tipos de dato.

**¿Por qué no puedo usar un alias en el WHERE?**
Porque el WHERE se ejecuta antes que el SELECT. Una consulta SQL no se ejecuta en el orden en que se escribe: primero se resuelve FROM y JOIN, después WHERE, después GROUP BY y HAVING, y recién en quinto lugar el SELECT. Cuando el motor evalúa el WHERE, el nombre que creaste en el SELECT todavía no existe. La solución es repetir la expresión completa en el WHERE, o envolver la consulta en otra que sí pueda referirse al alias.

**¿Cuál es la diferencia entre WHERE y HAVING?**
WHERE filtra filas individuales y HAVING filtra grupos ya formados. La diferencia sale del orden de ejecución: WHERE corre antes del GROUP BY, cuando los grupos todavía no existen, así que no puede usar el resultado de un COUNT o un SUM. HAVING corre después, cuando los grupos ya están armados. La regla práctica: si tu condición usa una función de agregado, va en HAVING; si no, va en WHERE, que además es más eficiente porque descarta filas antes de agrupar.

**¿Cuál es la diferencia entre INNER JOIN y LEFT JOIN?**
INNER JOIN devuelve solo las filas que tienen coincidencia en las dos tablas. LEFT JOIN devuelve todas las filas de la tabla de la izquierda, y para las que no tienen coincidencia completa las columnas de la derecha con NULL. La consecuencia práctica es importante: si querés listar todos los clientes con la cantidad de pedidos que hizo cada uno, un INNER JOIN te va a esconder los clientes sin pedidos, mientras el LEFT JOIN los muestra con cero. Elegir mal el tipo de JOIN es una de las causas más comunes de informes con datos faltantes.

**¿Es peligroso ejecutar un UPDATE o un DELETE?**
Puede ser, y por un motivo muy concreto: si te olvidás el WHERE, la instrucción se aplica a todas las filas de la tabla. Un DELETE FROM clientes sin WHERE borra todos los clientes, y no hay deshacer. La costumbre que evita el accidente es escribir siempre primero la consulta como un SELECT con el mismo WHERE, verificar que devuelva exactamente las filas que querés afectar, y solo entonces cambiar SELECT por UPDATE o DELETE. En una base de producción, además, conviene hacerlo dentro de una transacción.

**¿Dónde puedo practicar SQL con datos reales?**
Con una base de muestra. La más conocida en el mundo MySQL es Sakila, que Oracle publica como esquema estándar para ejemplos en libros, tutoriales y artículos, y que además incluye vistas, procedimientos almacenados y disparadores. En el foro de Underc0de, DtxdF la recomendó en 2021 justamente para quien está empezando. Practicar sobre una base ya poblada es mucho mejor que inventar tres filas: te obliga a enfrentarte a datos que no son perfectos.

## Fuentes

Documentación oficial y material de la comunidad consultados para esta guía. Fecha de consulta: 27 de julio de 2026.

1. **PostgreSQL.** [SQL Conformance](https://www.postgresql.org/docs/current/features.html). Nombre formal del estándar ISO/IEC 9075, la revisión SQL:2023, el listado de versiones anteriores y la aclaración de que ningún motor lo implementa por completo.
2. **PostgreSQL.** [The SQL Language](https://www.postgresql.org/docs/current/tutorial-sql-intro.html). Introducción oficial al lenguaje.
3. **MySQL.** [Sakila Sample Database](https://dev.mysql.com/doc/sakila/en/). Origen de la base de muestra, su propósito y las características del motor que ilustra.
4. **Underc0de, foro.** [«MySQL: Base de datos de muestra para practicar»](https://underc0de.org/foro/base-de-datos/mysql-base-de-datos-de-muestra-para-practicar/), por *DtxdF*, 26 de mayo de 2021, sección Base de Datos. Recomendación de Sakila para quien está empezando.
5. **Underc0de, foro.** [«Manual SQL - Con motor MySQL»](https://underc0de.org/foro/base-de-datos/manual-sql-con-motor-mysql/), por *kid_goth*, 8 de junio de 2012, sección Base de Datos. Primer manual de SQL aportado a la comunidad; material histórico anterior a las versiones actuales del motor.

## Guías relacionadas

- [Bases de datos relacionales y tablas](../bases-de-datos-relacionales-y-tablas/index.md) — el paso anterior de la ruta.
- [Cómo diseñar una base de datos sencilla](../como-disenar-una-base-de-datos-sencilla/index.md)
- [MySQL: instalación y primeros pasos](../mysql-instalacion-y-primeros-pasos/index.md) — para poner esto a correr.
- [Base de datos relacional vs no relacional](../relacional-vs-no-relacional/index.md)
- [Guías de Datos y bases de datos](../index.md)

---

Esta guía forma parte de un proyecto de la comunidad Underc0de para organizar conocimiento técnico en español. Fuente canónica: https://underc0de.org/guias/bases-de-datos/introduccion-a-sql-desde-cero/
