Un cliente me pidió el ranking de sus marcas. Doce locales, doce marcas, un Excel de media hora. Corrí la consulta y me salieron veintitrés.
No había veintitrés marcas. Había doce, y once estaban escritas con
mayúsculas y con dos espacios delante porque alguien las cargó copiando de otro
sistema. Para la base, Market Central y
MARKET CENTRAL son dos cosas distintas. Y no te avisa. Te
entrega el reporte con cara de que todo está bien 🙃
Este capítulo va de eso: de texto sucio y de fechas que no son fechas. Son los dos sitios donde los cuatro motores más se separan, así que aquí la tabla de dialectos vale doble.
La tienda que aparecía dos veces
Vamos a ver la suciedad con nuestros ojos. El truco es meter el texto entre corchetes: los espacios de los bordes son invisibles hasta que pones algo al lado.
SELECT id, '[' || nombre || ']' AS entre_corchetes FROM clientes WHERE nombre <> TRIM(nombre) ORDER BY id LIMIT 5;
id entre_corchetes -- ---------------------------- 12 [ MARKET CENTRAL 012 ] 13 [ CAFE DEL PUERTO 013 ] 20 [ ALMACENES VEGA 020 ] 26 [ MINIMARKET EL SOL 026 ] 32 [ AUTOSERVICIO NORTE 032 ]
Dieciocho clientes de ciento veinte vienen así. Y fíjate que además están en mayúsculas, que es la otra mitad del problema.
Ahora la consulta que me dio veintitrés. La idea es quitarle los tres dígitos del final al nombre para quedarme con la marca.
SELECT COUNT(DISTINCT SUBSTR(nombre, 1, LENGTH(nombre) - 4)) AS marcas FROM clientes;
marcas ------ 23
SELECT COUNT(DISTINCT UPPER(TRIM(SUBSTR(TRIM(nombre), 1, LENGTH(TRIM(nombre)) - 4)))) AS marcas FROM clientes;
marcas ------ 12
Veintitrés contra doce. La misma pregunta, la misma base, el mismo día. Lo único que cambió es que la segunda limpia antes de contar.
Esa consulta se lee de adentro hacia afuera, como las muñecas rusas:
TRIM quita los espacios, SUBSTR corta el código,
TRIM otra vez limpia el espacio que quedó, UPPER pone
todo en mayúsculas y recién ahí COUNT(DISTINCT ...) cuenta. Cinco
funciones que vamos a ver una por una 🧼
Y guárdate la regla, que es la que de verdad importa: antes de agrupar por un texto, límpialo. Siempre. No importa qué tan confiable te digan que es la base.
Juntar texto
SELECT nombre || ' - ' || ciudad AS ficha FROM clientes ORDER BY id LIMIT 4;
ficha ----------------------------- Minimarket El Sol 001 - Piura Minimarket El Sol 002 - Piura Minimarket El Sol 003 - Piura Mayorista Peru 004 - Lima
Esas dos barras verticales son el "pega esto con esto". Y son la primera cosa que cambia de motor a motor.
Pegar dos textos
| PostgreSQL | nombre || ' - ' || ciudad |
| MySQL | CONCAT(nombre, ' - ', ciudad) |
| SQL Server | nombre + ' - ' + ciudad |
| SQLite | nombre || ' - ' || ciudad |
MySQL usa || para el OR lógico, así que ahí las barras no pegan nada: te devuelven 0 o 1 y encima sin error. En SQL Server el + hace de suma y de pegamento según los tipos, que es cómo aparece el "conversion failed" cuando una de las dos columnas es un número.
Si quieres una sola forma que funcione en los cuatro, es
CONCAT(): existe en PostgreSQL, en MySQL, en SQL Server desde 2012
y en SQLite desde la versión 3.44. Pero ojo, porque con nulos no se portan
igual.
SELECT 'Lima' || NULL AS con_pipes, COALESCE('Lima' || NULL, 'se perdio') AS rescatado;
con_pipes rescatado
--------- ---------
se perdio
SELECT CONCAT('Lima', NULL, 'Peru') AS con_concat;
con_concat ---------- LimaPeru
Con || un solo nulo se come toda la frase, igual que en el
capítulo 4. Con CONCAT() en SQLite el nulo se ignora y sale
LimaPeru.
Qué hace CONCAT cuando uno de los textos es NULL
| PostgreSQL | ignora el NULL |
| MySQL | devuelve NULL entero |
| SQL Server | trata el NULL como texto vacío |
| SQLite | ignora el NULL |
Tres lo ignoran y MySQL no, así que la misma consulta que en Postgres te arma la ficha del cliente, en MySQL te deja la columna vacía en cuanto falte un dato. Si hay nulos posibles, envuelve cada trozo en COALESCE y deja de depender del motor.
Mayúsculas, minúsculas y espacios que no ves
UPPER y LOWER hacen lo que suena y se escriben
igual en los cuatro. TRIM quita los espacios de los dos bordes, y
también en los cuatro.
SELECT id,
LENGTH(nombre) AS largo,
LENGTH(TRIM(nombre)) AS largo_limpio,
'[' || TRIM(nombre) || ']' AS limpio
FROM clientes
WHERE id IN (1, 26)
ORDER BY id;
id largo largo_limpio limpio -- ----- ------------ ----------------------- 1 21 21 [Minimarket El Sol 001] 26 25 21 [MINIMARKET EL SOL 026]
El cliente 26 mide 25 caracteres pero su nombre real mide 21. Los cuatro sobrantes son aire, y ese aire es lo que hacía que apareciera como una marca aparte.
Contar cuántos caracteres tiene un texto
| PostgreSQL | LENGTH(nombre) |
| MySQL | CHAR_LENGTH(nombre) |
| SQL Server | LEN(nombre) |
| SQLite | LENGTH(nombre) |
Aquí hay dos trampas. En MySQL, LENGTH cuenta BYTES, así que "Ñaña" mide 6 y no 4: para caracteres es CHAR_LENGTH. Y el LEN de SQL Server ignora los espacios del final, así que el cliente 26 mediría 23 y no 25, y la suciedad se te esconde justo cuando la estás buscando.
Quitar los espacios de los bordes
| PostgreSQL | TRIM(nombre) |
| MySQL | TRIM(nombre) |
| SQL Server | TRIM(nombre) -- desde 2017; antes LTRIM(RTRIM(nombre)) |
| SQLite | TRIM(nombre) |
De las pocas que se escriben igual en los cuatro. Si trabajas contra un SQL Server viejito, el LTRIM(RTRIM(...)) sigue funcionando en todas las versiones, así que es el que no falla nunca.
Cortar y reemplazar
SUBSTR(texto, desde, cuántos) corta un pedazo. Y ojo con algo
que confunde el primer día: en SQL se empieza a contar en 1, no en
0. Si vienes de Python, es al revés de lo que tienes en el dedo.
SELECT id,
'[' || SUBSTR(nombre, -3) || ']' AS ultimos_tres,
UPPER(TRIM(SUBSTR(TRIM(nombre), 1, LENGTH(TRIM(nombre)) - 4))) AS marca
FROM clientes
WHERE id IN (1, 26)
ORDER BY id;
id ultimos_tres marca -- ------------ ----------------- 1 [001] MINIMARKET EL SOL 26 [6 ] MINIMARKET EL SOL
Mira el cliente 26: pedí los últimos tres caracteres y me trajo
6 y dos espacios, porque los últimos tres caracteres de verdad son
espacios. La suciedad no se arregla sola en ningún paso, hay que limpiarla
antes de cortar. Por eso la columna marca lleva el
TRIM por dentro.
Cortar un pedazo de texto
| PostgreSQL | SUBSTRING(nombre FROM 1 FOR 10) -- también SUBSTR(nombre, 1, 10) |
| MySQL | SUBSTRING(nombre, 1, 10) |
| SQL Server | SUBSTRING(nombre, 1, 10) -- los tres argumentos son obligatorios |
| SQLite | SUBSTR(nombre, 1, 10) |
SUBSTRING(texto, desde, cuántos) con esos tres argumentos funciona en los cuatro y es la forma que yo escribo. Lo que no viaja es el número negativo para contar desde el final: en SQL Server no existe y hay que usar RIGHT(nombre, 3).
REPLACE cambia un texto por otro, y esta sí se escribe igual en
todos.
SELECT id, direccion, REPLACE(direccion, 'nro', 'N.') AS normalizada FROM direcciones ORDER BY id LIMIT 3;
id direccion normalizada -- --------------- -------------- 1 Av. 847 nro 331 Av. 847 N. 331 2 Av. 283 nro 161 Av. 283 N. 161 3 Av. 672 nro 178 Av. 672 N. 178
Reemplazar un texto por otro
| PostgreSQL, MySQL, SQL Server y SQLite | REPLACE(direccion, 'nro', 'N.') |
Una de las poquísimas que se escribe idéntica en los cuatro. Reemplaza TODAS las apariciones, no solo la primera.
Buscar, y la trampa de las mayúsculas
En el capítulo 3 vimos LIKE y quedó dicho que se comporta
distinto en cada motor. Ahora lo vamos a medir, que es otra cosa.
SELECT COUNT(*) AS con_like FROM clientes WHERE nombre LIKE '%Sol%';
con_like -------- 11
SELECT COUNT(*) AS con_glob FROM clientes WHERE nombre GLOB '*Sol*';
con_glob -------- 10
Once y diez. La diferencia es el cliente 26, el de
MINIMARKET EL SOL 026: el LIKE de SQLite no distingue
mayúsculas y lo encuentra, y el GLOB sí distingue y lo deja
fuera.
Y aquí está lo que quiero que se te quede: esa misma consulta con
LIKE te devuelve 10 en PostgreSQL, porque ahí el
LIKE sí distingue mayúsculas. Mismo SQL, misma data, distinto
número. Sin error, sin aviso, sin nada 😳
SELECT COUNT(*) AS a_prueba_de_motor FROM clientes WHERE UPPER(nombre) LIKE '%SOL%';
a_prueba_de_motor ----------------- 11
Buscar sin que importen las mayúsculas
| PostgreSQL | nombre ILIKE '%sol%' -- el LIKE normal SÍ distingue |
| MySQL | nombre LIKE '%sol%' -- el collation por defecto no distingue |
| SQL Server | nombre LIKE '%sol%' -- según el collation de la base |
| SQLite | nombre LIKE '%sol%' -- no distingue, pero solo en letras sin tilde |
La única que da el mismo número en los cuatro es UPPER(columna) LIKE '%TEXTO%'. Cuesta seis letras más y te ahorra la conversación de por qué el reporte de la nube y el de tu laptop no coinciden.
Un detalle peruano que muerde: el LIKE de SQLite ignora
mayúsculas solo en el alfabeto inglés. Con Ñ o con vocales con
tilde vuelve a distinguir, así que '%ÑAÑA%' no encuentra
Ñaña. Nuestros nombres de negocio están llenos de tildes, o sea que
esto no es un caso raro de manual, es el martes 🇵🇪
Las fechas no son fechas
Esta es la parte que hay que leer despacio, porque de aquí salen los errores más caros y ninguno da error.
SELECT id, fecha, TYPEOF(fecha) AS tipo, monto, TYPEOF(monto) AS tipo_monto FROM pedidos ORDER BY id LIMIT 3;
id fecha tipo monto tipo_monto -- ---------- ---- ------ ---------- 1 2025-05-30 text 892.06 real 2 2025-06-03 text 731.09 real 3 2025-09-23 text 407.39 real
text. La columna fecha no guarda fechas, guarda
texto que a nosotras nos parece una fecha. Y no es que la base esté mal hecha:
SQLite no tiene tipo fecha, punto. Los otros tres sí.
Cómo se guarda una fecha
| PostgreSQL | DATE, TIMESTAMP |
| MySQL | DATE, DATETIME, TIMESTAMP |
| SQL Server | DATE, DATETIME2 |
| SQLite | no existe: se guarda como TEXT en formato AAAA-MM-DD |
Que SQLite no tenga tipo fecha suena a defecto y en la práctica funciona, porque el formato AAAA-MM-DD ordena y compara bien como texto: el 2025 va antes que el 2026 tanto en el calendario como en el diccionario. Lo que no funciona es cualquier otro formato.
Y ahí está la trampa. Mira lo que pasa si escribes la fecha como la escribimos en Perú.
SELECT COUNT(*) AS pedidos_de_2026 FROM pedidos WHERE fecha >= '01/01/2026';
pedidos_de_2026 --------------- 900
Novecientos. O sea todos los pedidos de la tabla, incluidos los de 2025.
¿Por qué? Porque está comparando texto letra por letra, como el diccionario.
El 0 de 01/01/2026 va antes que el 2 de
2025-05-30, así que todas las fechas le parecen mayores.
Cero errores, cero avisos, un número que está mal.
SELECT COUNT(*) AS pedidos_de_2026 FROM pedidos WHERE fecha >= '2026-01-01';
pedidos_de_2026 --------------- 292
Doscientos noventa y dos. Ese es el bueno.
Y si intentas convertir la fecha peruana a fecha de verdad, tampoco te avisa.
SELECT DATE('24/06/2026') AS a_la_peruana,
DATE('2026-06-24') AS en_iso;
a_la_peruana en_iso
------------ ----------
2026-06-24
La primera sale vacía, que como vimos en el capítulo 4 es NULL.
SQLite no entendió la fecha y en vez de reclamar, se encogió de hombros.
La regla, y va en mayúsculas porque me ha costado caro: dentro de la base, las fechas se escriben AAAA-MM-DD. El formato bonito se pone al final, cuando el número ya está calculado.
El error que sí es error
Ahora prueba lo que hace todo el mundo que viene de MySQL o de SQL Server.
SELECT YEAR(fecha) AS anio FROM pedidos LIMIT 3;
OperationalError: no such function: YEAR
no such function: YEAR. Y este es de los buenos, en serio 🙌 Porque te lo dice de frente en vez de devolverte un número equivocado.
YEAR() existe en MySQL y en SQL Server, no existe en SQLite, y
en PostgreSQL tampoco: ahí es EXTRACT. En SQLite todo lo de fechas
pasa por una sola función, STRFTIME.
SELECT COUNT(*) AS pedidos_de_junio_2026
FROM pedidos
WHERE STRFTIME('%Y-%m', fecha) = '2026-06';
pedidos_de_junio_2026 --------------------- 50
Sacar el año de una fecha
| PostgreSQL | EXTRACT(YEAR FROM fecha) |
| MySQL | YEAR(fecha) -- también EXTRACT(YEAR FROM fecha) |
| SQL Server | YEAR(fecha) -- también DATEPART(year, fecha) |
| SQLite | CAST(STRFTIME('%Y', fecha) AS INTEGER) |
EXTRACT es la forma del estándar y funciona en PostgreSQL y en MySQL. SQL Server no la tiene. Para el mes es igual: MONTH() en dos, EXTRACT en dos, STRFTIME('%m', ...) en SQLite.
El CAST del final no es decoración: STRFTIME
devuelve texto, así que sin él te llevas '2026' entre comillas y no
puedes sumarlo ni restarlo.
Hoy, hace 90 días y el primero del mes
Casi todo reporte de negocio empieza igual: "lo de los últimos 90 días". Para eso hay que saber hacerle cuentas a una fecha.
SELECT DATE('2026-06-24', '-90 days') AS hace_90_dias,
DATE('2026-06-24', '+1 month') AS en_un_mes,
DATE('2026-06-24', 'start of month') AS inicio_de_mes;
hace_90_dias en_un_mes inicio_de_mes ------------ ---------- ------------- 2026-03-26 2026-07-24 2026-06-01
SELECT COUNT(*) AS ultimos_90_dias
FROM pedidos
WHERE fecha >= DATE('2026-06-24', '-90 days');
ultimos_90_dias --------------- 166
Ciento sesenta y seis pedidos en los últimos 90 días de la base. Puse la fecha a mano porque el último pedido de esta tienda es del 24 de junio de 2026 y quiero que a ti te salga el mismo número que a mí. En tu trabajo, en cambio, vas a querer "hoy", y ahí es donde los cuatro se ponen creativos.
La fecha de hoy
| PostgreSQL | CURRENT_DATE |
| MySQL | CURDATE() -- también CURRENT_DATE |
| SQL Server | CAST(GETDATE() AS DATE) |
| SQLite | DATE('now') -- también CURRENT_DATE |
CURRENT_DATE funciona en tres de los cuatro; el que se queda fuera es SQL Server, que tiene CURRENT_TIMESTAMP pero no CURRENT_DATE. Y en SQLite el DATE('now') te da la hora UTC, o sea cinco horas adelantada de Lima: si te importa el borde del día, es DATE('now', 'localtime').
Restarle 90 días a una fecha
| PostgreSQL | fecha - INTERVAL '90 days' |
| MySQL | DATE_SUB(fecha, INTERVAL 90 DAY) |
| SQL Server | DATEADD(day, -90, fecha) |
| SQLite | DATE(fecha, '-90 days') |
Cuatro sintaxis distintas para la misma resta, y ninguna se parece a otra. Esta es la fila que yo tengo pegada al monitor.
Ponerle formato al final
SELECT id, fecha, STRFTIME('%d/%m/%Y', fecha) AS a_la_peruana
FROM pedidos
ORDER BY id
LIMIT 3;
id fecha a_la_peruana -- ---------- ------------ 1 2025-05-30 30/05/2025 2 2025-06-03 03/06/2025 3 2025-09-23 23/09/2025
Mostrar una fecha como 24/06/2026
| PostgreSQL | TO_CHAR(fecha, 'DD/MM/YYYY') |
| MySQL | DATE_FORMAT(fecha, '%d/%m/%Y') |
| SQL Server | FORMAT(fecha, 'dd/MM/yyyy') -- también CONVERT(varchar, fecha, 103) |
| SQLite | STRFTIME('%d/%m/%Y', fecha) |
Cuatro nombres distintos y encima dos idiomas de máscara: MySQL y SQLite usan %d/%m/%Y, PostgreSQL usa DD/MM/YYYY y SQL Server usa dd/MM/yyyy con la M en mayúscula para el mes, porque la m minúscula ahí son los minutos.
Y una cosa de orden que vale para los cuatro: el formato se pone en
el SELECT, nunca en el WHERE ni en el ORDER BY. Si ordenas por
'24/06/2026' estás ordenando por día, y diciembre de 2025 te va a
quedar antes que enero del mismo año.
El día de la semana también sale de ahí, y sirve para preguntas de negocio de verdad.
SELECT COUNT(*) AS pedidos_en_domingo
FROM pedidos
WHERE STRFTIME('%w', fecha) = '0';
pedidos_en_domingo ------------------ 129
En SQLite el domingo es el 0. En MySQL DAYOFWEEK() también
arranca el domingo pero en 1, y en SQL Server DATEPART(weekday, ...)
depende de una configuración de la sesión. Si vas a usar el día de la semana,
compruébalo con una fecha que sepas antes de creerle.
Cuánto tiempo pasó
SELECT nombre,
fecha_alta,
CAST(JULIANDAY('2026-06-24') - JULIANDAY(fecha_alta) AS INTEGER) AS dias_con_nosotros
FROM clientes
ORDER BY dias_con_nosotros DESC
LIMIT 3;
nombre fecha_alta dias_con_nosotros ------------------------------ ---------- ----------------- Bodega La Esquina 044 2025-01-04 536 Almacenes Vega 014 2025-01-05 535 RESTAURANTE MIRAFLORES 070 2025-01-05 535
JULIANDAY convierte una fecha en un número de días corridos, así
que restar dos julianday da días. Y mira el tercero de la lista, que sigue
teniendo su nombre sucio: la limpieza que hicimos arriba no cambió la base, solo
la consulta 🧽
Días entre dos fechas
| PostgreSQL | fecha_fin - fecha_ini -- con columnas DATE devuelve el número de días |
| MySQL | DATEDIFF(fecha_fin, fecha_ini) |
| SQL Server | DATEDIFF(day, fecha_ini, fecha_fin) |
| SQLite | JULIANDAY(fecha_fin) - JULIANDAY(fecha_ini) |
Fíjate bien en las dos del medio: se llaman igual, DATEDIFF, y reciben los argumentos al revés. La de MySQL va (fin, inicio) y la de SQL Server va (unidad, inicio, fin). Copiar una consulta de un motor al otro te cambia el signo del resultado sin un solo error.
Ejercicios
Los siete corren sobre tienda.db. Intenta antes de abrir 💛
1. La ficha del cliente
Arma una sola columna que diga
NOMBRE LIMPIO (segmento, ciudad) para los clientes 1, 12 y 44. El
12 es uno de los sucios.
SELECT UPPER(TRIM(nombre)) || ' (' || segmento || ', ' || ciudad || ')' AS ficha
FROM clientes
WHERE id IN (1, 12, 44)
ORDER BY id;
ficha ----------------------------------------- MINIMARKET EL SOL 001 (Horeca, Piura) MARKET CENTRAL 012 (Horeca, Arequipa) BODEGA LA ESQUINA 044 (Minimarket, Piura)
El TRIM va por dentro del UPPER, aunque en este
caso da igual el orden. Lo que no da igual es que esté.
2. Cuánta suciedad hay
Cuenta cuántos clientes tienen espacios en los bordes. Sin subconsultas todavía, que eso es el capítulo 8.
SELECT COUNT(*) AS con_espacios FROM clientes WHERE nombre <> TRIM(nombre);
con_espacios ------------ 18
SELECT COUNT(*) AS en_mayuscula FROM clientes WHERE nombre = UPPER(nombre);
en_mayuscula ------------ 18
Dieciocho y dieciocho, y no es casualidad: son los mismos dieciocho registros, que entraron por la misma carga mal hecha. Un 15% de la tabla de clientes 🫠
3. El código de tres dígitos, como número
Saca los tres dígitos del final del nombre y conviértelos en entero, para los clientes 1, 26 y 120. El 26 es sucio, así que ahí está la gracia.
SELECT id, CAST(SUBSTR(TRIM(nombre), -3) AS INTEGER) AS codigo FROM clientes WHERE id IN (1, 26, 120) ORDER BY id;
id codigo --- ------ 1 1 26 26 120 120
El TRIM tiene que ir dentro del SUBSTR. Si
lo pones fuera, cortas primero y limpias después, o sea que te llevas
'6 ' y el CAST te devuelve 6 en vez de 26. Sin
error.
Y de paso: el CAST se come los ceros de la izquierda, por eso el
001 sale como 1. Si el código es un identificador y no una cantidad, déjalo
como texto.
4. Todas las bodegas
Cuenta los clientes cuyo nombre empieza por "Bodega", de forma que dé el mismo número en los cuatro motores.
SELECT COUNT(*) AS bodegas FROM clientes WHERE UPPER(TRIM(nombre)) LIKE 'BODEGA%';
bodegas ------- 17
Diecisiete. El UPPER lo hace igual en los cuatro y el
TRIM rescata a las que tenían espacios delante, que con
LIKE 'BODEGA%' se habrían quedado fuera porque el
% está al final y no al principio.
5. El primer trimestre de 2026
Cuántos pedidos y cuántos soles entre el 1 de enero y el 31 de marzo de 2026.
SELECT COUNT(*) AS primer_trimestre_2026, ROUND(SUM(monto), 2) AS soles FROM pedidos WHERE fecha BETWEEN '2026-01-01' AND '2026-03-31';
primer_trimestre_2026 soles --------------------- -------- 139 81510.72
Funciona porque la columna es texto en formato ISO y no lleva hora. El día
que esa columna tenga hora, el 31 de marzo a las 3 de la tarde se queda fuera y
el BETWEEN te miente. Por eso en producción se escribe
>= '2026-01-01' AND fecha < '2026-04-01', que es correcto
lleve hora o no.
6. Fin de semana por WhatsApp
Cuántos pedidos de WhatsApp cayeron en sábado o domingo.
SELECT COUNT(*) AS whatsapp_fin_de_semana
FROM pedidos
WHERE canal = 'WhatsApp' AND STRFTIME('%w', fecha) IN ('0', '6');
whatsapp_fin_de_semana ---------------------- 70
Setenta de los 231 pedidos de WhatsApp, o sea un 30%, que es casi exactamente lo que pesan dos días de siete. Un dato que suena a hallazgo y no lo es: antes de contárselo a nadie, compara siempre contra lo que saldría por puro azar 📊
7. Escríbelo para los cuatro
Sin ejecutar: los pedidos de los últimos 90 días contados desde hoy, en los cuatro motores.
-- PostgreSQL
SELECT COUNT(*) FROM pedidos WHERE fecha >= CURRENT_DATE - INTERVAL '90 days';
-- MySQL
SELECT COUNT(*) FROM pedidos WHERE fecha >= DATE_SUB(CURDATE(), INTERVAL 90 DAY);
-- SQL Server
SELECT COUNT(*) FROM pedidos WHERE fecha >= DATEADD(day, -90, CAST(GETDATE() AS DATE));
-- SQLite
SELECT COUNT(*) FROM pedidos WHERE fecha >= DATE('now', '-90 days');
Cuatro formas de escribir exactamente lo mismo. No hay salida publicada porque el resultado cambia cada día que la corras, y en este libro no se publica una salida que no se pueda comprobar.
Sobre nuestra base te va a dar un número distinto al mío según el día que la corras, porque "hoy" se mueve y los pedidos no. Y en algún momento va a dar cero, porque el último pedido de la tienda es del 24 de junio de 2026. Los datos de práctica también envejecen 🐣
Lo que te llevas
- 🧼 Antes de agrupar por un texto:
UPPER(TRIM(columna)). Doce marcas se convirtieron en veintitrés por saltarse esto. - 🔗 Pegar texto es
||en PostgreSQL y SQLite,CONCAT()en MySQL y+en SQL Server.CONCAT()es la que funciona en los cuatro. - 🔍
LIKEdistingue mayúsculas en PostgreSQL y no en los otros tres.UPPER(columna) LIKE '%TEXTO%'da el mismo número siempre. - 📅 SQLite no tiene tipo fecha: son textos en formato AAAA-MM-DD, y ese formato es obligatorio o las comparaciones mienten sin avisar.
- 🚫
YEAR()no existe en SQLite ni en PostgreSQL. En SQLite todo sale deSTRFTIME, y hay que envolverlo enCASTpara tener un número. - ⚠️
DATEDIFFse llama igual en MySQL y en SQL Server y recibe los argumentos al revés. - 🎀 El formato bonito va en el
SELECT, nunca en elWHEREni en elORDER BY.
En el capítulo 6 llega GROUP BY, que es donde el SQL deja de
listar filas y empieza a responder preguntas. Y donde PostgreSQL te va a exigir
una cosa que MySQL te deja pasar, con consecuencias.
Que tengas lindo día! 🌸