¿Cómo tener una relación de uno a muchos con un niño privilegiado?


22

Quiero tener una relación de uno a muchos en la que para cada padre, uno o cero de los hijos se marque como un "favorito". Sin embargo, no todos los padres tendrán un hijo. (Piense en los padres como preguntas en este sitio, los niños como respuestas y favoritos como la respuesta aceptada). Por ejemplo,

TableA
    Id            INT PRIMARY KEY

TableB
    Id            INT PRIMARY KEY
    Parent        INT NOT NULL FOREIGN KEY REFERENCES TableA.Id

A mi modo de ver, puedo agregar la siguiente columna a la Tabla A:

    FavoriteChild INT NULL FOREIGN KEY REFERENCES TableB.Id

o la siguiente columna de la Tabla B:

    IsFavorite    BIT NOT NULL

El problema con el primer enfoque es que introduce una clave externa anulable, que, según tengo entendido, no está en forma normalizada. El problema con el segundo enfoque es que se necesita hacer más trabajo para garantizar que, como máximo, un niño sea el favorito.

¿Qué tipo de criterios debo usar para determinar qué enfoque usar? ¿O hay otros enfoques que no estoy considerando?

Estoy usando SQL Server 2012.

Respuestas:


19

Otra forma (sin Nulos y sin ciclos en las FOREIGN KEYrelaciones) es tener una tercera mesa para almacenar los "hijos favoritos". En la mayoría de los DBMS, necesitará una UNIQUErestricción adicional TableB.

@ Aaron fue más rápido al identificar que la convención de nomenclatura anterior es bastante engorrosa y puede provocar errores. Por lo general, es mejor (y lo mantendrá cuerdo) si no tiene Idcolumnas en todas sus tablas y si las columnas (que están unidas) tienen los mismos nombres en las muchas tablas que aparecen. Entonces, aquí hay un cambio de nombre:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    UNIQUE (ParentID, ChildID)

FavoriteChild
    ParentID        INT NOT NULL PRIMARY KEY
    ChildID         INT NOT NULL 
    FOREIGN KEY (ParentID, ChildID) 
        REFERENCES Child (ParentID, ChildID)

En SQL-Server (que está utilizando), también tiene la opción de la IsFavoritecolumna de bits que menciona. El niño favorito único por padre se puede lograr a través de un índice único filtrado:

Parent
    ParentID        INT NOT NULL PRIMARY KEY

Child
    ChildID         INT NOT NULL PRIMARY KEY
    ParentID        INT NOT NULL FOREIGN KEY REFERENCES Parent (ParentID)
    IsFavorite      BIT NOT NULL

CREATE UNIQUE INDEX is_FavoriteChild
  ON Child (ParentID)
  WHERE IsFavorite = 1 ;

Y la razón principal por la que no se recomienda su opción 1, al menos no en SQL-Server, es que el patrón de rutas circulares en las referencias de clave externa tiene algunos problemas.

Lea un artículo bastante antiguo: SQL By Design: The Circular Reference

Al insertar o eliminar filas de las dos tablas, se encontrará con el problema del "huevo y la gallina". ¿Qué tabla debo insertar primero, sin violar ninguna restricción?

Para resolver eso, debe definir al menos una columna que pueda contener nulos. (OK, técnicamente no tiene que hacerlo, puede tener todas las columnas, NOT NULLpero solo en DBMS, como Postgres y Oracle, que han implementado restricciones diferibles. Vea la respuesta de @ Erwin en una pregunta similar: restricción de clave externa compleja en SQLAlchemy sobre cómo Esto se puede hacer en Postgres). Aún así, esta configuración se siente como patinar sobre hielo delgado.

Compruebe también una pregunta casi idéntica en SO (pero para MySQL) En SQL, ¿está bien que dos tablas se refieran entre sí? donde mi respuesta es más o menos la misma. Sin embargo, MySQL no tiene índices parciales, por lo que las únicas opciones viables son el FK anulable y la solución de tabla adicional.


9

Depende de cuál sea su prioridad. ¿Desea evitar el trabajo o desea adherirse a las más estrictas reglas de normalización?

Personalmente, creo que es mejor estar IsFavoriteen la mesa de los niños, y estaría dispuesto a trabajar para asegurarse de que, como máximo, un niño por cada padre sea el favorito de ese padre. La razón principal no tiene nada que ver con lo de la clave externa anulable: no me gusta la idea de que las claves externas apunten en ambas direcciones.

La sugerencia de @ypercube también es un buen compromiso.

Por otro lado, por favor, por favor, no ensucies tu esquema con nombres de columna sin sentido como Id. Prefiero ver el Idnombre de una manera significativa en todo el esquema. ¿Identifica a un autor? Ok, llámalo AuthorID. ¿Representa un producto? Ok, ProductID. ¿Es un empleado y, en algunos casos, hace referencia a un gerente? Ok, EmployeeIDy ManagerIDtiene más sentido para mí que IDy Parent. Si bien puede parecer lógico dejar eso fuera (y es redundante ponerlo), cuando comience a escribir combinaciones complejas (o publique consultas aquí) definitivamente sentirá algo de maldición cuando intente realizar ingeniería inversa en un montón de combinaciones en ese punto a columnas como a.Parent = b.ID... blecch.


1

Los datos pertenecen a la tabla secundaria. Lo mantenemos correcto con un disparador en la tabla para garantizar que un único registro esté marcado como favorito (o en nuestro caso como la dirección preferida).

Sin embargo, la idea de @ypercube de una tabla separada también es buena.

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.