Relaciones
La mayoría de apps tienen tablas que se conectan entre sí: un order pertenece a un user, un product tiene muchos reviews, un project tiene muchos members. Estas conexiones se llaman claves foráneas — una columna en una tabla que referencia una fila en otra.
Crear una clave foránea
No hay un tipo de columna visual "Reference" — las claves foráneas se definen en SQL (en el editor SQL) o pidiéndoselo a tu IA, que las aplica como una migración MCP.
El patrón es una columna normal más una cláusula REFERENCES:
-- Añade la FK como parte del CREATE TABLE
CREATE TABLE public.orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id TEXT NOT NULL REFERENCES system.users(id) ON DELETE CASCADE,
total NUMERIC(10,2) NOT NULL
);
-- ...o añádela a una tabla existente
ALTER TABLE public.orders
ADD CONSTRAINT orders_user_id_fkey
FOREIGN KEY (user_id) REFERENCES system.users(id) ON DELETE CASCADE;
Algunas reglas al montar una clave foránea:
- Nombra la columna por lo que apunta —
user_id,product_id. - Coincide con el tipo de la clave primaria destino — normalmente
uuid. Excepción: si referencia a un usuario, usatext(ver Tablas y Schema). - Elige un comportamiento
ON DELETE— qué pasa cuando se borra la fila referenciada (ver abajo).
Deja que lo haga tu IA
"Añade una clave foránea user_id en orders que borre en cascada." — tu cliente de IA escribe el ALTER TABLE y lo aplica como migración por ti.
Comportamiento ON DELETE
Cuando borras una fila que otras referencian, Postgres necesita saber qué hacer con los referentes:
| Opción | Qué hace | Úsala para |
|---|---|---|
| CASCADE | Borra también las filas referentes | Las tareas de un usuario, los comentarios de un post, los mensajes de una sesión |
| SET NULL | Mantiene los referentes, pone el FK a NULL | Un autor en un post — si el autor se va, conserva el post |
| RESTRICT | Bloquea el borrado del todo | Productos referenciados por pedidos — nunca borres un producto si está en el historial de alguien |
| NO ACTION | Igual que RESTRICT en la mayoría de casos | Rara vez usado |
El default más seguro para datos de usuario es CASCADE. Cuando el usuario se borra, sus tareas se van con él.
Uno-a-muchos
El caso común: un usuario tiene muchas tareas, un post tiene muchos comentarios. Pon la clave foránea en el lado muchos:
CREATE TABLE public.posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
user_id TEXT NOT NULL REFERENCES system.users(id) ON DELETE CASCADE
);
CREATE TABLE public.comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
post_id UUID NOT NULL REFERENCES public.posts(id) ON DELETE CASCADE,
content TEXT NOT NULL
);
Un post tiene muchos comentarios. La columna post_id vive en comments.
Muchos-a-muchos
Cuando ambos lados pueden tener muchos del otro — un usuario puede estar en muchos proyectos, un proyecto tiene muchos usuarios — usa una tabla pivote (también llamada tabla de unión):
CREATE TABLE public.project_members (
project_id UUID NOT NULL REFERENCES public.projects(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES system.users(id) ON DELETE CASCADE,
role TEXT NOT NULL DEFAULT 'member',
joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (project_id, user_id)
);
La clave primaria es la combinación de ambas columnas — un usuario solo puede estar en un proyecto una vez. Añade columnas extra (role, joined_at) en la tabla pivote cuando necesites metadata sobre la relación.
Consultar a través de relaciones
Para obtener datos de tablas relacionadas en una sola petición, usa un SQL JOIN en el nodo database de tu workflow:
SELECT
posts.id,
posts.title,
COUNT(comments.id) AS comment_count
FROM public.posts
LEFT JOIN public.comments ON comments.post_id = posts.id
WHERE posts.user_id = ${current_user_id}
GROUP BY posts.id
ORDER BY posts.created_at DESC;
Ver API Builder para cómo usar SQL custom dentro de un workflow.