Cómo relacionar dos filas en la misma tabla


11

Tengo una tabla donde las filas pueden estar relacionadas entre sí, y lógicamente, la relación va en ambos sentidos (básicamente, no tiene dirección) entre las dos filas. (Y si se está preguntando, sí, esto realmente debería ser una tabla. Son dos cosas de la misma entidad / tipo lógico). Puedo pensar en un par de formas de representar esto:

  1. Almacenar la relación y su reverso
  2. Almacene la relación de una manera, impida que la base de datos la almacene de la otra manera, y tenga dos índices con órdenes opuestas para las FK (un índice es el índice PK)
  3. Almacene la relación de una manera con dos índices y permita que se inserte el segundo de todos modos (suena un poco asqueroso, pero bueno, lo completo)
  4. Cree algún tipo de tabla de agrupación y tenga un FK en la tabla original. (Plantea muchas preguntas. La tabla de agrupación solo tendría un número; ¿por qué incluso tener la tabla? ¿Hacer FK NULLable o tener grupos con una sola fila asociada?)

¿Cuáles son algunas de las principales ventajas y desventajas de estas formas y, por supuesto, hay alguna forma en la que no haya pensado?

Aquí hay un SQLFiddle para jugar: http://sqlfiddle.com/#!12/7ee1a/1/0 . (Parece ser PostgreSQL ya que eso es lo que estoy usando, pero no creo que esta pregunta sea muy específica para PostgreSQL). Actualmente almacena la relación y su reversa solo como un ejemplo.


¿Puede un valor dado estar relacionado con más de uno? ¿Un valor dado siempre está relacionado con otro? ¿Comparten los mismos otros datos comunes?
Philᵀᴹ

Sí, pueden estar relacionados con más de 1 otra fila. No, no siempre están necesariamente relacionados con otra fila. No necesariamente tienen datos comunes. Gracias.
jpmc26

Ups Olvidé el @Phil. También editado para agregar una estructura potencial que se me acaba de ocurrir.
jpmc26

Respuestas:


9

Lo que has diseñado es bueno. Lo que hay que agregar es una restricción para que la relación no tenga dirección. Por lo tanto, no puede tener una (1,5)fila sin que (5,1)se agregue una fila también.

Esto se puede lograr * con una restricción de autorreferencia en la tabla del puente.

*: se puede lograr en Postgres, Oracle, DB2 y todos los DBMS que han implementado restricciones de clave externa como lo describe el estándar SQL (diferido, por ejemplo, verificado al final de la transacción). La verificación diferida no es realmente necesaria de todos modos, como en SQL- Servidor que los verifica al final del extracto y esta construcción aún funciona. No puede hacer esto en MySQL porque "InnoDB verifica las restricciones ÚNICAS y EXTRANJERAS fila por fila" .

Entonces, en Postgres lo siguiente coincidirá con sus requisitos:

CREATE TABLE x
(
  x_id SERIAL NOT NULL PRIMARY KEY,
  data VARCHAR(10) NOT NULL
);

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    FOREIGN KEY (x_id2, x_id1)
    REFERENCES bridge_x (x_id1, x_id2)
);

Probado en: SQL-Fiddle

Si intenta agregar una fila (1,5):

INSERT INTO bridge_x VALUES
(1,5) ;

Falla con:

ERROR: insertar o actualizar en la tabla "bridge_x" viola la restricción de clave externa "x_x_directionless"
Detalle: La clave (x_id2, x_id1) = (5, 1) no está presente en la tabla "bridge_x" .:
INSERTAR EN LOS VALORES bridge_x (1,5)

Además, puede agregar una CHECKrestricción si desea prohibir (y,y)filas:

ALTER TABLE bridge_x
  ADD CONSTRAINT x_x_self_referencing_items_not_allowed
    CHECK (x_id1 <> x_id2) ;

Hay otras formas de implementar esto como usted menciona, como almacenar solo una dirección de la relación (en una fila, no dos) forzando la identificación inferior x_id1y la identificación superior en la x_id2columna. Parece más fácil de implementar, pero generalmente lleva a consultas más complejas más adelante:

CREATE TABLE bridge_x
(
  x_id1 INTEGER NOT NULL REFERENCES x (x_id),
  x_id2 INTEGER NOT NULL REFERENCES x (x_id),
  PRIMARY KEY(x_id1, x_id2),
  CONSTRAINT x_x_directionless
    CHECK (x_id1 <= x_id2)                       -- or "<" to forbid `(y,y)` rows
);
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.