Elementos básicos del modelo relacional
Las tablas: estructuras, no listas caóticas
En el modelo relacional, la tabla es la unidad estructural fundamental.
No es una lista cualquiera: es una representación matemática de una relación.
Cada tabla debe tener:
- Un nombre único.
- Un conjunto de columnas (atributos) que definen el tipo de datos.
- Un conjunto de filas (tuplas) que representan instancias de esa relación.
Ejemplo conceptual (tabla Producto):
| id_producto | nombre | precio | stock |
|---|---|---|---|
| 1 | Teclado | 20.00 | 15 |
| 2 | Ratón | 10.00 | 30 |
| 3 | Monitor 24" | 120.00 | 5 |
Cada fila representa un producto concreto.
Cada columna representa una propiedad bien definida.
Importante:
En el modelo relacional:
- No hay orden garantizado de filas ni columnas.
- Cada fila debe ser única en su clave.
- El orden visual no significa nada para el motor.
Las columnas: atributos y dominios
Las columnas no solo tienen nombre, también tienen:
- Un dominio: el conjunto de valores permitidos.
- Una restricción semántica: qué representan.
Ejemplo:
Columna precio podría tener un dominio numérico positivo (por ejemplo, DECIMAL).
Columna nombre pertenece a un dominio de texto limitado.
Esto evita errores como:
- Introducir texto en una columna que espera un número.
- Guardar precios negativos.
- Duplicar tipos incompatibles.
Por eso, en un buen diseño relacional, los dominios se definen con cuidado, no se dejan “abiertos”.
Las filas: instancias de la relación
Cada fila (tupla) representa una instancia única del concepto modelado.
Si tienes una tabla Cliente, cada fila es un cliente distinto.
Ejemplo:
| id_cliente | nombre | correo |
|---|---|---|
| 1 | Ana | ana@example.com |
| 2 | Luis | luis@example.com |
| 3 | Sara | sara@example.com |
Cada fila está definida de forma precisa y verificable.
Si duplicas una fila completa sin control… ya rompiste una de las bases del modelo: unicidad de instancias.
Claves: identidad y consistencia
En el modelo relacional toda tabla debe tener una clave primaria que identifique unívocamente cada fila.
Esta clave es la identidad lógica de la instancia.
Tipos de claves más comunes:
-
Clave primaria (PK): la identidad única de la fila.
Ej:
id_cliente. -
Clave alternativa (AK): otra columna que también podría identificar (por ejemplo, un
correoúnico). -
Clave compuesta: combinación de columnas que juntas identifican una fila única.
Ej: (id_producto, id_pedido) en una tabla de detalle de pedidos.
Ejemplo:
| id_cliente (PK) | correo | nombre |
|---|---|---|
| 1 | ana@example.com | Ana |
| 2 | luis@example.com | Luis |
Aquí id_cliente es la PK. Si alguien intenta insertar otra fila con id_cliente = 1, debe fallar.
Esto no es un capricho: sin clave, no puedes relacionar tablas de forma segura.
Valores nulos (NULL): ausencia, no cero
Un valor NULL no es “0” ni “cadena vacía”.
Significa literalmente: “valor desconocido o no aplicable”.
Ejemplo:
| id_cliente | nombre | telefono |
|---|---|---|
| 1 | Ana | 611000000 |
| 2 | Luis | NULL |
Luis no tiene teléfono registrado. Esto no es un error, es un estado válido que hay que manejar con cuidado.
Muchos principiantes caen en este error: confundir NULL con “vacío”.
Esto puede llevar a consultas incorrectas y bugs difíciles de rastrear.
Ejercicio práctico guiado — Representar relaciones con estructuras planas
Este ejercicio sigue siendo agnóstico de motor, usando archivos CSV para reforzar los conceptos de tabla, fila, columna, clave y NULL.
Estructura de carpetas:
proyecto-relacional/
│
├── datos/
│ ├── clientes.csv
│ ├── productos.csv
│ ├── pedidos.csv
│
├── scripts/
│ └── claves.js
└── README.md
clientes.csv:
id_cliente,nombre,correo,telefono
1,Ana,ana@example.com,611000000
2,Luis,luis@example.com,
3,Sara,sara@example.com,612000000
Nota el valor vacío en la columna telefono de Luis: eso representa NULL.
productos.csv:
id_producto,nombre,precio
1,Teclado,20.00
2,Ratón,10.00
3,Monitor 24",120.00
pedidos.csv:
id_pedido,id_cliente,id_producto,cantidad
A001,1,1,2
A002,1,3,1
A003,2,2,4
Código ejemplo claves.js (JavaScript)
Vamos a:
- Leer los tres CSV.
- Verificar que no haya duplicados en las claves primarias.
- Validar que todos los pedidos referencian clientes y productos existentes.
import fs from "fs";
// Importamos el módulo nativo "fs" de Node.js.
// Este módulo permite trabajar con el sistema de archivos: leer, escribir,
// borrar, etc. Aquí lo usaremos para leer archivos CSV desde disco.
// Función genérica para leer un archivo CSV y convertirlo en un array de objetos
function leerCSV(ruta) {
// "ruta" es la ruta al archivo CSV, por ejemplo "./datos/clientes.csv".
const data = fs.readFileSync(ruta, "utf-8").trim();
// Leemos el archivo completo de forma síncrona (bloqueante) usando readFileSync.
// El segundo parámetro "utf-8" indica que queremos el contenido como texto.
// Luego usamos ".trim()" para eliminar espacios o saltos de línea sobrantes
// al principio o al final del archivo que podrían causar líneas vacías.
const [cabecera, ...filas] = data.split("\n");
// Dividimos el archivo en líneas usando el carácter de salto de línea "\n".
// La primera línea del CSV suele ser la cabecera, con los nombres de las columnas.
// Usamos destructuring:
// - "cabecera" contendrá la primera línea (ej: "id_cliente,nombre,email")
// - "filas" será un array con el resto de líneas, una por cada registro.
const campos = cabecera.split(",");
// Convertimos la cabecera en un array de nombres de columna.
// Ejemplo: "id_cliente,nombre,email" → ["id_cliente", "nombre", "email"].
// Estos nombres de campo se usarán como claves de los objetos que devolvemos.
return filas.map(fila => {
// Recorremos cada línea de datos (cada "fila" representa un registro).
const valores = fila.split(",");
// Separamos la línea por comas para obtener los valores de cada columna.
// Ejemplo: "1,Ana,ana@mail.com" → ["1", "Ana", "ana@mail.com"].
return Object.fromEntries(
valores.map((v, i) => [campos[i], v])
);
// Aquí convertimos los valores en un objeto con pares clave-valor.
//
// "valores.map((v, i) => [campos[i], v])" crea un array de tuplas:
// [
// ["id_cliente", "1"],
// ["nombre", "Ana"],
// ["email", "ana@mail.com"]
// ]
//
// Object.fromEntries toma ese array de pares [clave, valor] y lo convierte en:
// { id_cliente: "1", nombre: "Ana", email: "ana@mail.com" }
//
// De esta manera, cada fila del CSV se representa como un objeto JS
// con propiedades con nombre, lo que facilita mucho trabajar con los datos.
});
}
// Leemos los distintos archivos CSV de nuestro "pequeño sistema"
const clientes = leerCSV("./datos/clientes.csv");
// "clientes" será un array de objetos, cada uno representando un cliente.
// Ejemplo de un elemento: { id_cliente: "1", nombre: "Ana", ... }
const productos = leerCSV("./datos/productos.csv");
// "productos" será un array de objetos, cada uno representando un producto.
// Ejemplo: { id_producto: "10", nombre: "Teclado", precio: "25.99", ... }
const pedidos = leerCSV("./datos/pedidos.csv");
// "pedidos" será un array de objetos, cada uno representando un pedido.
// Ejemplo: { id_pedido: "100", id_cliente: "1", id_producto: "10", ... }
// 1. Verificar duplicados en claves primarias
// -------------------------------------------------------------
// En una base de datos, las "claves primarias" (primary keys) deben ser únicas.
// Esta función comprueba que en un conjunto de datos no se repita el valor
// de un campo que hemos decidido usar como clave primaria.
// Por ejemplo, que no haya dos clientes con el mismo id_cliente.
function verificarClaveUnica(datos, campoClave) {
// "datos" es un array de objetos (clientes, productos o pedidos).
// "campoClave" es el nombre del campo que debe ser único (ej: "id_cliente").
const claves = new Set();
// Usamos un Set para almacenar valores sin permitir duplicados.
// Un Set es una estructura que solo guarda valores únicos.
for (const fila of datos) {
// Recorremos cada objeto (fila) de la colección de datos.
const valorClave = fila[campoClave];
// Obtenemos el valor de la clave primaria para esta fila.
// Ejemplo: fila["id_cliente"] → "1".
if (claves.has(valorClave)) {
// Si el Set ya contiene este valor, significa que ya lo hemos visto antes,
// por lo tanto existe un duplicado de la clave primaria.
console.error(
`Duplicado detectado en ${campoClave}: ${valorClave}`
);
// Mostramos un mensaje de error indicando qué campo y qué valor están duplicados.
} else {
// Si el valor aún no existía, lo añadimos al Set para marcarlo como "ya visto"
// y poder detectar futuros duplicados.
claves.add(valorClave);
}
}
// Al final del bucle, si no se ha mostrado ningún error, significa que
// todas las claves de ese campo son únicas en el conjunto de datos.
}
// Llamamos a la función de verificación para cada conjunto de datos
verificarClaveUnica(clientes, "id_cliente");
// Comprueba que no haya dos clientes con el mismo id_cliente.
verificarClaveUnica(productos, "id_producto");
// Comprueba que no haya dos productos con el mismo id_producto.
verificarClaveUnica(pedidos, "id_pedido");
// Comprueba que no haya dos pedidos con el mismo id_pedido.
// 2. Validar integridad de referencias
// -------------------------------------------------------------
// Aquí comprobamos la "integridad referencial".
// Es decir, verificamos que los pedidos apunten a clientes y productos que existen.
// En una base de datos relacional real, esto se hace con claves foráneas (foreign keys).
// Nosotros lo simulamos a mano con Sets y bucles.
const idsClientes = new Set(clientes.map(c => c.id_cliente));
// Creamos un Set con todos los id_cliente existentes.
// clientes.map(c => c.id_cliente) produce un array de ids, por ejemplo: ["1", "2", "3"].
// El Set nos permite consultar de forma rápida si un id existe.
const idsProductos = new Set(productos.map(p => p.id_producto));
// Hacemos lo mismo para los productos: un Set con todos los id_producto válidos.
for (const pedido of pedidos) {
// Recorremos cada pedido para comprobar que sus referencias son correctas.
if (!idsClientes.has(pedido.id_cliente)) {
// Si el id_cliente del pedido no está en el Set de clientes válidos,
// significa que este pedido apunta a un cliente que no existe.
console.error(
`Pedido ${pedido.id_pedido} referencia un cliente inexistente`
);
}
if (!idsProductos.has(pedido.id_producto)) {
// De manera similar, comprobamos si el id_producto del pedido existe
// en el conjunto de productos. Si no existe, hay un problema de integridad.
console.error(
`Pedido ${pedido.id_pedido} referencia un producto inexistente`
);
}
// Con estas comprobaciones estamos emulando lo que sería
// una restricción de clave foránea en una base de datos real:
// no se permite guardar un pedido que haga referencia a un cliente o producto
// que no esté registrado.
}
console.log("Verificación completada");
// Mensaje final indicando que el proceso de verificación ha terminado.
// Los posibles problemas ya se habrán mostrado en la consola con console.error.
Salida esperada (si todo está bien):
Verificación completada
Lo que hiciste manualmente aquí es:
- Definir claves primarias (sin duplicados).
- Usar claves foráneas (referencias entre tablas).
- Manejar valores NULL correctamente.
Esto es exactamente lo que un motor relacional hace por ti cuando defines la estructura correctamente.
Buenas prácticas en la definición de elementos básicos
- Define siempre una clave primaria clara y estable.
- Evita claves que cambien con el tiempo (por ejemplo, correos).
- Usa NULL solo cuando signifique realmente “desconocido” o “no aplica”.
- Usa nombres de columnas consistentes (ej.
id_clienteen todas las tablas). - Mantén los dominios de datos coherentes: no mezcles tipos.
Errores comunes de principiantes
- Usar el nombre del usuario como clave → cambia con el tiempo.
- Dejar columnas “abiertas” → datos inconsistentes.
- No usar NULL correctamente → falsas igualdades.
- Repetir información en múltiples tablas → duplicidad y caos.