SQL · Fundamentos · 6 min de lectura

Cómo se construye una base de datos

Antes de la primera consulta: qué es de verdad una tabla, para qué sirven las claves y por qué una tienda guarda sus clientes, pedidos y productos en tablas separadas que los joins vuelven a juntar.

La idea

Una base de datos relacional es un conjunto de tablas. Cada tabla guarda un tipo de cosa: clientes, pedidos, productos. Cada fila es una de esas cosas, y cada columna es un dato sobre ella: la ciudad de un cliente, la fecha de un pedido, el precio de un producto.

Suena a hoja de cálculo, y casi lo es. La diferencia es la regla que hay detrás: cada dato se guarda una sola vez, en la tabla a la que pertenece, y las tablas se apuntan entre sí con claves. Luego una consulta reúne los datos que necesita desde donde estén.

Por qué existe

Imagina que Harbor & Pine funciona con una gran hoja: una fila por línea de pedido, con el nombre y la ciudad del cliente escritos a mano, más el producto y su categoría. Funciona durante una semana.

Figura 1

De una gran hoja a tablas separadas

Una gran hoja
order_idfirst_namecityproduct_idcategory
1231GaryChicago165Textiles
1231GaryChicago129Lighting
1734CharlesLas Vegas166Textiles
4230GaryChicago135Lighting
4230GaryChicago191Decor
4230GaryChicago177Textiles
customers
customer_idfirst_namecity
69GaryChicago
101CharlesLas Vegas
orders
order_idcustomer_id
123169
1734101
423069
order_items
order_idproduct_id
1231165
1231129
1734166
4230135
4230191
4230177

Paso 1 de 6 Seis líneas de pedido guardadas al estilo hoja de cálculo: una fila por línea, con todo lo del pedido y del cliente escrito en ella.

  1. Seis líneas de pedido guardadas al estilo hoja de cálculo: una fila por línea, con todo lo del pedido y del cliente escrito en ella.
  2. El nombre y la ciudad de Gary están guardados cinco veces. El pedido 4230 está guardado tres veces. La hoja repite un dato cada vez que lo necesita.
  3. Gary se muda a Denver. Hay que cambiar cinco celdas. Si se te escapa una, la hoja dice que vive en dos ciudades a la vez, y cada informe que agrupa por ciudad queda mal sin que nadie lo note.
  4. Divídela. Cada cliente se convierte en una fila de customers, con un customer_id que nunca cambia.
  5. Cada pedido se convierte en una fila de orders, que guarda solo el customer_id de quien lo hizo.
  6. Las líneas van a order_items, una fila por producto de un pedido. Ahora cada dato vive en un solo lugar, y la mudanza a Denver es una única actualización.

El problema de la hoja no es su tamaño. Es que el mismo dato está en muchas celdas, y las copias se van separando. Alguien corrige una errata en una fila y no en las demás. Un cliente cambia su correo y la mitad de su historial se queda con el viejo. Borrar el último pedido de un cliente borra el único registro de que ese cliente existe. Esas tres fallas tienen nombre (anomalías de actualización, de inserción y de borrado), y todas vienen de guardar un dato dos veces.

Dividir los datos en tablas arregla las tres de una vez, porque solo hay una copia que mantener correcta. El precio es que la respuesta a una pregunta ahora está repartida en varias tablas. Para eso sirve JOIN, y por eso tiene un capítulo entero.

Cómo funciona por dentro

Tablas, filas y columnas

Una tabla tiene una lista fija de columnas, cada una con un tipo: números enteros para quantity, decimales para unit_price, fechas para order_date, texto para city. Cada fila tiene un valor (o NULL, "sin valor") en cada columna. Las filas no tienen un orden propio. "La primera fila" no significa nada hasta que una consulta dice ORDER BY.

La granularidad (grain) de una tabla es lo que representa una fila. En orders es un pedido; en order_items, una línea de un pedido; en web_sessions, una visita al sitio. Conocer la granularidad te dice qué cuenta COUNT(*) y qué números se pueden sumar sin problema.

Claves

Una clave primaria es la columna que distingue las filas entre sí. No hay dos filas que la compartan y nunca cambia: customer_id en customers, order_id en orders. Los nombres son malas claves (hay tres clientes distintos que se llaman Michael Chavez); los ids están hechos para ser claves.

Una clave foránea es una columna que guarda la clave primaria de otra tabla. orders.customer_id dice qué cliente hizo el pedido. Es el hilo que sigue un JOIN.

Figura 2

Claves primarias y claves foráneas

order_items
order_item_idorder_idproduct_idunit_price
601123116525.99
6021231129109.00
1828173416626.99
82304230135149.00
products
product_idcategoryunit_price
129Lighting109.00
135Lighting159.00
165Textiles25.99
166Textiles28.99

Paso 1 de 4 Toda tabla tiene una clave primaria: una columna cuyo valor es distinto en cada fila. order_item_id identifica exactamente una línea, product_id exactamente un producto.

  1. Toda tabla tiene una clave primaria: una columna cuyo valor es distinto en cada fila. order_item_id identifica exactamente una línea, product_id exactamente un producto.
  2. order_items.product_id es una clave foránea: guarda la clave primaria de un producto. Si la sigues desde una línea, siempre llegas a un solo producto.
  3. Si la sigues en sentido contrario, un producto puede tener muchas líneas. Eso es una relación uno a muchos, la forma más común en cualquier base de datos.
  4. No toda columna repetida es un error. unit_price en la línea es lo que el cliente pagó ese día; products.unit_price es el precio de lista de hoy. La línea 8230 pagó 149.00 por una lámpara que ahora cuesta 159.00.

