</>waridocu

Bases de datos

Del modelo lógico a las tablas

Cómo cada elemento del diagrama se convierte en tabla, columna, clave primaria y clave foránea, y qué SQL sale de eso.

bases-de-datosmodelo-relacionalclave-primariaclave-foraneasqldata-modeler

Una traducción mecánica

Todo el trabajo difícil ya está hecho. Pasar del modelo lógico al modelo relacional —tablas y columnas— es aplicar una tabla de equivalencias, y es tan mecánico que Data Modeler lo hace con un botón.

Lo que sigue es esa traducción, para que entiendas qué produce la herramienta en vez de aceptarlo a ciegas.

Modelo lógicoModelo relacional
EntidadTabla
InstanciaFila
AtributoColumna
Atributo # (UID)PRIMARY KEY
Atributo *Columna NOT NULL
Atributo oColumna que acepta NULL
Relación 1:NFOREIGN KEY en el lado “muchos”
Relación N:M (ya resuelta)Tabla intermedia con dos FOREIGN KEY
UID barradoPRIMARY KEY compuesta por las claves foráneas

También cambia el vocabulario, y conviene tener las dos columnas en la cabeza porque la bibliografía mezcla los dos:

LógicoRelacional
EntidadTabla (table)
AtributoColumna (column)
UIDClave primaria (primary key, PK)
RelaciónClave foránea (foreign key, FK)

Dónde va la clave foránea

Es la única decisión que confunde, y tiene una regla de una línea:

La clave foránea va siempre del lado de la pata de gallo, o sea, en el lado “muchos”.

texttext
  PROFESOR                                CURSO
 ┌──────────────┐                       ┌──────────────┐
 │ # id_profesor│- - - - - - - - - - - <│ # codigo     │
 │ * nombre     │                       │ * nombre     │
 └──────────────┘                       │ * horas      │
                                        │ * id_profesor│ ← la FK aterriza acá
                                        └──────────────┘

El motivo es simple si lo piensas en filas: cada curso tiene un profesor, así que en la fila del curso cabe perfectamente una columna con el id de ese profesor. Al revés no funcionaría: un profesor dicta varios cursos, y en su fila no cabría una lista de códigos — sería otra vez un atributo multivaluado.

Y la opcionalidad del extremo también viaja:

El extremo hacia el “uno” eraLa columna FK queda
Continuo (obligatorio)NOT NULL
Punteado (opcional)Acepta NULL

Es decir: si un curso debe tener profesor, id_profesor es NOT NULL; si pudiera existir sin profesor asignado, aceptaría NULL.

El instituto convertido en tablas

Partiendo del modelo lógico completo:

texttext
┌──────────────┐   ┌──────────────────┐   ┌──────────────┐   ┌──────────────┐
│ ALUMNO       │   │ INSCRIPCION      │   │ CURSO        │   │ PROFESOR     │
├──────────────┤   ├──────────────────┤   ├──────────────┤   ├──────────────┤
│ # id_alumno  │──┼<│ * fecha         │>┼─│ # codigo     │>- │ # id_profesor│
│ * nombre     │   │ o nota           │   │ * nombre     │ └─│ * nombre     │
│ * email      │   └──────────────────┘   │ * horas      │   │ * email      │
│ o telefono   │                          │ o descripcion│   │ o telefono   │
└──────────────┘                          └──────────────┘   └──────────────┘

El resultado son cuatro tablas:

Ejemplo: el script que sale del modelosql
CREATE TABLE profesor (
    id_profesor   NUMBER        PRIMARY KEY,
    nombre        VARCHAR2(100) NOT NULL,
    email         VARCHAR2(150) NOT NULL,
    telefono      VARCHAR2(30)
);

CREATE TABLE alumno (
    id_alumno     NUMBER        PRIMARY KEY,
    nombre        VARCHAR2(100) NOT NULL,
    email         VARCHAR2(150) NOT NULL,
    telefono      VARCHAR2(30)
);

CREATE TABLE curso (
    codigo        VARCHAR2(10)  PRIMARY KEY,
    nombre        VARCHAR2(100) NOT NULL,
    horas         NUMBER        NOT NULL,
    descripcion   VARCHAR2(500),
    id_profesor   NUMBER        NOT NULL,
    CONSTRAINT fk_curso_profesor
        FOREIGN KEY (id_profesor) REFERENCES profesor (id_profesor)
);

CREATE TABLE inscripcion (
    id_alumno     NUMBER        NOT NULL,
    codigo        VARCHAR2(10)  NOT NULL,
    fecha         DATE          NOT NULL,
    nota          NUMBER(3,1),
    CONSTRAINT pk_inscripcion PRIMARY KEY (id_alumno, codigo),
    CONSTRAINT fk_insc_alumno
        FOREIGN KEY (id_alumno) REFERENCES alumno (id_alumno),
    CONSTRAINT fk_insc_curso
        FOREIGN KEY (codigo)    REFERENCES curso (codigo)
);

Recorre el script comparándolo con el diagrama y vas a ver que cada línea viene de un símbolo:

  • Cada # se convirtió en un PRIMARY KEY.
  • Cada * se convirtió en un NOT NULL.
  • Cada o es una columna sin NOT NULLtelefono, descripcion, nota.
  • La relación PROFESOR→CURSO puso id_profesor en curso, del lado de la pata de gallo, y NOT NULL porque ese extremo era continuo.
  • El UID barrado de INSCRIPCION se convirtió en PRIMARY KEY (id_alumno, codigo), que es lo que impide inscribir dos veces al mismo alumno en el mismo curso.
Nota

El orden de creación importa: curso referencia a profesor, así que profesor tiene que existir antes. Data Modeler ordena el script solo. Si escribes el SQL a mano, la regla es crear primero las tablas sin claves foráneas y después las que dependen de ellas.

Los tipos de dato

El modelo lógico no habla de tipos: ese es el salto al modelo físico, donde recién importa qué motor se va a usar. Los más habituales en Oracle:

DatoTipoNota
Texto de largo variableVARCHAR2(n)En otros motores es VARCHAR; el 2 es propio de Oracle
Número enteroNUMBER o NUMBER(p)
Número con decimalesNUMBER(p, d)NUMBER(3,1) da hasta 99.9 — sirve para una nota
FechaDATEEn Oracle incluye la hora
Fecha con precisiónTIMESTAMP
Sí/NoNUMBER(1) o CHAR(1)Oracle no tiene un tipo booleano en SQL
Advertencia

Elegir el largo de un VARCHAR2 de menos es un problema real y recurrente: VARCHAR2(50) para un email parece de sobra hasta que llega uno más largo y el INSERT falla. Y elegir un tipo numérico para algo que no se usa para calcular —un teléfono, un código postal— rompe los que empiezan con cero o tienen guiones. Si no vas a sumarlo, es texto.

La regla de integridad referencial

Una vez creadas las claves foráneas, la base de datos empieza a hacer cumplir sola algo llamado integridad referencial: no puede existir una fila que apunte a algo que no existe.

sqlsql
INSERT INTO curso VALUES ('JAV-01', 'Java Básico', 40, NULL, 99);

Si no hay ningún profesor con id_profesor = 99, la base rechaza el INSERT:

texttext
ORA-02291: integrity constraint (FK_CURSO_PROFESOR) violated - parent key not found

Y en el otro sentido, borrar un profesor que dicta cursos también se rechaza (ORA-02292), porque dejaría cursos apuntando a la nada. Eso es exactamente lo que el modelo prometía cuando dibujaste esa línea continua: la base ahora lo garantiza, sin depender de que ningún programa se acuerde de controlarlo.

Importante

Esta es la razón de fondo por la que vale la pena modelar. Las restricciones que dibujaste —#, *, las relaciones— se convierten en garantías que la base hace cumplir para todos los programas que la usen: el sistema web, el script de importación, y el que alguien escriba dentro de tres años. Un control escrito en el código de una aplicación solo protege a esa aplicación.

Hacerlo en Data Modeler

Todo esto en la herramienta es un botón. Con el modelo lógico terminado:

  1. En la barra de herramientas, Engineer to Relational Model (la flecha que apunta a la derecha), o menú File → Engineer to Relational Model.
  2. Se abre un diálogo con la lista de lo que se va a generar; se aceptan las opciones por defecto y Engineer.
  3. Aparece el modelo relacional en la pestaña Relational Models: las mismas cajas, ahora con PK y FK marcadas.
  4. Para el SQL: menú File → Export → DDL File, se elige el motor y se genera el script.

