Una base de datos relacional organiza los datos en tablas: cada tabla es un conjunto de filas, y cada fila tiene el mismo conjunto de columnas con un tipo de dato definido. Dos mecanismos sostienen todo lo demás: la clave primaria, que identifica de forma única cada fila, y la clave foránea, que apunta a la primaria de otra tabla y crea el vínculo. Con esos dos elementos se representan los tres tipos de relación posibles, y el motor pasa a rechazar activamente los datos que romperían la estructura.
Ver índice de contenidos
- 01Por qué se llaman «relacionales»
- 02Tabla, fila, columna
- 03Tipos de dato: la primera restricción
- 04Clave primaria y clave foránea
- 05Los tres tipos de relación
- 06NULL: el valor que no es un valor
- 07Restricciones: reglas que el motor hace cumplir
- 08Índices, en dos párrafos
- 09Errores frecuentes al modelar
- 10Preguntas frecuentes
- 11Fuentes
Por qué se llaman «relacionales»
Vale empezar por acá porque casi todo el mundo lo tiene mal, incluido material educativo bastante difundido. No se llaman relacionales por las relaciones entre tablas. Se llaman así porque «relación» es el nombre matemático de lo que hoy llamamos tabla.
La documentación de PostgreSQL lo dice sin rodeos: es «un sistema para administrar datos guardados en relaciones», y aclara que «relación es esencialmente un término matemático para tabla».
El modelo lo formuló Edgar F. Codd en 1970, mientras trabajaba en IBM, en un artículo que propuso guardar los datos en tablas simples de filas y columnas en lugar de estructuras jerárquicas, y vincular esas tablas por los valores de sus columnas. Más de cincuenta años después sigue siendo el modelo dominante, lo cual dice bastante sobre lo acertada que era la idea.
Más allá de la anécdota, entender que «relación = tabla» aclara vocabulario que vas a encontrar seguido: álgebra relacional, tupla (una fila), atributo (una columna), cardinalidad (cuántas filas). Son los términos formales de los mismos conceptos que ya conocés.
Tabla, fila, columna
La estructura es simple y conviene fijar el vocabulario con precisión, porque después todo se apoya en él.
- Una tabla guarda información sobre un tipo de cosa: clientes, pedidos, productos. Si una tabla guarda dos tipos de cosa distintos, es una señal de que hay que partirla.
- Una fila —o registro, o tupla— es una instancia concreta: un cliente en particular.
- Una columna —o campo, o atributo— es una característica que todas las filas comparten, con un tipo de dato definido.
Hay un detalle que la documentación de PostgreSQL señala y que genera muchos errores silenciosos: las columnas tienen un orden fijo en cada fila, pero las filas no tienen orden garantizado dentro de la tabla. Puede parecer que salen en el orden en que las cargaste, y funcionar así durante meses, hasta el día en que el motor reorganiza los datos o alguien agrega un índice. Si el orden importa, hay que pedirlo con ORDER BY.
Elegí una convención y respetala en todo el esquema: minúsculas, sin tildes ni espacios, palabras separadas por guión bajo, y tablas en plural o en singular pero siempre igual. fecha_alta es mejor que FechaAlta o "Fecha Alta", porque no obliga a poner comillas ni depende de si el motor distingue mayúsculas.
Tipos de dato: la primera restricción
Elegir el tipo de cada columna es la primera decisión de integridad que tomás, y es la que evita el problema de la planilla donde alguien escribió «noventa mil» en la columna de precio.
| Necesitás guardar | Tipo habitual | Nota importante |
|---|---|---|
| Texto de largo acotado | VARCHAR(n) | Poné un límite realista; no uses el máximo «por si acaso». |
| Texto largo o libre | TEXT | Para descripciones, comentarios, contenido. |
| Números enteros | INT, BIGINT | Identificadores, cantidades, contadores. |
| Dinero | DECIMAL(p,s) | Nunca FLOAT: introduce errores de redondeo en las sumas. |
| Fechas y horas | DATE, TIMESTAMP | Nunca texto: perdés poder comparar, ordenar y calcular. |
| Verdadero o falso | BOOLEAN | Mejor que un entero 0/1 o un texto «S»/«N». |
Los dos primeros «nunca» de esa tabla merecen énfasis porque son errores caros y frecuentes. Guardar dinero en un tipo de punto flotante produce diferencias de centavos que aparecen al sumar miles de registros, y son muy difíciles de explicar en una auditoría. Y guardar fechas como texto hace que «10/03/2026» y «2026-03-10» convivan en la misma columna, que ordenar alfabéticamente no coincida con ordenar cronológicamente, y que calcular «cuántos días pasaron» sea imposible sin limpiar todo primero.
Clave primaria y clave foránea
Acá está el mecanismo central del modelo. Dos conceptos, y con ellos se construye cualquier esquema.
Clave primaria
Identifica de forma única cada fila de su tabla. Tiene dos condiciones inseparables: no puede repetirse y no puede estar vacía. Es lo que permite decir «este cliente» sin ambigüedad.
La decisión práctica es qué columna usar, y hay dos escuelas. Una clave natural usa un dato real que ya identifica a la entidad: el número de documento, el correo, el código de producto. Una clave subrogada usa un número sin significado, generado automáticamente por el motor.
El motivo es que cualquier dato real puede cambiar. Un correo mal cargado hay que corregirlo; un código de producto se reorganiza; un número de documento se carga con un dígito de más. Si ese valor es la clave primaria, corregirlo obliga a actualizarlo en todas las tablas que lo referencian, y en el peor caso rompe la integridad. Un id que no significa nada nunca necesita cambiar.
Clave foránea
Es una columna que contiene el valor de la clave primaria de otra tabla. En el diagrama, pedidos.cliente_id guarda el id de un cliente, y eso es todo el vínculo: no hay flechas guardadas en ninguna parte, solo un valor que coincide.
Lo importante es declararla. Podrías guardar el número sin decirle al motor qué significa, y funcionaría. Pero cuando la declarás como clave foránea, el motor empieza a trabajar para vos: rechaza un pedido cuyo cliente_id no exista, y no te deja borrar un cliente que tenga pedidos sin decidir antes qué pasa con ellos. Eso se llama integridad referencial, y es la diferencia entre una base que se cuida sola y una que depende de que la aplicación no tenga errores.
Los tres tipos de relación
Todo modelo, por complejo que sea, se descompone en estos tres casos.
Uno a muchos (1:N)
El más común con diferencia. Un cliente tiene varios pedidos, pero cada pedido pertenece a un solo cliente. La regla mecánica es simple y conviene memorizarla: la clave foránea va siempre en el lado «muchos». Es pedidos la que guarda cliente_id, nunca al revés.
Si intentaras hacerlo al revés —guardar los pedidos en la tabla de clientes— tendrías que poner varios valores en una sola columna, y eso es precisamente lo que el modelo relacional no permite. Esa restricción, que parece una limitación, es la que fuerza el buen diseño.
Muchos a muchos (N:M)
Un pedido lleva varios productos, y un producto aparece en varios pedidos. No se puede representar con una sola clave foránea, así que se crea una tabla intermedia con dos foráneas, una a cada lado.
CREATE TABLE pedido_producto (
pedido_id INT NOT NULL,
producto_id INT NOT NULL,
cantidad INT NOT NULL,
precio_unit DECIMAL(10,2) NOT NULL, -- el precio del día de la compra
PRIMARY KEY (pedido_id, producto_id),
FOREIGN KEY (pedido_id) REFERENCES pedidos(id),
FOREIGN KEY (producto_id) REFERENCES productos(id)
);
Fijate en dos detalles que separan un modelo pensado de uno copiado. El primero: la clave primaria es compuesta por las dos columnas juntas, lo que impide cargar dos veces el mismo producto en el mismo pedido. El segundo, y más interesante: la tabla intermedia tiene columnas propias. La cantidad y el precio unitario no pertenecen al pedido ni al producto: pertenecen a la combinación. Y guardar el precio del momento de la compra es lo que evita que la factura del año pasado cambie cuando actualizás la lista de precios.
Uno a uno (1:1)
El menos frecuente. Cada fila de una tabla se corresponde con una sola de la otra: un usuario y su perfil extendido. Se implementa poniendo una clave foránea con restricción UNIQUE en una de las dos tablas.
Antes de usarlo conviene preguntarse por qué no es una sola tabla. Hay dos razones legítimas: separar datos que casi nunca se consultan para que no pesen en cada lectura, y separar datos sensibles para poder darles permisos distintos. Si no es por una de esas dos, probablemente sean una sola tabla.
NULL: el valor que no es un valor
NULL significa «acá no hay valor». No es cero, no es texto vacío, no es falso: es la ausencia de dato. Y se comporta de una forma que sorprende a todo el mundo la primera vez.
NULL no es igual a NULL. Una condición como WHERE telefono = NULL no devuelve nada, nunca, ni siquiera para las filas que tienen el teléfono vacío. La razón es coherente si se piensa: si no sé un valor y no sé otro, no puedo afirmar que sean iguales. Para preguntar por la ausencia hay que escribir IS NULL o IS NOT NULL.
Eso se propaga a los cálculos: cualquier operación aritmética con un NULL da NULL. Y en las funciones de agregado se ignora, lo que a veces es lo que querés y a veces no: un promedio sobre una columna con nulos promedia solo las filas que tienen valor.
Declará NOT NULL en toda columna que siempre deba tener un valor, y hacelo desde el principio. Relajar la regla después es trivial; limpiar una tabla que acumuló registros incompletos durante un año, no.
Restricciones: reglas que el motor hace cumplir
La documentación de PostgreSQL plantea muy bien por qué hacen falta: los tipos de dato son una restricción, pero «demasiado gruesa». Una columna de precio es numérica, pero nada en el tipo impide que sea negativa. Las restricciones cierran ese hueco.
| Restricción | Qué garantiza | Ejemplo de uso |
|---|---|---|
NOT NULL | La columna siempre tiene un valor | El nombre de un cliente |
UNIQUE | Ningún valor se repite en la columna | El correo de un usuario |
PRIMARY KEY | UNIQUE y NOT NULL a la vez | El id de cualquier tabla |
FOREIGN KEY | El valor existe en la tabla referenciada | pedidos.cliente_id |
CHECK | El valor cumple una condición propia | precio >= 0, edad BETWEEN 0 AND 130 |
DEFAULT | Un valor si no se indica otro | La fecha de alta = hoy |
Hay una objeción habitual: «eso ya lo valida la aplicación». Y la respuesta es que la aplicación no es el único camino a los datos. Hay scripts de migración, cargas masivas, otra aplicación que se conecta a la misma base, y alguien corrigiendo algo a mano un viernes a la tarde. La restricción en la base es la única que se aplica siempre, sin importar quién escriba.
Índices, en dos párrafos
Un índice es una estructura auxiliar que el motor mantiene para encontrar filas rápido, igual que el índice de un libro evita leerlo entero. Sin índice, buscar un cliente por su correo obliga a recorrer toda la tabla; con índice, la búsqueda es casi inmediata incluso con millones de filas.
El precio es que cada índice hace más lenta la escritura, porque hay que actualizarlo en cada inserción o modificación, y ocupa espacio. Las claves primarias y las columnas UNIQUE se indexan automáticamente. Más allá de eso, la regla razonable al empezar es: indexá las columnas por las que buscás y las claves foráneas, y no agregues más «por si acaso». Optimizar índices sin medir es adivinar.
Errores frecuentes al modelar
- No declarar las claves foráneas. Guardar el número sin decirle al motor qué significa desperdicia la única protección real contra datos huérfanos.
- Usar un dato de negocio como clave primaria. El día que hay que corregirlo, el cambio se propaga a todas las tablas que lo referencian.
- Guardar dinero en punto flotante. Los errores de redondeo aparecen al sumar y son imposibles de justificar.
- Guardar fechas como texto. Se pierde ordenar, comparar y calcular, y conviven varios formatos en la misma columna.
- Varios valores en una sola columna. Una columna «teléfonos» con «11-1234, 11-5678» separados por coma es una tabla que falta.
- Columnas numeradas.
producto_1,producto_2,producto_3es siempre una relación uno a muchos disfrazada, y se rompe con el cuarto producto. - Confiar en el orden de las filas. Sin
ORDER BYno hay orden garantizado, aunque durante meses parezca que sí. - Dejar todo aceptando
NULL. Es cómodo al principio y produce una tabla llena de registros a medias.
Preguntas frecuentes
¿Por qué se llaman «relacionales»?
No por las relaciones entre tablas, como suele creerse, sino porque «relación» es el término matemático para lo que hoy llamamos tabla. La documentación de PostgreSQL lo dice de forma directa: es un sistema para administrar datos guardados en relaciones, y relación es esencialmente un término matemático para tabla. El modelo lo formuló Edgar F. Codd en 1970 mientras trabajaba en IBM. El nombre viene de la teoría de conjuntos, no de los vínculos entre tablas.
¿Cuál es la diferencia entre clave primaria y clave foránea?
La clave primaria identifica de forma única cada fila dentro de su propia tabla: no puede repetirse ni estar vacía. La clave foránea es una columna que apunta a la clave primaria de otra tabla, y es lo que crea el vínculo entre ambas. En un ejemplo de clientes y pedidos, el id de clientes es la primaria, y la columna cliente_id de pedidos es la foránea que apunta a ese id. La primaria dice quién soy; la foránea dice a quién pertenezco.
¿Conviene usar un id numérico o un dato real como clave primaria?
En general conviene un id numérico sin significado de negocio, lo que se llama clave subrogada. El motivo es práctico: cualquier dato real puede cambiar. Un correo electrónico, un número de documento o un código de producto parecen estables hasta que hay que corregir uno cargado con error, y si ese valor es la clave primaria, hay que actualizarlo en todas las tablas que lo referencian. Un id que no significa nada nunca necesita cambiar.
¿Cómo se representa una relación muchos a muchos?
Con una tercera tabla intermedia, porque no se puede representar con una sola clave foránea. Si un pedido lleva varios productos y un producto aparece en varios pedidos, se crea una tabla que suele llamarse pedido_producto, con dos claves foráneas: una al pedido y otra al producto. Esa tabla intermedia además suele ganar columnas propias muy útiles, como la cantidad pedida y el precio al momento de la compra, que no pertenecen ni al pedido ni al producto sino a la combinación de ambos.
¿Qué significa NULL y por qué da problemas?
NULL significa «no hay valor», y no es lo mismo que cero ni que texto vacío. Da problemas porque se comporta de forma distinta en las comparaciones: NULL no es igual a NULL, así que una condición como columna = NULL nunca se cumple. Para preguntar por su ausencia hay que escribir IS NULL o IS NOT NULL. La recomendación práctica es declarar NOT NULL en toda columna que siempre deba tener un valor: es más fácil relajar la regla después que limpiar datos incompletos.
¿Las filas de una tabla tienen un orden?
No, y suponer que sí es una de las causas más comunes de errores silenciosos. La documentación de PostgreSQL lo aclara: mientras las columnas tienen un orden fijo en cada fila, SQL no garantiza el orden de las filas dentro de una tabla. Puede coincidir con el orden de carga durante un tiempo y cambiar el día que el motor reorganiza los datos o se agrega un índice. Si el orden importa, hay que pedirlo explícitamente con ORDER BY.
Fuentes
Documentación oficial y referencias consultadas para esta guía. Fecha de consulta: 27 de julio de 2026.
- PostgreSQL. Concepts. «Relación» como término matemático para tabla, estructura de filas y columnas, y la aclaración de que SQL no garantiza el orden de las filas.
- PostgreSQL. Constraints. Por qué los tipos de dato son una restricción «demasiado gruesa» y qué agrega cada restricción disponible.
- PostgreSQL. Data Types. Tipos disponibles y sus características.
- Codd, E. F. «A Relational Model of Data for Large Shared Data Banks». Communications of the ACM, vol. 13, n.º 6, 1970, pp. 377-387. Artículo original que formuló el modelo relacional. Publicación con acceso restringido; se cita como referencia bibliográfica.
- Underc0de, foro. Sección Base de Datos. Material de la comunidad sobre bases de datos y SQL.