La base de datos puede hacer cumplir las dos. Una restricción de clave primaria rechaza un id duplicado; una restricción de clave foránea rechaza un pedido de un cliente que no existe. Los analistas casi nunca las crean, pero saber que existen te dice en qué puedes confiar. Si order_id es la clave primaria de orders, un join con orders por order_id nunca puede devolver más de un pedido por línea.

NULL: el valor que no está

NULL no es cero ni un texto vacío. Significa "sin valor": un cliente que no dijo cómo conoció la tienda tiene referral_source en NULL, y un producto cuyo costo se desconoce tiene unit_cost en NULL. Como el valor es desconocido, cualquier comparación con él también lo es. NULL = NULL no es verdadero, y WHERE referral_source = 'search' se salta las filas con NULL sin avisar. Para pedirlas usas IS NULL. Las agregaciones también se las saltan: AVG(unit_cost) promedia solo los productos que tienen costo, y COUNT(unit_cost) cuenta solo esos, mientras que COUNT(*) cuenta todas las filas. Cuando un número parece demasiado pequeño o demasiado grande, los NULL son el primer lugar donde mirar.

La normalización, en palabras sencillas

Normalización es el nombre que recibe diseñar las tablas para que cada dato se guarde una sola vez. Los libros definen "formas normales"; en la práctica se reducen a tres preguntas sobre cada columna:

  1. Un valor por celda. Una columna guarda una sola cosa, no una lista. Tres productos en una celda ("165, 129, 177") son señal de que esos datos necesitan su propia tabla, aquí order_items.
  2. Sobre la clave completa. Una columna describe la clave de la fila y nada más. La categoría de un producto depende del producto, no de la línea de pedido, así que vive en products.
  3. Nada más que la clave. Si una columna se puede deducir de otra columna que no es clave (una ciudad a partir del cliente), pertenece a esa otra tabla.

Sigue las tres y obtienes la estructura de la tienda.

Figura 3

La tienda Harbor & Pine, tabla por tabla

customers

  • customer_id
  • first_name
  • city
  • state

orders

  • order_id
  • customer_id
  • order_date
  • status

order_items

  • order_item_id
  • order_id
  • product_id
  • quantity

products

  • product_id
  • name
  • category

returns

  • return_id
  • order_item_id
  • reason

Paso 1 de 4 Cinco de las tablas de la tienda. Cada línea es una clave: el extremo 1 guarda la clave primaria y el extremo * la guarda como clave foránea.

  1. Cinco de las tablas de la tienda. Cada línea es una clave: el extremo 1 guarda la clave primaria y el extremo * la guarda como clave foránea.
  2. Un cliente, muchos pedidos: orders.customer_id apunta a customers.
  3. Un pedido, muchas líneas; un producto, muchas líneas. order_items está en el medio y conecta los pedidos con los productos.
  4. Una devolución pertenece a una línea de pedido. Para ir de una devolución a un cliente recorres las claves: returns → order_items → orders → customers. Ese recorrido es una cadena de JOINs.

Las bases de datos reales rompen las reglas a propósito. order_items.unit_price repite un precio porque el precio del día de la venta es un dato distinto del precio de lista de hoy. Las herramientas de informes van más allá y reconstruyen tablas anchas (un "esquema en estrella", que se ve en la ruta de Power BI) porque leer es más fácil cuando hacen falta menos joins. Son decisiones tomadas teniendo en cuenta las reglas, no en lugar de ellas.

Errores comunes

"Una base de datos es una gran hoja de cálculo"

Una hoja de cálculo es una cuadrícula de celdas en la que puedes poner cualquier cosa. Una tabla es un conjunto de filas que tienen todas las mismas columnas, con tipos y claves. Eso es lo que permite a una base de datos responder preguntas sobre millones de filas y mantenerlas coherentes mientras mucha gente escribe en ella.

"Dividir los datos hace perder información"

No se pierde nada: las claves conservan cada conexión. Cualquier versión de la gran hoja se puede reconstruir con joins cuando una pregunta lo necesite, y siempre estará al día, porque se construye a partir de la única copia de cada dato.

"Las filas se guardan en el orden en que las ves"

Las tablas no tienen orden. Dos ejecuciones de la misma consulta sin ORDER BY pueden devolver las filas en otro orden, y las dos son correctas. Cuando el orden importa para una respuesta, la consulta tiene que pedirlo.

Ponte a prueba

Una pregunta. Al responderla, la lección queda marcada en el mapa; nunca bloquea un nivel.

Harbor & Pine cambia el nombre de la categoría de producto Decor a Home Decor. En la base de datos de la tienda, ¿cuántas filas tienen que cambiar?

Elige qué puede ejecutarse en este sitio. Puedes cambiarlo cuando quieras desde "Configurar cookies", al pie de cada página.