6

Tengo una base de datos creada con muchas mesas y todo se ve bien, aparte de un bit ...¿Cómo crear un múltiplo de la propia mesa de

Inventory Table <*-----1> Storage Table <1-----1> Van Table 
          ^
           1 
           |-------1> Warehouse Table 

La tabla de almacenamiento se utiliza desde la furgoneta y almacén se similar, pero ¿cómo creo una relación entre Storage y Warehouse/Van tables? Tendría sentido que tengan que ser 1 a 1, ya que un objeto de almacenamiento solo puede ser 1 Lugar y tipo de almacenamiento. Tuve el enlace de la tabla Van/Warehouse a la clave primaria StorageId y luego agregué una restricción para asegurarme de que las tablas Van y Warehouse no tienen el mismo StorageId, pero parece que se podría hacer de una manera mejor.

Puedo ver varias formas de hacerlo, pero todas parecen estar equivocadas, ¡así que cualquier ayuda sería buena!

+0

Por favor, evaluar a fondo si realmente necesita una relación uno a uno. La mayoría de las veces cuando piensas que necesitas uno, realmente no. –

+0

Use EER. La clave aquí es identificar el tipo de relación 'is a',' has'/'pertenece a', o son los subtipos del mismo supertipo. Luego reduzca sus entidades a tablas. – Oybek

+0

Ahh vale, bueno, entonces Storage Table es una superclase y Van/Warehouse Tables son subtipos, pero ¿ahora qué? lol – Luckyl337

Respuesta

15

Está utilizando la herencia (también conocida en el modelado de relaciones entre entidades como "subclase" o "categoría"). En general, hay 3 formas de representarlo en la base de datos:

  1. "Todas las clases en una mesa": tener sólo una mesa de "cobertura" a los padres y todas las clases hijas (es decir, con todos los padres e hijos columnas), con una restricción CHECK para asegurar que el subconjunto correcto de campos no sea NULL (es decir, que dos niños diferentes no se "mezclen").
  2. "Clase de hormigón por tabla": Tenga una tabla diferente para cada niño, pero no una tabla principal. Esto requiere que las relaciones de los padres (en su caso, Inventario < - Almacenamiento) se repitan en todos los niños.
  3. "Clase por tabla": Tener una tabla primaria y una tabla separada para cada hijo, que es lo que está intentando hacer. Esto es más limpio, pero puede costar cierto rendimiento (sobre todo cuando se modifican datos, no tanto cuando se realiza la consulta porque se puede unir directamente desde el niño y omitir al padre).

Por lo general prefieren la tercera aproximación, pero hacer cumplir tanto la presencia y la exclusividad de un niño a nivel de aplicación. Hacer cumplir ambos a nivel de la base de datos es un poco engorroso, pero se puede hacer si el DBMS admite restricciones diferidas. Por ejemplo:

enter image description here

CHECK (
    (
     (VAN_ID IS NOT NULL AND VAN_ID = STORAGE_ID) 
     AND WAREHOUSE_ID IS NULL 
    ) 
    OR (
     VAN_ID IS NULL 
     AND (WAREHOUSE_ID IS NOT NULL AND WAREHOUSE_ID = STORAGE_ID) 
    ) 
) 

Esto hará cumplir tanto la exclusividad (debido a la CHECK) y la presencia (debido a la combinación de CHECK y FK1/FK2) del niño.

Desafortunadamente, MS SQL Server does not support deferred constraints, pero es posible que pueda "ocultar" toda la operación detrás de los procedimientos almacenados y prohibir a los clientes modificar las tablas directamente.


Sólo la exclusividad puedan aplicarse sin limitaciones diferidos:

enter image description here

El STORAGE_TYPE es un discriminador de tipo, por lo general un número entero para ahorrar espacio (En el ejemplo anterior, 0 y 1 son " conocido "para su aplicación e interpretado en consecuencia).

Las columnas VAN.STORAGE_TYPE y WAREHOUSE.STORAGE_TYPE se pueden calcular (también conocidas como "calculadas") para ahorrar espacio de almacenamiento y evitar la necesidad de CHECK s.

--- --- EDITAR

columnas calculadas trabajarían bajo SQL Server como esto:

