Padrão de design - uma das muitas tabelas principais

13

Me deparei com uma situação no banco de dados com bastante frequência, em que uma determinada tabela pode ser FK para uma de várias tabelas pai diferentes. Eu já vi duas soluções para o problema, mas nenhuma delas é pessoalmente satisfatória. Estou curioso para ver outros padrões que você já viu por aí. Há uma melhor forma de fazê-lo?

Um exemplo inventado
Digamos que meu sistema tenhaAlerts . Os alertas podem ser recebidos para uma variedade de objetos - Clientes, Notícias e Produtos. Um determinado alerta pode ser para um e apenas um item. Por qualquer motivo, os Clientes, os Artigos e os Produtos estão se movendo rapidamente (ou localizados) para que o texto / dados necessários não possam ser inseridos nos Alertas após a criação de um Alerta. Dada essa configuração, eu vi duas soluções.

Nota: Abaixo de DDL é para SQL Server, mas minha pergunta deve ser aplicável a qualquer DBMS.

Solução 1 - FKeys múltiplas anuláveis

Nesta solução, a tabela vinculada a uma das muitas tabelas possui várias colunas FK (por uma questão de brevidade, o DDL abaixo não mostra a criação da FK). O BOM - Nesta solução, é bom ter chaves estrangeiras. A opção nula dos FKs torna conveniente e relativamente fácil adicionar dados precisos. A consulta BAD não é boa porque requer instruções N LEFT JOINS ou N UNION para obter os dados associados. No SQL Server, especificamente o LEFT JOINS impede a criação de uma exibição indexada.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    ProductID   int null,
    NewsID      int null,
    CustomerID  int null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

ALTER TABLE Alert WITH CHECK ADD CONSTRAINT CK_OnlyOneFKAllowed 
CHECK ( 
    (ProductID is not null AND NewsID is     null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is not null and CustomerID is     null) OR 
    (ProductID is     null AND NewsID is     null and CustomerID is not null) 
)

Solução 2 - Um FK em cada tabela pai
Nesta solução, cada tabela 'pai' possui um FK na tabela Alert. Isso facilita a recuperação de alertas associados a um pai. No lado negativo, não há uma cadeia real do alerta para quem faz referência. Além disso, o modelo de dados permite alertas órfãos - onde um alerta não está associado a um produto, notícias ou cliente. Mais uma vez, várias LEFT JOINs para descobrir a associação.

CREATE TABLE Product (
    ProductID    int identity(1,1) not null,
    CreateUTC    datetime2(7) not null,
     Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT   PK_Product Primary Key CLUSTERED (ProductID)
)
CREATE TABLE Customer (
    CustomerID  int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
     Name       varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_Customer Primary Key CLUSTERED (CustomerID)
)
CREATE TABLE News (
    NewsID      int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    Name        varchar(100) not null
    AlertID     int null,
    CONSTRAINT  PK_News Primary Key CLUSTERED (NewsID)
)

CREATE TABLE Alert (
    AlertID     int identity(1,1) not null,
    CreateUTC   datetime2(7) not null,
    CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

Isso é apenas vida em um banco de dados de relações? Existem soluções alternativas que você achou mais satisfatórias?

EBarr
fonte
1
Você pode criar uma tabela pai, Alertable, com as seguintes colunas: ID, CreateDate, Nome, Tipo. Você pode ter sua tabela de três filhos FK, e seu ALert irá FK para apenas uma tabela, Alertable.
25412 AK
Em alguns casos, você faz um bom argumento - efetivamente a solução nº 3. Sempre que os dados são movidos rapidamente ou localizados, no entanto, eles não funcionam. Digamos, por exemplo, Produto, Cliente e Notícias, cada um tem uma tabela "Lang" correspondente para dar suporte ao Nome em vários idiomas. Se eu tiver que fornecer 'nome' no idioma nativo do usuário, não será possível armazená-lo Alertable. Isso faz algum sentido?
EBarr
1
@EBarr: "Se eu tiver que fornecer 'nome' no idioma nativo do usuário, não posso armazená-lo no Alertable. Isso faz algum sentido?" Não, não faz. Com seu esquema atual, se você precisar fornecer 'nome' no idioma nativo do usuário, poderá armazená-lo na tabela Produto, Cliente ou Notícias?
precisa saber é o seguinte
@ypercube Adicionei que cada uma dessas tabelas tem uma tabela de idiomas associada. Eu estava tentando criar um cenário em que o texto "nome" pode variar de acordo com a solicitação e, portanto, não pode ser armazenado em Alertável.
EBarr
A menos que ele já tenha um nome aceito, proponho o termo "junção de polvo" para a consulta que você faria para ver todos os alertas e os pais associados. :)
Nathan Long

