# Tabla puente
También llamada _tabla intermedia_, _junction table_ o _entidad asociativa_.
Considere que los ejemplos descritos parten del caso de Universidad que hemos trabajo en clase.
## Qué es y para qué sirve
- Es una **tabla que resuelve una relación muchos-a-muchos (N:M)** entre dos (o más) entidades del modelo.
- Convierte esa relación N:M en **dos relaciones 1:N**, garantizando integridad y evitando duplicación.
- Además de enlazar, puede **almacenar atributos propios de la relación** (p. ej., fecha de inscripción, rol, nota).
## Cuándo la necesitas
- Cuando A ↔ B tiene cardinalidad **N:M**:
    - _Student_ ↔ _Course_ (un estudiante cursa muchos cursos y un curso tiene muchos estudiantes) → **Enrollment**.
    - _Libro_ ↔ _Autor_ → **BookAuthor**.
    - **Auto-relación** N:M (seguidores en redes): _User_ ↔ _User_ → **UserFollow**.
- En **relaciones ternarias** (A–B–C) que requieren mantener una **combinación única de tres claves** (p. ej., _Student–Course–Semester_).
## Estructura típica
Una tabla puente suele contener:
1. **Claves foráneas** a las entidades que vincula (NOT NULL).
2. **Clave primaria** que **garantiza unicidad** de la pareja (o tercia) vinculada.
3. **Atributos de la relación** (opcionales).
4. **Restricciones** para reflejar reglas de negocio (UNIQUE, CHECK, ON DELETE…).
### Dos patrones de clave primaria
- **PK compuesta** (recomendada si solo enlazas y no necesitas referenciar la fila desde otros lados):
    - PK = `(fk_a, fk_b)` y `UNIQUE` implícita por la PK.
- **PK surrogate (id autoincremental)** + `UNIQUE(fk_a, fk_b)`:
    - Útil si la fila tendrá **muchos atributos**, si habrá **FK externas** apuntando a este enlace, o **historial**.