La flecha contraria también existe: partiendo de un modelo relacional, se puede hacer ingeniería inversa hacia el lógico — útil cuando heredas una base ya creada y quieres entender su diseño.

Pon a prueba

🎯 ¿Dónde va la clave foránea?
texttext
  DEPARTAMENTO                          EMPLEADO
 ┌──────────────┐                      ┌──────────────┐
 │ # id_depto   │- - - - - - - - - - -<│ # id_empleado│
 │ * nombre     │                      │ * nombre     │
 └──────────────┘                      └──────────────┘
  • Correcto. La pata de gallo está del lado de EMPLEADO (muchos empleados por departamento), y la FK va siempre del lado "muchos". Cada empleado tiene un solo departamento, así que la columna cabe perfecta en su fila.

  • Es el error clásico de poner la FK del lado equivocado. Un departamento tiene muchos empleados, así que esa columna solo podría guardar uno de ellos — o haría falta una lista, que es justo lo que no se puede hacer.

  • Eso sería lo correcto si la relación fuera N:M, o sea si un empleado pudiera pertenecer a varios departamentos. Con 1:N la tabla intermedia sobra: agrega un JOIN a cada consulta sin aportar nada.

  • Duplicar el vínculo en los dos lados es peligroso: las dos copias pueden contradecirse y nada garantiza que coincidan. El vínculo se guarda una sola vez, del lado "muchos".

✏️ Traduce los símbolos a SQL

La entidad tiene # id_socio, * nombre y o telefono. Escribe las restricciones que corresponden a cada símbolo.

CREATE TABLE socio (
  id_socio   NUMBER        ,
  nombre     VARCHAR2(100) ,
  telefono   VARCHAR2(30)
);

💡 Escribe el SQL completo

A partir de este modelo, escribe las tres sentencias CREATE TABLE.

texttext
┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│ SOCIO            │    │ PRESTAMO         │    │ LIBRO            │
├──────────────────┤    ├──────────────────┤    ├──────────────────┤
│ # id_socio       │──┼<│ # fecha_retiro   │>┼──│ # isbn           │
│ * nombre         │    │ o fecha_devol    │    │ * titulo         │
│ o email          │    └──────────────────┘    │ o autor          │
└──────────────────┘                            └──────────────────┘
sqlsql
CREATE TABLE socio (
    id_socio   NUMBER        PRIMARY KEY,
    nombre     VARCHAR2(100) NOT NULL,
    email      VARCHAR2(150)
);

CREATE TABLE libro (
    isbn       VARCHAR2(20)  PRIMARY KEY,
    titulo     VARCHAR2(200) NOT NULL,
    autor      VARCHAR2(100)
);

CREATE TABLE prestamo (
    id_socio        NUMBER       NOT NULL,
    isbn            VARCHAR2(20) NOT NULL,
    fecha_retiro    DATE         NOT NULL,
    fecha_devol     DATE,
    CONSTRAINT pk_prestamo
        PRIMARY KEY (id_socio, isbn, fecha_retiro),
    CONSTRAINT fk_prestamo_socio
        FOREIGN KEY (id_socio) REFERENCES socio (id_socio),
    CONSTRAINT fk_prestamo_libro
        FOREIGN KEY (isbn)     REFERENCES libro (isbn)
);

Lo que hay que notar:

  • La PK de prestamo tiene tres columnas, no dos: el UID barrado aporta id_socio e isbn, y fecha_retiro llevaba su propio #. Sin la fecha, el mismo socio no podría volver a pedir el mismo libro nunca más.
  • fecha_devol no lleva NOT NULL porque era o, y eso es justamente lo que representa un préstamo todavía sin devolver. Preguntar WHERE fecha_devol IS NULL da la lista de libros que están afuera.
  • isbn es VARCHAR2, no NUMBER, aunque parezca un número: tiene guiones, puede empezar con cero y nadie lo suma nunca. Es la regla de “si no vas a calcular con él, es texto”.
  • Las columnas de la PK compuesta son NOT NULL explícitamente. Formar parte de una clave primaria ya lo implica, pero escribirlo hace evidente de dónde salió cada * del diagrama.

Siguiente paso

Ya recorriste el camino completo, de un párrafo en palabras a un script SQL. Falta hacerlo con la herramienta delante, clic por clic — ver Tu primer diagrama en Data Modeler — y después practicar con el banco de ejercicios.