Respostas:

4

Entendo a segunda solução como não aplicável, pois ela não oferece um relacionamento (objeto) para muitos (alerta).

Você está preso a apenas duas soluções devido à estrita conformidade com a 3NF.

Eu projetaria um esquema de acoplamento menor:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  -- See (1)
  -- AlertID     int identity(1,1) not null,

  AlertClass char(1) not null, -- 'P' - Product, 'C' - Customer, 'N' - News
  ForeignKey int not null,
  CreateUTC  datetime2(7) not null,

  -- See (2)
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertClass, ForeignKey)
)

-- (1) you don't need to specify an ID 'just because'. If it's meaningless, just don't.
-- (2) I do believe in composite keys

Ou, se o relacionamento de integridade for obrigatório, eu poderia projetar:

CREATE TABLE Product  (ProductID  int identity(1,1) not null, ...)
CREATE TABLE Customer (CustomerID int identity(1,1) not null, ...)
CREATE TABLE News     (NewsID     int identity(1,1) not null, ...)

CREATE TABLE Alert (
  AlertID     int identity(1,1) not null,
  AlertClass char(1) not null, /* 'P' - Product, 'C' - Customer, 'N' - News */
  CreateUTC  datetime2(7) not null,
  CONSTRAINT  PK_Alert Primary Key CLUSTERED (AlertID)
)

CREATE TABLE AlertProduct  (AlertID..., ProductID...,  CONSTRAINT FK_AlertProduct_X_Product(ProductID)    REFERENCES Product)
CREATE TABLE AlertCustomer (AlertID..., CustomerID..., CONSTRAINT FK_AlertCustomer_X_Customer(CustomerID) REFERENCES Customer)
CREATE TABLE AlertNews     (AlertID..., NewsID...,     CONSTRAINT FK_AlertNews_X_News(NewsID)             REFERENCES News)

De qualquer forma...

Três soluções válidas e outra a ser considerada para muitos relacionamentos (objetos)-um-um (alerta) ...

Estes apresentados, qual é a moral?

Eles diferem sutilmente e pesam o mesmo nos critérios:

  • desempenho em inserções e atualizações
  • complexidade na consulta
  • espaço de armazenamento

Então, escolha esse conforto para você.

Marcus Vinicius Pompeu
fonte
1
Obrigado pela contribuição; Eu concordo com a maioria dos seus comentários. Eu provavelmente descrevi engenhosamente o relacionamento necessário (excluindo a solução 2 em seus olhos), pois era um exemplo inventado. fn1 - entendido, eu estava simplificando bastante as tabelas para focar no problema. fn2 - teclas compostas e eu vou voltar! Quanto ao esquema de acoplamento menor, entendo a simplicidade, mas, pessoalmente, tente projetar com DRI sempre que possível.
EBarr
Analisando isso, comecei a duvidar da exatidão da minha solução ... de qualquer maneira, foi votada duas vezes, o que agradeço. Embora eu acredite que meu projeto é válido, mas não é adequado para o problema dado, como os sindicatos 'n' / junta não são abordados ...
Marcus Vinicius Pompeu
A sigla DRI me pegou. Para todos vocês, significa Integridade Referencial Declarativa, a técnica por trás da integridade referencial dos dados que é comumente implementada como ... (drum roll) ... instruções DDL FOREIGN KEY. Mais sobre en.wikipedia.org/wiki/Declarative_Referential_Integrity e msdn.microsoft.com/en-us/library/...
Marcus Vinicius Pompeu
1

Eu usei tabelas de junção mantidas por gatilho. a solução funciona muito bem como último recurso se a refatoração do banco de dados não for possível ou desejável. A idéia é que você tenha uma tabela disponível apenas para lidar com os problemas do RI, e todo o DRI será contra.

Chris Travers
fonte