2011-10-21 48 views
79

que tengo 3 mesas relevantes en mi base de datos.clave externa a varias tablas

CREATE TABLE dbo.Group 
(
    ID int NOT NULL, 
    Name varchar(50) NOT NULL 
) 

CREATE TABLE dbo.User 
(
    ID int NOT NULL, 
    Name varchar(50) NOT NULL 
) 

CREATE TABLE dbo.Ticket 
(
    ID int NOT NULL, 
    Owner int NOT NULL, 
    Subject varchar(50) NULL 
) 

Los usuarios pertenecen a varios grupos. Esto se hace a través de una relación de muchos a muchos, pero irrelevante en este caso. Un ticket puede ser propiedad de un grupo o un usuario, a través del campo dbo.Ticket.Owner.

¿Cuál sería el MÁS CORRECTO manera de describir esta relación entre un boleto y, opcionalmente, un usuario o un grupo?

Estoy pensando que debo añadir una bandera en la tabla boleto que dice qué tipo es el dueño.

+0

Para mi mente cada billete es propiedad de un grupo. Es solo que un usuario es un grupo de uno. Qué opción 4 de los modelos @ nathan-skerl. Si usa Guids como claves, todo también funciona bastante bien – GraemeMiller

Respuesta

99

Usted tiene algunas opciones, que varían en toda la "corrección" y facilidad de uso. Como siempre, el diseño correcto depende de sus necesidades.

  • Simplemente podría crear dos columnas de entradas, OwnedByUserId y OwnedByGroupId, y tienen Fks anulables a cada mesa.

  • Se puede crear M: M tablas de referencia que permitan que tanto la compra de entradas: las relaciones del grupo: usuario y la entrada. ¿Tal vez en el futuro querrá permitir que un único ticket sea propiedad de múltiples usuarios o grupos? Este diseño no exige que un ticket debe ser propiedad de una sola entidad.

  • Se puede crear un grupo por defecto para todos los usuarios y tienen entradas simplemente ya sea propiedad de un cierto grupo o de un grupo de usuarios por defecto.

  • O (mi elección) modelar una entidad que actúa como una base para los usuarios y grupos, y tienen propietario entradas de dicha entidad.

Heres un ejemplo aproximado utilizando el esquema publicado:

create table dbo.PartyType 
( 
    PartyTypeId tinyint primary key, 
    PartyTypeName varchar(10) 
) 

insert into dbo.PartyType 
    values(1, 'User'), (2, 'Group'); 


create table dbo.Party 
(
    PartyId int identity(1,1) primary key, 
    PartyTypeid tinyint references dbo.PartyType(PartyTypeId), 
    unique (PartyId, PartyTypeId) 
) 

CREATE TABLE dbo.[Group] 
(
    ID int NOT NULL, 
    Name varchar(50) NOT NULL, 
    PartyTypeId as cast(2 as tinyint) persisted, 
    foreign key (ID, PartyTypeId) references Party(PartyId, PartyTypeID) 
) 

CREATE TABLE dbo.[User] 
(
    ID int NOT NULL, 
    Name varchar(50) NOT NULL, 
    PartyTypeId as cast(1 as tinyint) persisted, 
    foreign key (ID, PartyTypeId) references Party(PartyID, PartyTypeID) 
) 

CREATE TABLE dbo.Ticket 
(
    ID int NOT NULL, 
    [Owner] int NOT NULL references dbo.Party(PartyId), 
    [Subject] varchar(50) NULL 
) 
+0

Su solución me ayudó mucho, Gracias – mxasim

+4

Opción +1 para la opción número 4. – Catchops

+2

¿Cómo sería una consulta para tickets de Usuario/Grupo? Gracias. – paulkon

-2
CREATE TABLE dbo.OwnerType 
(
    ID int NOT NULL, 
    Name varchar(50) NULL 
) 

insert into OwnerType (Name) values ('User'); 
insert into OwnerType (Name) values ('Group'); 

Creo que esa sería la manera más general de representar lo que quiere en lugar de usar una bandera.

24

La primera opción en la lista @Nathan Skerl 's es lo que se llevó a cabo en un proyecto Una vez trabajé con, donde se estableció una relación similar entre tres tablas (Uno de ellos hace referencia a otros dos, uno por vez.)

Por lo tanto, la tabla de referencia tenía dos columnas clave externas, y también tenía una restricción para garantizar que se hizo referencia exactamente a una tabla (no ambas, ninguna) por una sola fila.

Así es como podría parecer cuando se aplica a las tablas:

CREATE TABLE dbo.[Group] 
(
    ID int NOT NULL CONSTRAINT PK_Group PRIMARY KEY, 
    Name varchar(50) NOT NULL 
); 

CREATE TABLE dbo.[User] 
(
    ID int NOT NULL CONSTRAINT PK_User PRIMARY KEY, 
    Name varchar(50) NOT NULL 
); 

CREATE TABLE dbo.Ticket 
(
    ID int NOT NULL CONSTRAINT PK_Ticket PRIMARY KEY, 
    OwnerGroup int NULL 
     CONSTRAINT FK_Ticket_Group FOREIGN KEY REFERENCES dbo.[Group] (ID), 
    OwnerUser int NULL 
     CONSTRAINT FK_Ticket_User FOREIGN KEY REFERENCES dbo.[User] (ID), 
    Subject varchar(50) NULL, 
    CONSTRAINT CK_Ticket_GroupUser CHECK (
     CASE WHEN OwnerGroup IS NULL THEN 0 ELSE 1 END + 
     CASE WHEN OwnerUser IS NULL THEN 0 ELSE 1 END = 1 
    ) 
); 

Como se puede ver, la tabla Ticket tiene dos columnas, y OwnerGroupOwnerUser, ambos de los cuales son claves externas anulables. (Las columnas respectivas en las otras dos tablas se convierten en claves primarias en consecuencia.) La restricción de comprobación CK_Ticket_GroupUser asegura que solo una de las dos columnas de clave externa contiene una referencia (la otra es NULL, por eso ambas tienen que ser anulables).

(La clave principal en Ticket.ID no es necesaria para esta aplicación particular, pero definitivamente no dañaría a tener uno en una tabla como ésta.)

+0

Esto es también lo que tenemos en nuestro software y lo evitaría si está intentando crear un marco de acceso a datos genérico. Este diseño aumentará la complejidad en la capa de la aplicación. –

+0

Soy realmente nuevo en SQL, así que corrígeme si es incorrecto, pero este diseño parece ser un enfoque para usar cuando confías plenamente en que solo necesitarás dos tipos de ticket. En el futuro, si se introdujo un tercer tipo de propietario de ticket, deberías agregar una tercera columna de clave externa anulable a la tabla. – Shadoninja

+0

@Shadoninja: No estás equivocado. De hecho, creo que es una forma completamente justa de expresarlo. En general estoy de acuerdo con este tipo de solución donde está justificado, pero ciertamente no sería lo primero en mi mente al considerar las opciones, precisamente por la razón que usted ha delineado. –

Cuestiones relacionadas