Pregunta

Bueno, aquí está mi problema, tengo tres tablas; regiones, países, estados. Los países pueden estar dentro de las regiones, los estados pueden estar dentro de las regiones. Las regiones son la parte superior de la cadena alimentaria.

Ahora estoy agregando una tabla popular_areas con dos columnas; region_id y popular_place_id. ¿Es posible hacer que popular_place_id sea una clave foránea para cualquiera de los países OR estados? Probablemente voy a tener que agregar una columna popular_place_type para determinar si la identificación describe un país o estado de cualquier manera.

¿Fue útil?

Solución

Lo que estás describiendo se llama Asociaciones polimórficas. Es decir, la "clave extranjera". La columna contiene un valor de identificación que debe existir en uno de un conjunto de tablas de destino. Por lo general, las tablas de destino están relacionadas de alguna manera, como ser instancias de alguna superclase de datos común. También necesitaría otra columna junto a la columna de clave externa, para que en cada fila, pueda designar a qué tabla de destino se hace referencia.

CREATE TABLE popular_places (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  place_type VARCHAR(10) -- either 'states' or 'countries'
  -- foreign key is not possible
);

No hay forma de modelar asociaciones polimórficas utilizando restricciones SQL. Una restricción de clave externa siempre hace referencia a una tabla de destino.

Las asociaciones polimórficas son compatibles con marcos como Rails e Hibernate. Pero dicen explícitamente que debe deshabilitar las restricciones de SQL para usar esta función. En cambio, la aplicación o el marco deben hacer un trabajo equivalente para garantizar que se cumpla la referencia. Es decir, el valor en la clave externa está presente en una de las posibles tablas de destino.

Las asociaciones polimórficas son débiles con respecto al cumplimiento de la coherencia de la base de datos. La integridad de los datos depende de que todos los clientes accedan a la base de datos con la misma lógica de integridad referencial aplicada, y también la aplicación debe estar libre de errores.

Estas son algunas soluciones alternativas que aprovechan la integridad referencial impuesta por la base de datos:

Cree una tabla adicional por objetivo. Por ejemplo, popular_states y popular_countries , que hacen referencia a states y países respectivamente. Cada uno de estos " popular " las tablas también hacen referencia al perfil del usuario.