CREATE TABLE STORAGE (
    STORAGE_ID int PRIMARY KEY, 
    STORAGE_TYPE tinyint NOT NULL, 
    UNIQUE (STORAGE_ID, STORAGE_TYPE) 
); 

CREATE TABLE VAN (
    STORAGE_ID int PRIMARY KEY, 
    STORAGE_TYPE AS CAST(0 as tinyint) PERSISTED, 
    FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE) 
); 

CREATE TABLE WAREHOUSE (
    STORAGE_ID int PRIMARY KEY, 
    STORAGE_TYPE AS CAST(1 as tinyint) PERSISTED, 
    FOREIGN KEY (STORAGE_ID, STORAGE_TYPE) REFERENCES STORAGE(STORAGE_ID, STORAGE_TYPE) 
); 

-- We can make a new van. 
INSERT INTO STORAGE VALUES (100, 0); 
INSERT INTO VAN VALUES (100); 

-- But we cannot make it a warehouse too. 
INSERT INTO WAREHOUSE VALUES (100); 
-- Msg 547, Level 16, State 0, Line 24 
-- The INSERT statement conflicted with the FOREIGN KEY constraint "FK__WAREHOUSE__695C9DA1". The conflict occurred in database "master", table "dbo.STORAGE". 

Desafortunadamente, SQL Server requiere de una columna calculada que se utiliza en una extranjera clave que se PERSISTERÁ. Es posible que otras bases de datos no tengan esta limitación (por ejemplo, las columnas virtuales de Oracle), lo que puede ahorrar algo de espacio de almacenamiento.

+0

Hola @Branko, gracias por la gran respuesta. Sin embargo, me confundieron las últimas frases de tu respuesta. ¿Le importaría profundizar en: (1) por qué, en su último diagrama, las tablas VAN y WAREHOUSE tienen una columna STORAGE_TYPE (imagino, todas con los mismos valores: 0 para VAN y 1 para WAREHOUSE), (2) qué es exactamente se supone que debe ir en el contenido de las columnas calculadas/calculadas (estoy confundido en cuanto a lo que se calcula aquí)? Muy obligado :) – youngrrrr

+1

@youngrrrr VAN y WAREHOUSE tienen un STORAGE_TYPE para que puedan declarar un CHECK en él. De esta forma, si una fila de ALMACENAMIENTO es un padre de una fila de VAN, _no puede ser también un padre de una fila de ALMACÉN (porque ALMACENAMIENTO.STORAGE_TYPE es 0, fallando el CHEQUE en ALMACÉN). Lo contrario también es cierto: si ALMACENAMIENTO es ALMACÉN, el TIPO DE ALMACENAMIENTO es 1, lo que significa que CHECK in VAN fallaría si alguien intentara insertar una fila correspondiente allí. La columna calculada es simplemente para ahorrar espacio, ya que siempre tiene el mismo valor dentro de la tabla dada, no hay necesidad de repetir físicamente ese valor en cada fila. –

+1

@youngrrrr Tenga en cuenta que hay un FK en VAN (STORAGE_ID, STORAGE) haciendo referencia a ALMACENAMIENTO (STORAGE_ID, STORAGE_TYPE). Y lo mismo en ALMACÉN. De esta forma, paren row (en ALMACENAMIENTO) y la fila secundaria (en VAN o WAREHOUSE, pero no en ambas) siempre tienen el mismo STORAGE_TYPE. –

1

Como dices, hay muchas soluciones. Yo recomendaría comenzar con la solución más simple, luego optimizando más adelante si el rendimiento o el almacenamiento se vuelven problemáticos. La solución más simple (pero no óptima en términos de almacenamiento) sería tener una tabla de almacenamiento que tenga una columna para el tipo de almacenamiento (que indique si la fila representa una camioneta o un almacén), además de columnas para los atributos de Van y Warehouse. En una fila que representa una Van, las columnas para los atributos de Warehouse serán nulas. En una fila que representa un Almacén, las columnas para los atributos Van serán todas nulas.

De esta forma, reduce el número de tablas y mantiene sus consultas simples y agradables. Esté preparado para revisar su decisión si el almacenamiento se vuelve difícil.

1

De alguna manera me parece que los artículos del inventario pueden cambiar de ubicación, así que iría con algo como esto.

enter image description here

Cuestiones relacionadas