## Ejemplo (MySQL/MariaDB) en el caso de Universdiad: 
Student ↔ Course → Enrollment
```sql
-- Opción A: PK compuesta
CREATE TABLE enrollment (
  student_id INT NOT NULL,
  course_id  INT NOT NULL,
  enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  grade TINYINT,
  PRIMARY KEY (student_id, course_id),   -- unicidad del par
  CONSTRAINT fk_enr_student FOREIGN KEY (student_id) REFERENCES student(student_id)
    ON DELETE CASCADE,
  CONSTRAINT fk_enr_course  FOREIGN KEY (course_id)  REFERENCES course(course_id)
    ON DELETE RESTRICT
);
-- Opción B: surrogate key + UNIQUE
CREATE TABLE enrollment (
  enrollment_id INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT NOT NULL,
  course_id  INT NOT NULL,
  enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  grade TINYINT,
  UNIQUE (student_id, course_id),     -- evita duplicados
  FOREIGN KEY (student_id) REFERENCES student(student_id),
  FOREIGN KEY (course_id)  REFERENCES course(course_id)
);
-- Índices útiles (según consultas más frecuentes)
CREATE INDEX idx_enr_course_student ON enrollment (course_id, student_id);
CREATE INDEX idx_enr_student_course ON enrollment (student_id, course_id);
```
## Cómo llega desde el E-R al relacional
- En el **modelo E-R** trazas una **relación N:M** entre _STUDENT_ y _COURSE_.
- En el **modelo relacional**, esa relación se **mapea** a una **tabla asociativa** con las FKs a cada entidad.
- Si la relación tiene atributos (p. ej., _grade_), **van en la tabla puente** (no en _student_ ni _course_).
## Reglas de negocio típicas (y cómo implementarlas)
- **“Un estudiante no puede inscribirse dos veces al mismo curso”** → PK compuesta o `UNIQUE(student_id, course_id)`.
- **“Si borro un estudiante, borro sus inscripciones”** → `ON DELETE CASCADE` en `fk_enr_student`.
- **“Si un curso tiene historial, no se puede borrar”** → `ON DELETE RESTRICT` en `fk_enr_course`.
- **“La nota debe ser 0–100”** → `CHECK (grade BETWEEN 0 AND 100)`.
## Buenas prácticas
- **Nombre descriptivo**: `enrollment`, `book_author`, `user_follow` (o `student_course` si prefieres compuesto).
- **FKs NOT NULL** (la relación no existe sin ambos lados).
- **Unicidad** del par/terna con PK compuesta o `UNIQUE`.
- **Índices** alineados a los JOIN y filtros más usados.
- **Evitar datos derivados** en la puente (p. ej., totales que se pueden calcular).
- **Transacciones** al insertar desde múltiples sesiones para que el `UNIQUE` evite “doble vínculo” por carrera de concurrencia.
## ¿Es una entidad débil?
- No por definición. Es una **entidad asociativa**.
- Puede _comportarse como débil_ si su identidad depende completamente de las FKs y no tiene clave propia; aun así, en la práctica la nombramos **tabla puente** o **entidad asociativa** para N:M.
Aquí va una explicación clara y práctica de **qué es un constraint** en bases de datos relacionales (MySQL/MariaDB, PostgreSQL, etc.):
# Constraint
## Qué es un constraint
Un **constraint** (restricción) es una **regla que el SGBD hace cumplir automáticamente** para proteger la **integridad de los datos**.  
Si una instrucción `INSERT`/`UPDATE` viola la regla, **la operación falla** y la base **no queda en un estado inválido**.
Tres grandes objetivos que cubren los constraints:
- **Dominio**: que cada columna tenga valores válidos (tipo, rango, obligatoriedad).
- **Entidad**: que cada fila sea identificable de forma única.
- **Referencia**: que las relaciones entre tablas se mantengan coherentes.
## Tipos de constraints (estándar SQL)
1. **NOT NULL** – prohíbe valores nulos en una columna.
2. **UNIQUE** – asegura que **no se repitan** valores (puede ser de **una o varias columnas**).
3. **PRIMARY KEY** – identifica de forma única a cada fila (implícitamente `NOT NULL` + `UNIQUE`).
4. **FOREIGN KEY** – obliga a que un valor exista **previamente** en la tabla referenciada (integridad referencial).
5. **CHECK** – valida una **condición booleana** (rango, patrón, consistencia simple).
> Nota de versiones: **CHECK** se **enfuerza** en **MySQL ≥ 8.0.16** y **MariaDB ≥ 10.2.x**. En versiones más antiguas se ignoraba o requería alternativas (triggers).
## Ejemplos rápidos (MariaDB/MySQL)
### A. Definir constraints al crear tablas
```sql
CREATE TABLE student (
  student_id   INT AUTO_INCREMENT,
  student_code VARCHAR(20) NOT NULL,         -- NOT NULL
  email        VARCHAR(255) UNIQUE,          -- UNIQUE
  PRIMARY KEY (student_id),                  -- PRIMARY KEY
  CONSTRAINT uq_student_code UNIQUE (student_code),   -- UNIQUE nombrado
  CONSTRAINT chk_code_len CHECK (CHAR_LENGTH(student_code) >= 3) -- CHECK
) ENGINE=InnoDB;
CREATE TABLE course (
  course_id INT AUTO_INCREMENT PRIMARY KEY,
  code      VARCHAR(20) NOT NULL UNIQUE,
  credits   TINYINT NOT NULL,
  CONSTRAINT chk_credits CHECK (credits BETWEEN 1 AND 10)
) ENGINE=InnoDB;
CREATE TABLE enrollment (
  student_id INT NOT NULL,
  course_id  INT NOT NULL,
  enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (student_id, course_id),                 -- UNIQUE del par
  CONSTRAINT fk_enr_student FOREIGN KEY (student_id)
    REFERENCES student(student_id) ON DELETE CASCADE,
  CONSTRAINT fk_enr_course  FOREIGN KEY (course_id)
    REFERENCES course(course_id)  ON DELETE RESTRICT
) ENGINE=InnoDB;
```
### B. Añadir o quitar constraints después (ALTER TABLE)
```sql
-- Agregar UNIQUE compuesto
ALTER TABLE student
  ADD CONSTRAINT uq_student_name_email UNIQUE (full_name, email);
-- Agregar FOREIGN KEY
ALTER TABLE enrollment
  ADD CONSTRAINT fk_enr_course FOREIGN KEY (course_id)
  REFERENCES course(course_id) ON DELETE RESTRICT;
-- Quitar una FOREIGN KEY (usa su nombre)
ALTER TABLE enrollment DROP FOREIGN KEY fk_enr_course;
-- Quitar un UNIQUE en MySQL/MariaDB se hace como índice:
ALTER TABLE student DROP INDEX uq_student_name_email;
-- Quitar un CHECK (MySQL 8+)
ALTER TABLE course DROP CHECK chk_credits;
```
## ¿Por qué usar constraints y no sólo validación en la app?
- **Consistencia centralizada**: protegen los datos aunque haya **múltiples aplicaciones** o usuarios conectados.
- **Atómicas**: se aplican dentro de la transacción; no hay “ventanas” para estados inválidos.
- **Performance**: `PRIMARY KEY` y `UNIQUE` crean índices útiles; las **FK** suelen beneficiarse de índices en las columnas referenciadas.
## Buenas prácticas
- **Nombra tus constraints** (`fk_enr_course`, `chk_credits`): facilita depurar y alterar.
- **Usa `NOT NULL` por defecto** salvo que _nulo_ tenga un significado claro.
- **Prefiere `UNIQUE` multicolumna** para reglas como “no hay dos matrículas del mismo estudiante en el mismo curso”.
- **Define `ON DELETE/UPDATE`** en FKs de acuerdo al negocio (`CASCADE`, `RESTRICT`, `SET NULL`).
- **CHECK para reglas de dominio** (rangos, formatos). Para reglas complejas que cruzan varias filas/ tablas, evalúa **triggers**.
- **Índices para FKs**: añade un índice en la columna que referencia; mejora `JOIN` y borrados/actualizaciones en cascada.
## Errores típicos
- Confiar sólo en la app: otro cliente puede saltarse la validación.
- `CHECK` en versiones antiguas de MySQL/MariaDB → no se aplicaba (ver tu versión).
- Falta de índices en FKs → `JOIN` lentos y cascadas costosas.
- `PRIMARY KEY` no estable (p. ej., usar un dato que cambia). Mejor **surrogate key** (`AUTO_INCREMENT`) y **UNIQUE** para la llave natural.
# 📘 Guía práctica: Organización de scripts SQL en Ubuntu (LAMP con MariaDB/MySQL)
## 0) (Opcional) Instalar LAMP con MariaDB
Si todavía no tienes tu entorno:
```bash
sudo apt update
sudo apt install -y apache2 mariadb-server php libapache2-mod-php php-mysql
sudo systemctl enable --now mariadb
sudo mysql_secure_installation   # asegura root, elimina test DB, etc.
```
> Nota: en MariaDB puedes entrar al cliente con `sudo mysql` o `sudo mariadb`.
## 1) Crear base de datos y usuario de trabajo
Abre el cliente como root:
```bash
sudo mysql
```
En el prompt SQL:
```sql
-- Crea la BD (utf8mb4 = Unicode completo)
CREATE DATABASE IF NOT EXISTS uni_db
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;
-- Usuario para base de datos
CREATE USER IF NOT EXISTS 'dev'@'localhost' IDENTIFIED BY 'Dev.2025!';
-- OJO: Aquí según se requiera puede dar privilegios
-- Todos los privilegios para el usuario
GRANT ALL ON uni_db.* TO 'dev'@'localhost';
-- Privilegios (no root, sólo lo necesario)
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX
  ON uni_db.* TO 'dev'@'localhost';
FLUSH PRIVILEGES;
EXIT;
```
## 2) Estructura de carpetas y archivos `/sql`
Dentro de tu proyecto:
Considera que podría estár en cualquier carpeta, pero, para ser ordenado/a, podría ser recomendable ubicarlos en la carpeta html:
`cd /var/www/html`
Luego de ello: 
```bash
cd /var/www/html
mkdir -p proyecto/sql
cd proyecto/sql
touch 01_schema.sql 02_seed.sql 03_queries.sql
```
## 3) Contenido de ejemplo (Universidad)
### 📂 `01_schema.sql` — Definición de tablas (DDL)
```sql
-- 01_schema.sql
SET NAMES utf8mb4;
SET time_zone = '+00:00';
CREATE DATABASE IF NOT EXISTS uni_db
  CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE uni_db;
-- Tabla STUDENT
CREATE TABLE IF NOT EXISTS student (
  student_id   INT AUTO_INCREMENT PRIMARY KEY,
  student_code VARCHAR(20) NOT NULL UNIQUE,
  full_name    VARCHAR(100) NOT NULL,
  email        VARCHAR(255) UNIQUE,
  created_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
-- Tabla COURSE
CREATE TABLE IF NOT EXISTS course (
  course_id INT AUTO_INCREMENT PRIMARY KEY,
  code      VARCHAR(20) NOT NULL UNIQUE,
  title     VARCHAR(120) NOT NULL,
  credits   TINYINT NOT NULL CHECK (credits BETWEEN 1 AND 10)
) ENGINE=InnoDB;
-- Relación N:M → ENROLLMENT
CREATE TABLE IF NOT EXISTS enrollment (
  enrollment_id INT AUTO_INCREMENT PRIMARY KEY,
  student_id INT NOT NULL,
  course_id  INT NOT NULL,
  enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT fk_enr_student FOREIGN KEY (student_id)
    REFERENCES student(student_id) ON DELETE CASCADE,
  CONSTRAINT fk_enr_course FOREIGN KEY (course_id)
    REFERENCES course(course_id)  ON DELETE RESTRICT,
  CONSTRAINT uq_enr UNIQUE (student_id, course_id)
) ENGINE=InnoDB;
-- Índice para consultas frecuentes
CREATE INDEX idx_enr_course_student ON enrollment (course_id, student_id);
```
### 📂 `02_seed.sql` — Datos iniciales (DML)
```sql
-- 02_seed.sql
USE uni_db;
START TRANSACTION;
-- Estudiantes
INSERT INTO student (student_code, full_name, email) VALUES
  ('S-001','Ana Pérez','
[email protected]'),
  ('S-002','Luis Díaz','
[email protected]'),
  ('S-003','Marta Ríos','
[email protected]');
-- Cursos
INSERT INTO course (code, title, credits) VALUES
  ('CS101','Fundamentos de Programación',4),
  ('DB201','Bases de Datos I',3),
  ('MA110','Cálculo I',4);
-- Matrículas (usando SELECT para obtener IDs)
INSERT INTO enrollment (student_id, course_id)
SELECT s.student_id, c.course_id
FROM student s JOIN course c
WHERE s.student_code='S-001' AND c.code='CS101';
INSERT INTO enrollment (student_id, course_id)
SELECT s.student_id, c.course_id
FROM student s JOIN course c
WHERE s.student_code='S-001' AND c.code='DB201';
INSERT INTO enrollment (student_id, course_id)
SELECT s.student_id, c.course_id
FROM student s JOIN course c
WHERE s.student_code='S-002' AND c.code='DB201';
COMMIT;
-- Validaciones rápidas
SELECT COUNT(*) AS total_students  FROM student;
SELECT COUNT(*) AS total_courses   FROM course;
SELECT COUNT(*) AS total_enrollments FROM enrollment;
```
### 📂 `03_queries.sql` — Consultas representativas
```sql
-- 03_queries.sql
USE uni_db;
-- Q1: Filtrar por nombre
SELECT student_id, full_name, email
FROM student
WHERE full_name LIKE 'Ana%';
-- Q2: JOIN → lista inscripciones con nombre de alumno y curso
SELECT e.enrollment_id, s.full_name, c.title, e.enrolled_at
FROM enrollment e
JOIN student s ON s.student_id = e.student_id
JOIN course  c ON c.course_id  = e.course_id
ORDER BY e.enrolled_at DESC;
-- Q3: Agregación → número de estudiantes por curso
SELECT c.code, c.title, COUNT(*) AS enrolled_students
FROM enrollment e
JOIN course c ON c.course_id = e.course_id
GROUP BY c.course_id, c.code, c.title
HAVING COUNT(*) >= 1
ORDER BY enrolled_students DESC;
-- Q4: Subconsulta → estudiantes SIN matrícula en DB201
SELECT s.full_name
FROM student s
WHERE s.student_id NOT IN (
  SELECT e.student_id
  FROM enrollment e
  JOIN course c ON c.course_id = e.course_id
  WHERE c.code='DB201'
);
-- Q5: Vista de resumen
CREATE OR REPLACE VIEW vw_enroll_summary AS
SELECT s.student_code, s.full_name, c.code AS course_code, c.title, e.enrolled_at
FROM enrollment e
JOIN student s ON s.student_id = e.student_id
JOIN course  c ON c.course_id  = e.course_id;
-- Consultar la vista
SELECT * FROM vw_enroll_summary;
```
## 4) Ejecutar los scripts en orden
```bash
#Ubiquese en en la carpeta de trabajo.
cd var/www/html/proyecto/sql 
# 1) Esquema (DDL)
mysql -u root -p < 01_schema.sql
# 2) Datos iniciales (DML)
mysql -u dev -p --database=uni_db < 02_seed.sql
# 3) Consultas y vista
mysql -u dev -p --database=uni_db < 03_queries.sql
```
> ⚠️ Recuerda: el **orden importa** → primero el esquema, luego los datos, después las consultas.
## 5) Integración con proyecto LAMP
Si tu aplicación web está en `/var/www/html/miapp`, puedes copiar los scripts allí:
```bash
sudo mkdir -p /var/www/html/miapp/sql
sudo cp var/www/html/proyecto/sql/*.sql /var/www/html/miapp/sql/
sudo chown -R $USER:www-data /var/www/html/miapp/sql
sudo chmod -R 750 /var/www/html/miapp/sql
```
> Apache no ejecuta SQL: estos archivos se usan desde la consola o desde tu app PHP.
## 🚀 Consejos finales
- **Valida siempre**: después de correr `02_seed.sql`, usa `SELECT COUNT(*)` para comprobar registros.
- **Evita sorpresas**: si necesitas rehacer, crea un `00_reset.sql` con `DROP TABLE ...` en orden inverso.
- **Documenta**: pon comentarios (`-- ...`) en cada bloque de tus scripts para que sepas qué hace cada parte.
- **Engine**: mantén `InnoDB` para soportar claves foráneas y transacciones.