Hola! Bienvenida al capítulo de los errores silenciosos
Todo lo de este capítulo tiene algo en común: no da error. Te devuelve un número, tú lo pones en un reporte, y está mal 😬
Los que dan error se arreglan solos porque los ves. Estos hay que conocerlos.
Y dime si te suena: ¿alguna vez entregaste un reporte y el total no cuadraba por poquito? Ese poquito casi siempre está en este capítulo, y casi siempre tiene forma de NULL 🕳️
Los tipos que vas a usar
Un tipo dice qué se puede guardar en una columna y cómo se opera con ella. Son muchos y en la práctica se reducen a cinco familias.
Texto de largo variable
| PostgreSQL | VARCHAR(100) o TEXT -- TEXT no tiene penalización |
| MySQL | VARCHAR(100) o TEXT -- TEXT no se puede indexar entero |
| SQL Server | NVARCHAR(100) o NVARCHAR(MAX) -- la N es para Unicode |
| SQLite | TEXT -- solo hay uno y acepta cualquier largo |
La N de SQL Server importa en Perú: sin ella, VARCHAR puede no guardar bien las tildes y las eñes según la configuración. Usa siempre NVARCHAR.
Dinero y decimales exactos
| PostgreSQL | NUMERIC(12, 2) |
| MySQL | DECIMAL(12, 2) |
| SQL Server | DECIMAL(12, 2) o MONEY |
| SQLite | REAL -- no tiene decimal exacto, y eso importa |
Para plata NUNCA uses FLOAT ni REAL: son aproximados y los centavos se van perdiendo. DECIMAL y NUMERIC son exactos. SQLite no tiene ninguno de los dos, así que ahí el dinero se suele guardar en céntimos como entero.
Fecha con hora
| PostgreSQL | TIMESTAMP o TIMESTAMPTZ -- el TZ guarda la zona horaria |
| MySQL | DATETIME o TIMESTAMP |
| SQL Server | DATETIME2 -- DATETIME es el viejo y tiene menos precisión |
| SQLite | TEXT en formato ISO -- no hay tipo fecha |
SQLite guarda las fechas como texto, y funciona porque el formato AAAA-MM-DD se ordena igual como texto que como fecha. Por eso el formato internacional no es una manía: es lo que hace que ordene bien.
Y una diferencia de fondo que explica muchas cosas raras de SQLite:
SELECT typeof(id), typeof(nombre), typeof(precio) FROM productos LIMIT 1;
typeof(id) typeof(nombre) typeof(precio) ---------- -------------- -------------- integer text real
SQLite tiene tipado dinámico: el tipo lo lleva cada valor, no la columna. Puedes meter un texto en una columna declarada como entero y lo acepta. Los otros tres te lo rechazan de plano.
Eso es cómodo mientras aprendes y es una fuente de desastres en producción, porque un dato mal cargado entra sin que nadie se entere 🙃
La división que devuelve cero
Este es el error más caro del capítulo y no da ningún aviso.
SELECT 1 / 2 AS mitad;
mitad ----- 0
Cero. Porque los dos son enteros, y en SQL entero dividido entero da entero. Se corta la parte decimal y ya.
SELECT 1 * 1.0 / 2 AS mitad, CAST(1 AS REAL) / 2 AS tambien_mitad;
mitad tambien_mitad ----- ------------- 0.5 0.5
Se arregla haciendo que uno de los dos sea decimal, y hay dos maneras:
multiplicar por 1.0 o convertir con CAST.
Dividir sin perder los decimales
| PostgreSQL | SELECT monto::numeric / cantidad -- o CAST(monto AS numeric) |
| MySQL | SELECT monto / cantidad -- MySQL ya devuelve decimal, es el raro |
| SQL Server | SELECT CAST(monto AS DECIMAL(12,2)) / cantidad |
| SQLite | SELECT monto * 1.0 / cantidad |
MySQL es el único que hace la división decimal por su cuenta. Si aprendes ahí y pasas a otro motor, tus porcentajes se convierten en ceros de golpe.
Dónde muerde de verdad, y lo he visto en informes de empresa:
SELECT canal, COUNT(*) AS pedidos, SUM(CASE WHEN monto > 1000 THEN 1 ELSE 0 END) / COUNT(*) AS mal, SUM(CASE WHEN monto > 1000 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) AS bien FROM pedidos GROUP BY canal;
canal pedidos mal bien ----------- ------- --- ----------------- Marketplace 229 0 8.296943231441048 Tienda 206 0 5.825242718446602 Web 234 0 8.11965811965812 WhatsApp 231 0 9.956709956709958
La columna mal da cero en todos los canales. Un porcentaje entero
que sale cero siempre es esto 💡
NULL, que no es cero ni vacío
NULL significa "no se sabe". No es el número cero, no es el texto vacío, no es un espacio. Es la ausencia del dato.
Y de ahí sale su comportamiento, que al principio parece caprichoso y en realidad es coherente.
SELECT 100 + NULL AS suma, 'Lima' || NULL AS texto, NULL = NULL AS son_iguales;
suma texto son_iguales ---- ----- -----------
Todo NULL. Y tiene lógica: si no sé cuánto es una cosa, tampoco sé cuánto es esa cosa más cien. Y no puedo afirmar que dos cosas que no conozco sean iguales 🤷
Dónde te muerde en serio
SELECT COUNT(*) AS todas, COUNT(monto) AS con_monto, COUNT(id_cliente) AS con_cliente FROM pedidos;
todas con_monto con_cliente ----- --------- ----------- 900 873 876
COUNT(*) cuenta filas y COUNT(columna)
cuenta valores no nulos. Esa diferencia, si no la sabes, te cambia
cualquier conteo.
Lo mismo con los promedios:
SELECT ROUND(AVG(monto), 2) AS promedio_sql, ROUND(SUM(monto) * 1.0 / COUNT(*), 2) AS sobre_todas_las_filas FROM pedidos;
promedio_sql sobre_todas_las_filas ------------ --------------------- 610.14 591.84
Dan distinto porque AVG ignora los nulos: divide entre los que
tienen valor, no entre todas las filas. Ninguno de los dos está mal; lo que está
mal es no saber cuál te dio tu reporte 🔍
COALESCE, que es el estándar
SELECT id, monto, COALESCE(monto, 0) AS con_repuesto FROM pedidos WHERE monto IS NULL LIMIT 3;
id monto con_repuesto -- ----- ------------ 22 0 57 0 78 0
Si es nulo, ponme otro valor
| PostgreSQL | COALESCE(monto, 0) |
| MySQL | COALESCE(monto, 0) o IFNULL(monto, 0) |
| SQL Server | COALESCE(monto, 0) o ISNULL(monto, 0) |
| SQLite | COALESCE(monto, 0) o IFNULL(monto, 0) |
COALESCE está en los cuatro y además acepta varios repuestos en cadena: COALESCE(a, b, c, 0) devuelve el primero que no sea nulo. Los atajos de cada motor solo aceptan dos.
Y el aviso importante: rellenar con cero no siempre está bien. Si el monto es nulo porque nadie lo cargó, ponerle cero convierte "no sé" en "no vendió", y eso te baja el promedio con datos inventados. A veces la respuesta correcta es dejarlo nulo y decir cuántos había 💜
Convertir tipos con CAST
SELECT CAST('2026-01-15' AS TEXT) AS fecha_texto, CAST('123' AS INTEGER) + 1 AS numero, CAST(89.7 AS INTEGER) AS trunca;
fecha_texto numero trunca ----------- ------ ------ 2026-01-15 124 89
Fíjate en el último: CAST a entero trunca, no
redondea. 89,7 se convierte en 89. Si querías redondear, es
ROUND.
SELECT ROUND(89.7) AS redondea, CAST(89.7 AS INTEGER) AS trunca;
redondea trunca -------- ------ 90.0 89
Convertir a número
| PostgreSQL | CAST('123' AS INTEGER) o '123'::integer |
| MySQL | CAST('123' AS SIGNED) -- ojo, no acepta INTEGER |
| SQL Server | CAST('123' AS INT) o CONVERT(INT, '123') |
| SQLite | CAST('123' AS INTEGER) |
CAST es estándar y está en los cuatro; lo que cambia es el nombre del tipo de destino. MySQL pide SIGNED donde los otros piden INTEGER o INT.
Ese ::integer de PostgreSQL es cortito y engancha, así que se
copia muchísimo. Mira lo que hace fuera de su casa.
SELECT SUM(monto)::numeric FROM pedidos;
OperationalError: unrecognized token: ":"
unrecognized token quiere decir "no sé ni qué es ese símbolo". Los
dos puntos dobles son de PostgreSQL y de nadie más: no están en MySQL, ni en SQL
Server, ni en SQLite. CAST(algo AS tipo) es más largo de escribir y
te sirve en los cuatro 💛
Verdadero, falso y una tercera cosa
Aquí está la idea que hace que todo lo anterior encaje, y casi nadie la cuenta: en SQL una comparación no devuelve dos valores, devuelve tres 🎲
SELECT COUNT(*) AS total, SUM(CASE WHEN monto > 500 THEN 1 ELSE 0 END) AS mayores, SUM(CASE WHEN NOT (monto > 500) THEN 1 ELSE 0 END) AS no_mayores FROM pedidos;
total mayores no_mayores ----- ------- ---------- 900 558 315
Mira la suma: 558 más 315 dan 873, y la tabla tiene 900 pedidos. Faltan 27 😳
Son los 27 pedidos sin monto. No están en "mayores que 500" y tampoco en "no
mayores que 500", porque para ellos la comparación no es ni verdadera ni falsa:
es desconocida. Y el NOT de un desconocido sigue
siendo desconocido.
Eso es lo que explica todos los sustos de este capítulo de una vez:
SELECT NULL = NULL AS son_iguales, NULL <> NULL AS son_distintos, NULL IS NULL AS es_nulo;
son_iguales son_distintos es_nulo
----------- ------------- -------
1
Las dos primeras salen vacías, que es como se ve un desconocido. La tercera sale 1 🎯
Por eso IS NULL no es una manía de la sintaxis: es el único
operador que contesta verdadero o falso cuando hay un hueco. Todos los demás se
encogen de hombros.
Y la regla práctica, que vale para el resto del libro: si una cuenta tiene que cuadrar con el total de filas, cuenta los tres casos, no dos. El tercero es el que se escapa.
El promedio que no es el promedio
Esta es la consecuencia cara, y la vas a ver en un informe algún día 💸
SELECT ROUND(SUM(monto), 2) AS suma, COUNT(*) AS filas, ROUND(SUM(monto) / COUNT(*), 2) AS a_mano, ROUND(AVG(monto), 2) AS con_avg FROM pedidos;
suma filas a_mano con_avg --------- ----- ------ ------- 532653.85 900 591.84 610.14
Dieciocho soles de diferencia por pedido, con los mismos datos y en la misma consulta 😖
Los dos están bien calculados y contestan preguntas distintas.
AVG divide entre los pedidos que tienen monto, o
sea 873. El de la izquierda divide entre todas las filas, 900,
repartiendo la suma también entre los 27 que no aportaron nada.
Cuál está bien depende de lo que preguntaste 🤔
- 💰 "¿Cuánto vale un pedido típico?" Ahí van los 873, porque un pedido sin monto no es un pedido de cero soles: es un pedido del que no sabemos el monto.
- 📉 "¿Cuánto facturamos por pedido registrado?" Ahí van los 900, y estás midiendo también lo mal que se registra.
Lo que no vale es no saber cuál de las dos estás calculando, que es lo que
pasa cuando uno escribe AVG sin mirar los huecos.
NULLIF, que es COALESCE al revés
COALESCE cambia un nulo por un valor. NULLIF hace lo
contrario: cambia un valor por un nulo. Y suena inútil hasta que ves para qué 🔧
SELECT 100 / 0 AS entre_cero, 100 / NULLIF(0, 0) AS con_nullif;
entre_cero con_nullif ---------- ----------
Las dos salen vacías, y aquí hay una trampa que solo se ve sabiendo dónde estás parada 🪤
En SQLite, dividir entre cero devuelve NULL y la consulta sigue como si nada. En PostgreSQL, MySQL en modo estricto y SQL Server, esa misma línea revienta con un error de división por cero y te tira el reporte entero.
O sea que si desarrollas en SQLite y despliegas en Postgres, este es exactamente el tipo de cosa que funciona en tu máquina y falla el día de la presentación 😬
NULLIF(divisor, 0) convierte el cero en nulo antes de dividir, y
un nulo no revienta en ningún motor: devuelve nulo. Es la forma portátil de
protegerse.
Juntando los dos, que es como se usan de verdad:
SELECT canal, COUNT(*) AS pedidos, SUM(CASE WHEN monto IS NULL THEN 1 ELSE 0 END) AS sin_monto, ROUND(COALESCE(100.0 * SUM(CASE WHEN monto IS NULL THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0), 0), 1) AS pct FROM pedidos GROUP BY canal ORDER BY pct DESC;
canal pedidos sin_monto pct ----------- ------- --------- --- Tienda 206 8 3.9 Marketplace 229 7 3.1 WhatsApp 231 6 2.6 Web 234 6 2.6
Y la respuesta es tranquilizadora: entre el 2,6% y el 3,9% en los cuatro canales. No hay un canal culpable, el registro falla parejo en todos 🤷♀️
Eso también es un hallazgo y hay que decirlo. Si un canal tuviera el 20%, la conversación sería con esa gente; como están todos igual, la conversación es sobre el proceso.
Dónde se ponen los nulos al ordenar
SELECT id, monto FROM pedidos ORDER BY monto LIMIT 5;
id monto --- ----- 22 57 78 83 152
Los nulos salen primero. Y ahora al revés:
SELECT id, monto FROM pedidos ORDER BY monto DESC LIMIT 5;
id monto --- ------- 25 1399.98 674 1372.34 373 1341.15 382 1324.71 440 1322.73
Al final, así que aquí no se ven 👀
Esto importa más de lo que parece. Si pides "los cinco pedidos más pequeños" y ordenas ascendente, en SQLite te salen cinco filas vacías y ni un monto. El top se lo comen los huecos.
Y no es igual en todos los motores, que es lo peor que puede pasar:
Dónde caen los nulos al ordenar
| PostgreSQL | ORDER BY monto -- nulos al final |
| MySQL | ORDER BY monto -- nulos al principio |
| SQL Server | ORDER BY monto -- nulos al principio |
| SQLite | ORDER BY monto -- nulos al principio |
Y la forma que funciona igual en los cuatro: ORDER BY monto IS NULL, monto. Ese IS NULL devuelve 0 o 1, así que ordenar por él primero manda los huecos al final digas lo que digas después.
PostgreSQL y Oracle los tratan como el valor más grande, así que en orden ascendente salen al final; SQLite, MySQL y SQL Server los tratan como el más pequeño y salen al principio. La misma consulta, dos resultados 🙃
La única forma de no depender de eso es decirlo tú, y por eso el
IS NULL del final aparece tanto en el código de verdad: ordena
primero por si es nulo, y solo después por el monto.
Ejercicios
1. La división que engaña
Calcula qué porcentaje de pedidos pasan de S/1000, primero mal y después bien.
SELECT SUM(CASE WHEN monto > 1000 THEN 1 ELSE 0 END) / COUNT(*) AS mal, ROUND(SUM(CASE WHEN monto > 1000 THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) AS bien FROM pedidos;
mal bien --- ---- 0 8.1
2. COUNT(*) contra COUNT(columna)
Sobre pedidos, muestra las dos cuentas y cuántos nulos hay.
SELECT COUNT(*) AS filas, COUNT(monto) AS con_valor, COUNT(*) - COUNT(monto) AS nulos FROM pedidos;
filas con_valor nulos ----- --------- ----- 900 873 27
3. El nulo que se come la suma
Comprueba que sumar algo a NULL devuelve NULL, y arréglalo con COALESCE.
SELECT 500 + NULL AS sin_arreglar, 500 + COALESCE(NULL, 0) AS arreglado;
sin_arreglar arreglado
------------ ---------
500
4. Truncar contra redondear
Con el precio del producto más caro, muestra el valor original, truncado y redondeado.
SELECT precio, CAST(precio AS INTEGER) AS truncado, ROUND(precio) AS redondeado FROM productos ORDER BY precio DESC LIMIT 1;
precio truncado redondeado ------ -------- ---------- 89.35 89 89.0
Un céntimo por fila no suena a nada, y en un millón de filas es dinero.
5. El tipo de cada valor
Comprueba el tipado dinámico de SQLite: mira el tipo de tres columnas distintas.
SELECT typeof(id) AS tipo_id, typeof(fecha) AS tipo_fecha, typeof(monto) AS tipo_monto FROM pedidos LIMIT 1;
tipo_id tipo_fecha tipo_monto ------- ---------- ---------- integer text real
La fecha es text. En PostgreSQL, MySQL y SQL Server sería un tipo
fecha de verdad, y ahí sí puedes restar dos fechas directamente.
6. Promedio con nulos
Calcula el monto promedio de las dos formas y explica por qué difieren.
SELECT ROUND(AVG(monto), 2) AS avg_ignora_nulos, ROUND(SUM(COALESCE(monto, 0)) * 1.0 / COUNT(*), 2) AS nulos_como_cero FROM pedidos;
avg_ignora_nulos nulos_como_cero ---------------- --------------- 610.14 591.84
El primero divide entre las filas con valor; el segundo entre todas. Cuál es el correcto depende de qué significa el hueco, y eso lo sabe quien cargó los datos, no la consulta.
7. Escríbelo para los cuatro
Sin ejecutar: el ticket promedio por pedido con dos decimales, en los cuatro motores.
-- PostgreSQL SELECT ROUND(SUM(monto)::numeric / COUNT(*), 2) FROM pedidos; -- MySQL (el único que divide decimal por su cuenta) SELECT ROUND(SUM(monto) / COUNT(*), 2) FROM pedidos; -- SQL Server SELECT ROUND(CAST(SUM(monto) AS DECIMAL(12,2)) / COUNT(*), 2) FROM pedidos; -- SQLite SELECT ROUND(SUM(monto) * 1.0 / COUNT(*), 2) FROM pedidos;
Y en los cuatro existe AVG(monto), que hace lo mismo en una
palabra. Cuando exista la función, úsala: es más corta y no tiene el problema de
la división entera 🌟
7. Los nulos donde tú digas
Pide los cinco pedidos más baratos, de verdad.
SELECT id, monto FROM pedidos ORDER BY monto IS NULL, monto LIMIT 5;
id monto --- ----- 852 5.05 708 5.77 52 16.48 557 16.62 351 42.04
Ahí está el truco entero: monto IS NULL devuelve 0 o 1, y
ordenar por eso primero manda todos los huecos al final 🎯
Sin esa línea, en SQLite esta misma consulta te devuelve cinco filas vacías y ni un solo monto, porque los nulos van primero. Y en PostgreSQL te habría funcionado sin ponerla, que es justo lo que hace que el error viaje: en un motor sale bien por casualidad y en el otro no.
La versión declarada de esto es ORDER BY monto NULLS LAST, que
existe en PostgreSQL y en Oracle pero no en SQLite ni en MySQL. El
IS NULL funciona en los cuatro.
8. Rellenar con cero, ¿cambia algo?
Calcula la suma y el promedio de dos formas: ignorando los nulos y tratándolos como cero.
SELECT ROUND(SUM(monto), 2) AS suma_ignorando, ROUND(SUM(COALESCE(monto, 0)), 2) AS suma_con_cero, ROUND(AVG(monto), 2) AS promedio_ignorando, ROUND(AVG(COALESCE(monto, 0)), 2) AS promedio_con_cero FROM pedidos;
suma_ignorando suma_con_cero promedio_ignorando promedio_con_cero -------------- ------------- ------------------ ----------------- 532653.85 532653.85 610.14 591.84
Las dos sumas dan exactamente lo mismo, y los dos promedios no 🤨
La suma no cambia porque sumar cero no suma nada, y SUM ya
ignoraba esos huecos de todas formas. El promedio sí cambia porque el
COALESCE convirtió 27 desconocidos en 27 pedidos de cero soles, y
esos 27 ahora sí entran en el reparto.
De ahí sale la regla que uso: rellenar nulos con cero es una decisión
sobre el denominador, no sobre el numerador. Y hay que tomarla a
sabiendas, no por costumbre de escribir COALESCE en todo.
9. Las tres formas de contar, juntas
Cuenta filas, valores y valores distintos en una sola consulta.
SELECT COUNT(*) AS todas, COUNT(monto) AS con_valor, COUNT(DISTINCT canal) AS canales FROM pedidos;
todas con_valor canales ----- --------- ------- 900 873 4
Tres números que se parecen y contestan tres preguntas distintas 🔢
- 🧾
COUNT(*)cuenta filas, mire lo que mire. - 💵
COUNT(monto)cuenta valores que hay. La diferencia con el primero son los huecos, y aquí son 27. - 🏷️
COUNT(DISTINCT canal)cuenta valores diferentes. Y también se salta los nulos, aunque aquí no haya.
El truco de restar los dos primeros es la forma más corta que conozco de
contar huecos sin escribir un CASE.
10. Dónde se pierden los montos
Mira si los pedidos sin monto se concentran en algún tipo de cliente.
SELECT c.segmento, COUNT(*) AS pedidos, SUM(CASE WHEN p.monto IS NULL THEN 1 ELSE 0 END) AS sin_monto, ROUND(AVG(p.monto), 2) AS ticket FROM pedidos p JOIN clientes c ON c.id = p.id_cliente GROUP BY c.segmento ORDER BY ticket DESC;
segmento pedidos sin_monto ticket ---------- ------- --------- ------ Minimarket 174 8 630.43 Horeca 257 8 610.05 Mayorista 194 3 607.59 Bodega 251 8 596.05
Ocho, ocho, ocho y tres. Repartidos, salvo Mayorista que tiene menos 🧐
Y aquí hay que tener cuidado con lo que se concluye. Mayorista tiene 3 huecos sobre 194 pedidos y Bodega tiene 8 sobre 251: son 1,5% contra 3,2%. Parece el doble, pero estamos hablando de cinco pedidos de diferencia, y con números tan chicos eso se mueve solo.
Lo honesto es decir que no se ve un patrón, no inventar uno. Cuando la diferencia son cinco filas, lo que hace falta no es una consulta más lista: son más datos.
La consulta que contesta cero y debería contestar uno
Con todo lo del capítulo en la cabeza, mira esta. Es la trampa más cara de los nulos y la contesta mal casi todo el mundo la primera vez 😬
La trampa
Quieres los clientes que nunca compraron. Lo escribes como se dice en español: los que no están en la lista de los que hicieron pedidos.
SELECT COUNT(*) FROM clientes WHERE id NOT IN (SELECT id_cliente FROM pedidos); -- 0 SELECT COUNT(*) FROM clientes c WHERE NOT EXISTS ( SELECT 1 FROM pedidos p WHERE p.id_cliente = c.id); -- 1
Qué está mal
La misma pregunta y dos respuestas distintas. La verdadera es 1 😬
La tabla pedidos tiene 24 filas con el id_cliente en NULL. Y NOT IN con una lista que contiene un NULL no devuelve nada nunca. Por dentro es "id distinto de 3 Y distinto de 7 Y distinto de NULL", y esa última comparación es desconocida, así que la cadena entera deja de ser verdadera para todo el mundo.
Y fíjate en la forma que tiene de fallar, que es la peor de todas: no da error, no da un número raro, da cero. Y cero se lee como "qué bien, no hay ningún cliente sin comprar" en vez de como "esta consulta está rota".
La regla que uso: NOT IN solo contra una lista que yo escribí a mano. Contra una subconsulta, siempre NOT EXISTS, que trata los nulos como cualquiera esperaría.
Comprueba que lo tienes
Filtras con WHERE descuento != 15 y esperabas 2.400 filas, pero salen 1.900. Compruebas y hay 500 filas con el descuento vacío.
- Los NULL no son distintos de 15 ni iguales: se caen del filtro
- El != no funciona en SQL, hay que usar <>
- Los nulos cuentan como cero y cero sí es distinto de 15
- Falta un paréntesis en la condición
Lo que te llevas
- 💸 Para dinero,
DECIMALoNUMERIC, nuncaFLOAT. - ➗ Entero entre entero da entero: multiplica por 1.0 o usa
CAST. Un porcentaje que sale cero siempre es esto. - 🕳️
NULLes "no se sabe": cualquier operación con él daNULL. - 🔢
COUNT(*)cuenta filas,COUNT(columna)cuenta valores.AVGignora los nulos. - 🧰
COALESCEfunciona en los cuatro y acepta varios repuestos. - ✂️
CASTa entero trunca, no redondea.
Y si de todo el capítulo te llevas una sola frase, que sea esta:
Un NULL no es cero ni vacío. Es un "no se sabe", y se contagia a todo lo que toca.
Y qué hacer con los nulos no es una pregunta de SQL, es una pregunta de estadística: si los borras cambias el promedio y si los rellenas también. Está contado en el libro de estadística 📐
En el capítulo 5 vamos a texto y fechas, que es donde los cuatro motores más se separan y donde vas a agradecer tener la tabla al lado.
Que tengas lindo día! 🌸
Practica este capítulo 📓
Todo el código de arriba en un cuaderno que corre de principio a fin, y los ejercicios con una celda vacía para que los hagas tú. Se abre en Google Colab de un clic y no hay que instalar nada. Donde veas %%revisa, escribe tu respuesta y el cuaderno te dice si te salió.
¿Prefieres trabajar en tu máquina? Bájate el cuaderno de práctica o el de soluciones. Todos están también en github.com/soymissyera/MissYeraEjercicios.