Hola! Aquí empieza lo bueno
Con lo de este capítulo ya puedes responder el 60% de lo que te van a pedir en un trabajo. En serio 💜
El orden en que se escribe y el orden en que corre
Esto va primero porque explica casi todos los errores del principio.
Se escribe así:
SELECT columnas FROM tabla WHERE filtro ORDER BY orden LIMIT cuántas
Pero el motor lo ejecuta en otro orden: primero FROM (de dónde),
después WHERE (qué filas), después SELECT (qué
columnas), después ORDER BY y al final LIMIT.
De ahí sale una consecuencia práctica: en el WHERE no deberías usar un alias que creaste en el SELECT, porque cuando el WHERE corre, el SELECT todavía no pasó.
Y aquí tenemos el primer caso de este libro donde SQLite te miente por ser amable. Mira:
SELECT id, monto * 1.18 AS con_igv FROM pedidos WHERE con_igv > 1000 LIMIT 3;
id con_igv -- ------------------ 1 1052.6308 12 1001.5957999999998 14 1344.8814
Funcionó. Y esa es exactamente la trampa 😬
SQLite es permisivo y te deja usar el alias. Los otros tres no, porque siguen
el orden de ejecución al pie de la letra: en PostgreSQL, MySQL y SQL Server esa
consulta te dice que la columna con_igv no existe.
Así que una consulta que aquí corre perfecto revienta en cuanto la lleves al trabajo. Es justo el tipo de cosa por la que este libro marca los cuatro motores 💜
Se arregla repitiendo el cálculo en el WHERE:
La forma que viaja a los cuatro es repetir el cálculo en el WHERE:
SELECT id, monto, monto * 1.18 AS con_igv FROM pedidos WHERE monto * 1.18 > 1000 ORDER BY con_igv DESC LIMIT 3;
id monto con_igv --- ------- ------------------ 25 1399.98 1651.9764 674 1372.34 1619.3611999999998 373 1341.15 1582.557
Y en el ORDER BY sí funciona el alias en los cuatro motores,
porque el ORDER BY corre después del SELECT. Ahí no hay discusión 🙂
Limitar filas: la diferencia que más muerde
Traer solo las 5 primeras
| PostgreSQL | SELECT * FROM pedidos ORDER BY monto DESC LIMIT 5; |
| MySQL | SELECT * FROM pedidos ORDER BY monto DESC LIMIT 5; |
| SQL Server | SELECT TOP 5 * FROM pedidos ORDER BY monto DESC; |
| SQLite | SELECT * FROM pedidos ORDER BY monto DESC LIMIT 5; |
El TOP de SQL Server va pegado al SELECT, antes de las columnas. Si vienes de MySQL y escribes LIMIT, el error que sale no te ayuda nada.
Saltarse las 10 primeras y traer las 5 siguientes (paginar)
| PostgreSQL | SELECT * FROM pedidos ORDER BY id LIMIT 5 OFFSET 10; |
| MySQL | SELECT * FROM pedidos ORDER BY id LIMIT 10, 5; |
| SQL Server | SELECT * FROM pedidos ORDER BY id OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY; |
| SQLite | SELECT * FROM pedidos ORDER BY id LIMIT 5 OFFSET 10; |
MySQL acepta las dos formas, pero en su atajo el orden se invierte: primero cuántas saltas y después cuántas traes. Es un clásico para equivocarse.
Y una regla que vale para los cuatro: limitar sin ordenar no
garantiza nada. Sin ORDER BY, "las 5 primeras" son las 5
que al motor le dé la gana devolver, y puede cambiar entre ejecuciones.
Filtrar con WHERE
SELECT id, canal, monto FROM pedidos WHERE monto > 1500 ORDER BY monto DESC LIMIT 5;
id canal monto -- ----- -----
Los operadores son los que esperas y funcionan igual en los cuatro:
=, <> o != para distinto,
>, <, >=, <=.
AND, OR y los paréntesis que cambian el resultado
SELECT COUNT(*) AS sin_parentesis FROM pedidos WHERE canal = 'Web' OR canal = 'Tienda' AND monto > 1000;
sin_parentesis -------------- 246
SELECT COUNT(*) AS con_parentesis FROM pedidos WHERE (canal = 'Web' OR canal = 'Tienda') AND monto > 1000;
con_parentesis -------------- 31
Números distintos, y la consulta no dio error en ninguno de los dos casos 😳
El motivo: AND se evalúa antes que OR,
como la multiplicación antes que la suma. Así que el primero significa "todo lo
de Web, más lo de Tienda que pase de 1000".
La regla que uso siempre: si mezclas AND con OR, pon paréntesis aunque creas que no hacen falta. Cuestan nada y evitan un reporte equivocado que nadie va a notar.
IN, BETWEEN y LIKE
SELECT COUNT(*) AS pedidos
FROM pedidos
WHERE canal IN ('Web', 'WhatsApp');
pedidos ------- 465
SELECT COUNT(*) AS del_primer_trimestre FROM pedidos WHERE fecha BETWEEN '2025-01-01' AND '2025-03-31';
del_primer_trimestre -------------------- 155
BETWEEN incluye los dos extremos, y ahí está su trampa: con
fechas que llevan hora, BETWEEN '2025-01-01' AND '2025-03-31' se
come todo el 31 de marzo salvo las horas posteriores a medianoche. Por eso en
producción se escribe >= inicio AND < día_siguiente.
SELECT nombre FROM clientes WHERE nombre LIKE 'Bodega%' LIMIT 4;
nombre --------------------- Bodega La Esquina 005 Bodega San Martin 030 Bodega San Martin 034 Bodega La Esquina 036
El % es "lo que sea, incluido nada", y el _ es
exactamente un carácter.
Buscar texto sin importar mayúsculas
| PostgreSQL | WHERE nombre ILIKE '%bodega%' |
| MySQL | WHERE nombre LIKE '%bodega%' -- ya ignora mayúsculas por defecto |
| SQL Server | WHERE nombre LIKE '%bodega%' -- depende del collation de la base |
| SQLite | WHERE LOWER(nombre) LIKE '%bodega%' |
Esta es de las peores, porque el mismo LIKE se comporta distinto en cada motor y ninguno te avisa. Si escribes LOWER() en los dos lados, funciona igual en los cuatro y no dependes de la configuración.
Los nulos, que no se comparan con igual
SELECT COUNT(*) AS con_igual FROM direcciones WHERE es_principal = NULL;
con_igual --------- 0
Cero. Y no porque no haya nulos, sino porque nada es igual a NULL, ni siquiera otro NULL. NULL significa "no se sabe", y dos cosas que no se saben no se puede decir que sean iguales.
SELECT COUNT(*) AS con_is_null FROM direcciones WHERE es_principal IS NULL;
con_is_null ----------- 0
Se usa IS NULL y IS NOT NULL, y eso es igual en los
cuatro motores. El capítulo 4 va entero sobre esto porque da para mucho más.
Ordenar
SELECT nombre, ciudad FROM clientes ORDER BY ciudad ASC, nombre DESC LIMIT 5;
nombre ciudad -------------------------- -------- Restaurante Miraflores 056 Arequipa Minimarket El Sol 109 Arequipa Minimarket Aurora 041 Arequipa Market Central 104 Arequipa Distribuidora Paz 095 Arequipa
ASC es ascendente y es el que se usa si no dices nada;
DESC es al revés. Puedes ordenar por varias columnas, y se aplican
en orden.
Dónde quedan los nulos al ordenar
| PostgreSQL | ORDER BY monto DESC NULLS LAST -- controlable |
| MySQL | ORDER BY monto DESC -- los nulos van al final |
| SQL Server | ORDER BY monto DESC -- los nulos van al final |
| SQLite | ORDER BY monto DESC -- los nulos van al final |
Ordenando de mayor a menor, PostgreSQL pone los nulos primero y los otros tres al final. Si tu top 10 empieza con filas vacías, es esto.
Un caso completo
SELECT c.nombre, c.ciudad, p.fecha, p.canal, p.monto
FROM pedidos p
JOIN clientes c ON c.id = p.id_cliente
WHERE p.monto > 1200
AND p.canal IN ('Web', 'Marketplace')
AND c.ciudad = 'Lima'
ORDER BY p.monto DESC
LIMIT 5;
nombre ciudad fecha canal monto -------------------------- ------ ---------- ----- ------- Restaurante Miraflores 009 Lima 2026-01-19 Web 1210.11
Ese JOIN lo vemos entero en el capítulo 7; aquí solo quería que
vieras cómo se lee una consulta de trabajo real. Y el alias de tabla
(p, c) es lo que la hace legible cuando hay dos o
más 🌟
Ejercicios
1. Los pedidos grandes de un canal
Trae los 5 pedidos de WhatsApp con mayor monto.
SELECT id, fecha, monto FROM pedidos WHERE canal = 'WhatsApp' ORDER BY monto DESC LIMIT 5;
id fecha monto --- ---------- ------- 25 2025-01-25 1399.98 440 2025-11-02 1322.73 780 2025-03-25 1310.96 878 2026-04-17 1209.79 53 2026-05-21 1144.55
2. Escríbelo en SQL Server
La misma consulta anterior, en T-SQL.
SELECT TOP 5 id, fecha, monto FROM pedidos WHERE canal = 'WhatsApp' ORDER BY monto DESC;
El TOP va pegado al SELECT y desaparece el LIMIT. Todo lo demás, idéntico.
Y ahora corre eso mismo contra tienda.db, que es SQLite, para
que veas de qué hablamos cuando decimos que hay cuatro SQL.
SELECT TOP 5 id, fecha, monto FROM pedidos ORDER BY monto DESC;
OperationalError: near "5": syntax error
Ese syntax error no significa que escribiste mal: significa que escribiste bien, pero en otro idioma. Es el error número uno de quien aprende con un motor y llega a una empresa que usa otro 💛
3. Los paréntesis que faltaban
Cuenta los pedidos de Web o Tienda que además pasen de S/800, escrito bien.
SELECT COUNT(*) AS pedidos FROM pedidos WHERE (canal = 'Web' OR canal = 'Tienda') AND monto > 800;
pedidos ------- 97
Sin los paréntesis el resultado sale distinto y no hay error. Es el tipo de fallo que llega a una presentación.
4. Un rango de fechas bien escrito
Cuenta los pedidos de enero de 2026, sin usar BETWEEN.
SELECT COUNT(*) AS pedidos_enero FROM pedidos WHERE fecha >= '2026-01-01' AND fecha < '2026-02-01';
pedidos_enero ------------- 51
Mayor o igual al primero, menor al primero del mes siguiente. Así funciona igual lleve hora o no, y no tienes que acordarte de cuántos días tiene el mes.
5. Buscar por texto
Encuentra los clientes cuyo nombre contenga "Norte".
SELECT nombre, ciudad FROM clientes WHERE nombre LIKE '%Norte%' LIMIT 5;
nombre ciudad -------------------------- -------- Autoservicio Norte 007 Chiclayo Autoservicio Norte 008 Chiclayo Autoservicio Norte 022 Piura Autoservicio Norte 028 Trujillo AUTOSERVICIO NORTE 032 Piura
6. El alias en el WHERE
Usa un alias del SELECT dentro del WHERE. Va a funcionar, y esa es la trampa. Escribe también la versión que sí viaja a los otros tres.
SELECT id, monto * 0.18 AS igv FROM pedidos WHERE igv > 200 LIMIT 3;
id igv -- -------- 14 205.1514 21 200.502 25 251.9964
En SQLite corre. En PostgreSQL, MySQL y SQL Server sale un error diciendo que
la columna igv no existe. La versión que viaja repite el cálculo:
SELECT id, monto * 0.18 AS igv FROM pedidos WHERE monto * 0.18 > 200 LIMIT 3;
id igv -- -------- 14 205.1514 21 200.502 25 251.9964
Misma salida, y esta sí corre en los cuatro. En el capítulo 8 verás la forma elegante de no repetir el cálculo, que es un CTE.
7. Los nulos, bien preguntados
Cuenta las direcciones que NO tienen marcado si son principales.
SELECT COUNT(*) AS sin_marcar FROM direcciones WHERE es_principal IS NULL;
sin_marcar ---------- 0
8. Paginar
Trae la segunda página de clientes, de 10 en 10, ordenados por nombre.
SELECT nombre, ciudad FROM clientes ORDER BY nombre LIMIT 10 OFFSET 10;
nombre ciudad ------------------------------ -------- MARKET CENTRAL 069 Trujillo MARKET CENTRAL 078 Chiclayo MAYORISTA PERU 074 Arequipa MINIMARKET AURORA 068 Arequipa MINIMARKET AURORA 071 Cusco MINIMARKET EL SOL 026 Arequipa RESTAURANTE MIRAFLORES 051 Arequipa RESTAURANTE MIRAFLORES 070 Arequipa Almacenes Vega 010 Lima Almacenes Vega 014 Piura
En MySQL también vale LIMIT 10, 10 y en SQL Server es
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY. El ORDER BY no
es opcional al paginar: sin él, la página 2 puede repetir filas de la 1.
Lo que te llevas
- 🔄 Se escribe SELECT primero y se ejecuta casi al final. Por eso el alias no sirve en el WHERE y sí en el ORDER BY.
- ✂️
LIMITen tres motores,TOPen SQL Server, y siempre conORDER BY. - 🧮 Con AND y OR mezclados, paréntesis siempre.
- 📅 Rangos de fecha con
>=y<, no con BETWEEN. - 🕳️ Los nulos se preguntan con
IS NULL, nunca con=. - 🔠 Para buscar texto sin importar mayúsculas,
LOWER()en los dos lados y funciona igual en los cuatro.
En el capítulo 4 vamos a los tipos de dato y a los nulos en serio, que es donde se producen los errores más caros y más silenciosos.
Que tengas un hermoso día! 🌟