CREATE TABLE popular_states (
  state_id INT NOT NULL,
  user_id  INT NOT NULL,
  PRIMARY KEY(state_id, user_id),
  FOREIGN KEY (state_id) REFERENCES states(state_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

CREATE TABLE popular_countries (
  country_id INT NOT NULL,
  user_id    INT NOT NULL,
  PRIMARY KEY(country_id, user_id),
  FOREIGN KEY (country_id) REFERENCES countries(country_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

Esto significa que para obtener todos los lugares favoritos populares de un usuario, debe consultar ambas tablas. Pero significa que puede confiar en la base de datos para hacer cumplir la coherencia.

Cree una tabla de lugares como supertable. Como menciona Abie, una segunda alternativa es que sus lugares populares hagan referencia a una tabla como lugares , que es padre de ambos states y country . Es decir, tanto los estados como los países también tienen una clave externa para lugares (incluso puede hacer que esta clave externa también sea la clave principal de states y países ).

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  PRIMARY KEY (user_id, place_id),
  FOREIGN KEY (place_id) REFERENCES places(place_id)
);

CREATE TABLE states (
  state_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (state_id) REFERENCES places(place_id)
);

CREATE TABLE countries (
  country_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

Use dos columnas. En lugar de una columna que pueda hacer referencia a cualquiera de las dos tablas de destino, use dos columnas. Estas dos columnas pueden ser NULL ; de hecho, solo uno de ellos no debe ser NULL .

CREATE TABLE popular_areas (
  place_id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  state_id INT,
  country_id INT,
  CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
  CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
  FOREIGN KEY (state_id) REFERENCES places(place_id),
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

En términos de teoría relacional, las asociaciones polimórficas violan Primera forma normal , porque el popular_place_id es en efecto una columna con dos significados: es un estado o un país. No almacenaría la age de una persona y su phone_number en una sola columna, y por la misma razón no debería almacenar tanto state_id como country_id en una sola columna. El hecho de que estos dos atributos tengan tipos de datos compatibles es una coincidencia; todavía significan diferentes entidades lógicas.

Las asociaciones polimórficas también violan Tercera forma normal , porque el significado de la columna depende del extra columna que nombra la tabla a la que se refiere la clave externa. En la Tercera forma normal, un atributo en una tabla debe depender solo de la clave primaria de esa tabla.


Re comentario de @SavasVedova:

No estoy seguro de seguir su descripción sin ver las definiciones de la tabla o una consulta de ejemplo, pero parece que simplemente tiene varias tablas Filters , cada una de las cuales contiene una clave foránea que hace referencia a un < code> Productos tabla.

CREATE TABLE Products (
  product_id INT PRIMARY KEY
);

CREATE TABLE FiltersType1 (
  filter_id INT PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

CREATE TABLE FiltersType2 (
  filter_id INT  PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

...and other filter tables...

Unir los productos a un tipo específico de filtro es fácil si sabe qué tipo

Otros consejos

Esta no es la solución más elegante del mundo, pero podría usar herencia de tablas concretas para hacer que esto funcione.

Conceptualmente, está proponiendo una noción de una clase de "cosas que pueden ser áreas populares". de donde heredan sus tres tipos de lugares. Puede representar esto como una tabla llamada, por ejemplo, lugares donde cada fila tiene una relación uno a uno con una fila en regiones , países o states . (Los atributos que se comparten entre regiones, países o estados, si los hay, podrían insertarse en esta tabla de lugares). Su popular_place_id sería una referencia de clave externa a una fila en la tabla de lugares que sería luego lo llevará a una región, país o estado.

La solución que propone con una segunda columna para describir el tipo de asociación es cómo Rails maneja las asociaciones polimórficas, pero en general no soy fanático de eso. Bill explica con excelente detalle por qué las asociaciones polimórficas no son tus amigos.

Aquí hay una corrección al "supertable" de Bill Karwin enfoque, usando una clave compuesta (place_type, place_id) para resolver las violaciones de forma normal percibidas:

CREATE TABLE places (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) NOT NULL
     CHECK ( place_type = 'state', 'country' ),
  UNIQUE ( place_type, place_id )
);

CREATE TABLE states (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'state' NOT NULL
     CHECK ( place_type = 'state' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to states go here
);

CREATE TABLE countries (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'country' NOT NULL
     CHECK ( place_type = 'country' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to country go here
);

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  UNIQUE ( user_id, place_id ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
);

Lo que este diseño no puede garantizar es que para cada fila en lugares exista una fila en states o países (pero no en ambos). Esta es una limitación de las claves externas en SQL. En un DBMS completo que cumpla con los estándares SQL-92, podría definir restricciones diferibles entre tablas que le permitirían lograr lo mismo, pero es torpe, implica transacciones y dicho DBMS aún no ha llegado al mercado.

Me doy cuenta de que este hilo es antiguo, pero vi esto y se me ocurrió una solución y pensé en tirarlo allí.

Regiones, países y estados son ubicaciones geográficas que viven en una jerarquía.

Puede evitar su problema por completo creando una tabla de dominio llamada Geographic_location_type que llenaría con tres filas (Región, País, Estado).

A continuación, en lugar de las tres tablas de ubicación, cree una sola tabla de ubicación geográfica que tenga una clave externa de ubicación_tipo_de_ubicación_geográfica (para que sepa si la instancia es una Región, País o Estado).

Modele la jerarquía haciendo que esta tabla haga referencia a sí misma de modo que una instancia de Estado mantenga la fKey en su instancia de País principal, que a su vez mantiene la fKey en su instancia de Región principal. Las instancias de región contendrían NULL en esa tecla f Esto no es diferente de lo que hubiera hecho con las tres tablas (tendría 1 - muchas relaciones entre región y país y entre país y estado), excepto que ahora todo está en una tabla.

La tabla popular_user_location sería una tabla de resolución de alcance entre user y georgraphical_location (a muchos usuarios les pueden gustar muchos lugares).

Soooo & # 8230;

 ingrese la descripción de la imagen aquí

CREATE TABLE [geographical_location_type] (
    [geographical_location_type_id] INTEGER NOT NULL,
    [name] VARCHAR(25) NOT NULL,
    CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id])
)

-- Add 'Region', 'Country' and 'State' instances to the above table


CREATE TABLE [geographical_location] (
   [geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL,
    [name] VARCHAR(1024) NOT NULL,
    [geographical_location_type_id] INTEGER NOT NULL,
    [geographical_location_parent] BIGINT,  -- self referencing; can be null for top-level instances
    CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id])
)

CREATE TABLE [user] (
    [user_id] BIGINT NOT NULL,
    [login_id] VARCHAR(30) NOT NULL,
    [password] VARCHAR(512) NOT NULL,
    CONSTRAINT [PK_user] PRIMARY KEY ([user_id])
)


CREATE TABLE [popular_user_location] (
    [popular_user_location_id] BIGINT NOT NULL,
    [user_id] BIGINT NOT NULL,
    [geographical_location_id] BIGINT NOT NULL,
    CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id])
)

ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location] 
    FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id])



ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location] 
    FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location] 
    FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location] 
    FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])

No estaba seguro de cuál era el DB objetivo; lo anterior es MS SQL Server.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top