De acordo com sua descrição do ambiente de negócios em consideração, existe uma estrutura de supertipo-subtipo que engloba Item - o supertipo - e cada uma de suas categorias , ou seja, carro , barco e avião (juntamente com outras duas que não foram divulgadas) - os subtipos—.
Detalharei abaixo o método que seguiria para gerenciar esse cenário.
Regras do negócio
Para começar a delinear o esquema conceitual relevante , algumas das regras comerciais mais importantes determinadas até o momento (restringindo a análise apenas às três categorias divulgadas , para manter as coisas o mais breve possível) podem ser formuladas da seguinte maneira:
- Um usuário possui zero-um-ou-muitos itens
- Um item pertence a exatamente um usuário em um instante específico
- Um item pode ser de propriedade de um para muitos usuários em momentos distintos
- Um item é classificado por exatamente uma categoria
- Um item é, sempre,
- um carro
- ou um barco
- ou um avião
Diagrama ilustrativo do IDEF1X
A Figura 1 exibe um diagrama IDEF1X 1 que eu criei para agrupar as formulações anteriores, juntamente com outras regras de negócios que parecem pertinentes:
Supertipo
Por um lado, Item , o supertipo, apresenta as propriedades † ou atributos comuns a todas as categorias , ou seja,
- CategoryCode - especificado como uma FOREIGN KEY (FK) que faz referência a Category.CategoryCode e funciona como um discriminador de subtipo , ou seja, indica a categoria exata do subtipo ao qual um determinado item deve ser conectado -,
- OwnerId - distinto como um FK que aponta para User.UserId , mas atribuí a ele um nome de função 2 para refletir suas implicações especiais com mais precisão -,
- Foo ,
- Bar ,
- Baz e
- CreatedDateTime .
Subtipos
Por outro lado, as propriedades ‡ que pertencem a cada categoria em particular , ou seja,
- Qux e Corge ;
- Grault , Garply e Plugh ;
- Xyzzy , Thud , Wibble e Flob ;
são mostrados na caixa de subtipo correspondente.
Identificadores
Em seguida, a Item.ItemId PRIMARY KEY (PK) migrou 3 para os subtipos com diferentes nomes de função, ou seja,
- CarId ,
- BoatId e
- PlaneId .
Associações mutuamente exclusivas
Como representado, há uma associação ou relação de cardinalidade um a um (1: 1) entre (a) cada ocorrência de supertipo e (b) sua instância complementar de subtipo.
O símbolo do subtipo exclusivo retrata o fato de que os subtipos são mutuamente exclusivos, ou seja, uma ocorrência concreta de item pode ser complementada apenas por uma única instância de subtipo: um carro , um avião ou um barco (nunca por dois ou mais).
† , ‡ Empreguei nomes de espaços reservados clássicos para dar direito a algumas das propriedades do tipo de entidade, pois suas denominações reais não foram fornecidas na pergunta.
Layout de nível lógico expositivo
Conseqüentemente, para discutir um design lógico expositivo, derivamos as seguintes instruções SQL-DDL com base no diagrama IDEF1X exibido e descrito acima:
-- You should determine which are the most fitting
-- data types and sizes for all your table columns
-- depending on your business context characteristics.
-- Also, you should make accurate tests to define the
-- most convenient INDEX strategies based on the exact
-- data manipulation tendencies of your business context.
-- As one would expect, you are free to utilize
-- your preferred (or required) naming conventions.
CREATE TABLE UserProfile (
UserId INT NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
BirthDate DATE NOT NULL,
GenderCode CHAR(3) NOT NULL,
Username CHAR(20) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT UserProfile_PK PRIMARY KEY (UserId),
CONSTRAINT UserProfile_AK1 UNIQUE ( -- Composite ALTERNATE KEY.
FirstName,
LastName,
GenderCode,
BirthDate
),
CONSTRAINT UserProfile_AK2 UNIQUE (Username) -- ALTERNATE KEY.
);
CREATE TABLE Category (
CategoryCode CHAR(1) NOT NULL, -- Meant to contain meaningful, short and stable values, e.g.; 'C' for 'Car'; 'B' for 'Boat'; 'P' for 'Plane'.
Name CHAR(30) NOT NULL,
--
CONSTRAINT Category_PK PRIMARY KEY (CategoryCode),
CONSTRAINT Category_AK UNIQUE (Name) -- ALTERNATE KEY.
);
CREATE TABLE Item ( -- Stands for the supertype.
ItemId INT NOT NULL,
OwnerId INT NOT NULL,
CategoryCode CHAR(1) NOT NULL, -- Denotes the subtype discriminator.
Foo CHAR(30) NOT NULL,
Bar CHAR(30) NOT NULL,
Baz CHAR(30) NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT Item_PK PRIMARY KEY (ItemId),
CONSTRAINT Item_to_Category_FK FOREIGN KEY (CategoryCode)
REFERENCES Category (CategoryCode),
CONSTRAINT Item_to_User_FK FOREIGN KEY (OwnerId)
REFERENCES UserProfile (UserId)
);
CREATE TABLE Car ( -- Represents one of the subtypes.
CarId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Qux CHAR(30) NOT NULL,
Corge CHAR(30) NOT NULL,
--
CONSTRAINT Car_PK PRIMARY KEY (CarId),
CONSTRAINT Car_to_Item_FK FOREIGN KEY (CarId)
REFERENCES Item (ItemId)
);
CREATE TABLE Boat ( -- Stands for one of the subtypes.
BoatId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Grault CHAR(30) NOT NULL,
Garply CHAR(30) NOT NULL,
Plugh CHAR(30) NOT NULL,
--
CONSTRAINT Boat_PK PRIMARY KEY (BoatId),
CONSTRAINT Boat_to_Item_FK FOREIGN KEY (BoatId)
REFERENCES Item (ItemId)
);
CREATE TABLE Plane ( -- Denotes one of the subtypes.
PlaneId INT NOT NULL, -- Must be constrained as (a) the PRIMARY KEY and (b) a FOREIGN KEY.
Xyzzy CHAR(30) NOT NULL,
Thud CHAR(30) NOT NULL,
Wibble CHAR(30) NOT NULL,
Flob CHAR(30) NOT NULL,
--
CONSTRAINT Plane_PK PRIMARY KEY (PlaneId),
CONSTRAINT Plane_to_Item_PK FOREIGN KEY (PlaneId)
REFERENCES Item (ItemId)
);
Como demonstrado, o tipo de super-entidade e cada um dos tipos de sub-entidade são representados pela tabela base correspondente .
As colunas CarId
, BoatId
e PlaneId
, restritas como as PKs das tabelas apropriadas, ajuda na representando o nível de conceptual associação de um-para-um a título de limitações FK § que aponte para a ItemId
coluna, a qual é constrangida como o PK da Item
tabela. Isso significa que, em um "par" real, as linhas de supertipo e subtipo são identificadas pelo mesmo valor de PK; portanto, é mais do que oportuno mencionar que
- (a) anexar uma coluna extra para armazenar valores substitutos controlados pelo sistema ” para (b) as tabelas que representam os subtipos são (c) inteiramente supérfluas .
§ Para evitar problemas e erros relacionados às definições de restrição KEY (particularmente ESTRANGEIRAS) - situação a que você se refere nos comentários -, é muito importante levar em conta a dependência da existência que ocorre entre as diferentes tabelas em mãos, como exemplificado em a ordem de declaração das tabelas na estrutura DDL expositiva, que também forneci neste SQL Fiddle .
‖ Eg, anexando uma coluna adicional com o AUTO_INCREMENT propriedade de uma tabela de uma base de dados construída em MySQL.
Considerações sobre integridade e consistência
É essencial ressaltar que, em seu ambiente de negócios, você deve (1) garantir que cada linha do "supertipo" seja sempre complementada pelo correspondente correspondente do "subtipo" e, por sua vez, (2) garantir que A linha "subtipo" é compatível com o valor contido na coluna "discriminador" da linha "supertipo".
Seria muito elegante aplicar essas circunstâncias de maneira declarativa , mas, infelizmente, nenhuma das principais plataformas SQL forneceu os mecanismos adequados para isso, tanto quanto eu sei. Portanto, recorrer ao código de procedimento dentro de ACID TRANSACTIONS é bastante conveniente, para que essas condições sejam sempre atendidas no banco de dados. Outra opção seria empregar TRIGGERS, mas eles tendem a tornar as coisas desarrumadas, por assim dizer.
Declarando visualizações úteis
Tendo um design lógico como o explicado acima, seria muito prático criar uma ou mais visualizações, isto é, tabelas derivadas que compreendem colunas que pertencem a duas ou mais das tabelas base relevantes . Dessa maneira, você pode, por exemplo, SELECIONAR diretamente a partir dessas visualizações sem precisar escrever todos os JOINs toda vez que precisar recuperar informações "combinadas".
Dados de amostra
A esse respeito, digamos que as tabelas base sejam "preenchidas" com os dados de amostra mostrados abaixo:
--
INSERT INTO UserProfile
(UserId, FirstName, LastName, BirthDate, GenderCode, Username, CreatedDateTime)
VALUES
(1, 'Edgar', 'Codd', '1923-08-19', 'M', 'ted.codd', CURDATE()),
(2, 'Michelangelo', 'Buonarroti', '1475-03-06', 'M', 'michelangelo', CURDATE()),
(3, 'Diego', 'Velázquez', '1599-06-06', 'M', 'd.velazquez', CURDATE());
INSERT INTO Category
(CategoryCode, Name)
VALUES
('C', 'Car'), ('B', 'Boat'), ('P', 'Plane');
-- 1. ‘Full’ Car INSERTion
-- 1.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(1, 1, 'C', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 1.2
INSERT INTO Car
(CarId, Qux, Corge)
VALUES
(1, 'Fantastic Car', 'Powerful engine pre-update!');
-- 2. ‘Full’ Boat INSERTion
-- 2.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(2, 2, 'B', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 2.2
INSERT INTO Boat
(BoatId, Grault, Garply, Plugh)
VALUES
(2, 'Excellent boat', 'Use it to sail', 'Everyday!');
-- 3 ‘Full’ Plane INSERTion
-- 3.1
INSERT INTO Item
(ItemId, OwnerId, CategoryCode, Foo, Bar, Baz, CreatedDateTime)
VALUES
(3, 3, 'P', 'This datum', 'That datum', 'Other datum', CURDATE());
-- 3.2
INSERT INTO Plane
(PlaneId, Xyzzy, Thud, Wibble, Flob)
VALUES
(3, 'Extraordinary plane', 'Traverses the sky', 'Free', 'Like a bird!');
--
Em seguida, uma vista vantajoso é um que reúne de colunas Item
, Car
e UserProfile
:
--
CREATE VIEW CarAndOwner AS
SELECT C.CarId,
I.Foo,
I.Bar,
I.Baz,
C.Qux,
C.Corge,
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Car C
ON C.CarId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
Naturalmente, uma abordagem semelhante pode ser seguida para que você possa SELECIONAR as informações "completas" Boat
e Plane
diretamente de uma única tabela (uma derivada, nesses casos).
Depois disso -Se você não se importa sobre a presença de marcas NULL no resultado conjuntos- com a seguinte definição de visualização, você pode, por exemplo, “a cobrar” colunas das tabelas Item
, Car
, Boat
, Plane
e UserProfile
:
--
CREATE VIEW FullItemAndOwner AS
SELECT I.ItemId,
I.Foo, -- Common to all Categories.
I.Bar, -- Common to all Categories.
I.Baz, -- Common to all Categories.
IC.Name AS Category,
C.Qux, -- Applies to Cars only.
C.Corge, -- Applies to Cars only.
--
B.Grault, -- Applies to Boats only.
B.Garply, -- Applies to Boats only.
B.Plugh, -- Applies to Boats only.
--
P.Xyzzy, -- Applies to Planes only.
P.Thud, -- Applies to Planes only.
P.Wibble, -- Applies to Planes only.
P.Flob, -- Applies to Planes only.
U.FirstName AS OwnerFirstName,
U.LastName AS OwnerLastName
FROM Item I
JOIN Category IC
ON I.CategoryCode = IC.CategoryCode
LEFT JOIN Car C
ON C.CarId = I.ItemId
LEFT JOIN Boat B
ON B.BoatId = I.ItemId
LEFT JOIN Plane P
ON P.PlaneId = I.ItemId
JOIN UserProfile U
ON U.UserId = I.OwnerId;
--
O código das visualizações aqui mostradas é apenas ilustrativo. Obviamente, fazer alguns exercícios e modificações de teste pode ajudar a acelerar a execução (física) das consultas em questão. Além disso, pode ser necessário remover ou adicionar colunas às visualizações, conforme as necessidades da empresa.
Os dados de amostra e todas as definições de visualização são incorporadas neste SQL Fiddle para que possam ser observadas "em ação".
Manipulação de dados: aliases de código e coluna de programa (s) de aplicativo
O uso do código do (s) aplicativo (s) (se é isso que você quer dizer com “código específico do servidor”) e os aliases da coluna são outros pontos significativos que você mencionou nos próximos comentários:
Eu consegui solucionar um problema [JOIN] com código específico do servidor, mas eu realmente não quero fazer isso - e - adicionar aliases a todas as colunas pode ser "estressante".
Muito bem explicado, muito obrigado. No entanto, como suspeitava, terei que manipular o conjunto de resultados ao listar todos os dados devido às semelhanças com algumas colunas, pois não quero usar vários aliases para manter a instrução mais limpa.
É oportuno indicar que, embora o código do programa de aplicação seja um recurso muito adequado para lidar com os recursos de apresentação (ou gráficos) dos conjuntos de resultados, é fundamental evitar a recuperação de dados linha por linha, para evitar problemas de velocidade de execução. O objetivo deve ser “buscar” os conjuntos de dados pertinentes in toto por meio dos robustos instrumentos de manipulação de dados fornecidos pelo mecanismo (precisamente) do conjunto da plataforma SQL, para que você possa otimizar o comportamento do seu sistema.
Além disso, a utilização de aliases para renomear uma ou mais colunas dentro de um certo escopo pode parecer estressante, mas, pessoalmente, vejo esse recurso como uma ferramenta muito poderosa que ajuda a (i) contextualizar e (ii) desambiguar o significado e a intenção atribuídos à colunas; portanto, esse é um aspecto que deve ser cuidadosamente ponderado com relação à manipulação dos dados de interesse.
Cenários semelhantes
Você também pode ajudar nesta série de postagens e neste grupo de postagens que contêm minha opinião sobre outros dois casos que incluem associações de supertipo-subtipo com subtipos mutuamente exclusivos.
Também propus uma solução para um ambiente de negócios que envolve um cluster de supertipos e subtipos, onde os subtipos não são mutuamente exclusivos nesta resposta (mais recente) .
Notas finais
1 Definição de Integração para Modelagem de Informações ( IDEF1X ) é uma técnica de modelagem de dados altamente recomendável que foi estabelecida como padrão em dezembro de 1993 pelo Instituto Nacional de Padrões e Tecnologia dos EUA (NIST). É solidamente baseado em (a) alguns dos trabalhos teóricos de autoria do único autordo modelo relacional , ou seja, Dr. EF Codd ; (b) a visão de entidade-relacionamento , desenvolvida pelo Dr. PP Chen ; e também (c) a Logical Database Design Technique, criada por Robert G. Brown.
2 No IDEF1X, um nome de função é um rótulo distinto atribuído a uma propriedade (ou atributo) de FK para expressar o significado que ela possui no escopo de seu respectivo tipo de entidade.
3 O padrão IDEF1X define migração de chave como “O processo de modelagem de colocar a chave primária de uma entidade pai ou genérica em seu filho ou entidade de categoria como uma chave estrangeira”.
Item
tabela inclui umaCategoryCode
coluna. Conforme mencionado na seção intitulada “Considerações sobre integridade e consistência”: