Estou tendo problemas para recriar um banco de dados com um relacionamento um-para-muitos (1:M) entre Users e Items .
Isso é bastante direto, sim; no entanto, cada Item pertence a uma determinada Categoria (por exemplo, um Carro , um Barco ou um Avião ), e cada Categoria possui um determinado número de atributos, por exemplo:
Car
estrutura:
+----+--------------+--------------+
| PK | Attribute #1 | Attribute #2 |
+----+--------------+--------------+
Boat
estrutura:
+----+--------------+--------------+--------------+
| PK | Attribute #1 | Attribute #2 | Attribute #3 |
+----+--------------+--------------+--------------+
Plane
estrutura:
+----+--------------+--------------+--------------+--------------+
| PK | Attribute #1 | Attribute #2 | Attribute #3 | Attribute #4 |
+----+--------------+--------------+--------------+--------------+
Devido a essa diversidade na quantidade de atributos (colunas), inicialmente pensei que seria uma boa ideia criar uma tabela separada para cada Category , assim evitaria vários NULLs e assim aproveitaria melhor a indexação.
Embora parecesse ótimo no começo, não consegui encontrar uma maneira de criar o relacionamento entre os itens e as categorias através do banco de dados porque, pelo menos em minha modesta experiência como administrador de banco de dados, ao criar chaves estrangeiras, informo explicitamente um banco de dados o nome da tabela e a coluna.
No final, gostaria de uma estrutura sólida para armazenar todos os dados, mas com todos os meios para listar todos os atributos de todos os itens que um usuário possa ter com uma consulta.
Eu poderia codificar consultas dinâmicas com a linguagem do lado do servidor , mas acho que isso está errado e não é o ideal.
Informação adicional
Estas são minhas respostas aos comentários do MDCCL:
1. Quantas categorias de itens de interesse existem em seu contexto de negócios, três (ou seja, carros , barcos e aviões ) ou mais?
Na verdade, é muito simples: existem apenas cinco categorias no total.
2. O mesmo Item pertencerá sempre ao mesmo Utilizador (ou seja, uma vez que um determinado Item tenha sido “atribuído” a um determinado Utilizador não pode ser alterado)?
Não, eles podem mudar. No cenário fictício da pergunta, seria como se o usuário A vendesse o item nº 1 para o usuário B , portanto, a propriedade deve ser refletida.
3. Existem atributos compartilhados por algumas ou todas as categorias ?
Não compartilhado, mas, de memória, posso dizer que pelo menos três atributos estão presentes em todas as Categorias .
4. Existe uma chance de que a cardinalidade do relacionamento entre Usuário e Item seja muitos-para-muitos (M:N) em vez de um-para-muitos (1:M)? Por exemplo, no caso das seguintes regras de negócios:
A User owns zero-one-or-many Items
eAn Item is owned by one-to-many Users
Não, porque Itens descreveriam um objeto físico. Os usuários terão uma cópia virtual deles, cada um identificado por um GUID v4 exclusivo
5. Em relação à sua seguinte resposta a um dos comentários da pergunta:
“No cenário fictício da pergunta, seria como se o usuário A vendesse o item nº 1 para o usuário B , então a propriedade deve ser refletida.”
Parece que você está planejando acompanhar a evolução da propriedade do item , por assim dizer. Desta forma, quais atributos você gostaria de armazenar sobre tal fenômeno? Apenas a modificação do atributo que indica o Usuário específico que é o Dono de um Item específico ?
Não, na verdade não. A propriedade pode mudar, mas não preciso acompanhar o proprietário anterior .
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 mais dois que não foram divulgados) — os subtipos—.
Vou detalhar abaixo o método que eu empregaria para gerenciar esse cenário.
Regras do negócio
Para começar a delinear o esquema conceitual relevante, algumas das regras de negócios mais importantes determinadas até agora (restringindo a análise apenas às três categorias divulgadas , para manter as coisas o mais breves possível) podem ser formuladas da seguinte forma:
Diagrama IDEF1X ilustrativo
A Figura 1 exibe um diagrama IDEF1X 1 que criei para agrupar as formulações anteriores junto 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,
Subtipos
Por outro lado, as propriedades ‡ que pertencem a cada categoria particular , ou seja,
são mostrados na caixa de subtipo correspondente.
Identificadores
Então, a Item.ItemId PRIMARY KEY (PK) migrou 3 para os subtipos com diferentes nomes de função, ou seja,
associações mutuamente exclusivas
Conforme ilustrado, há uma associação ou relacionamento com uma razão de cardinalidade de um para um (1:1) entre (a) cada ocorrência de supertipo e (b) sua instância de subtipo complementar.
O símbolo de subtipo exclusivo retrata o fato de que os subtipos são mutuamente exclusivos, ou seja, uma ocorrência de Item concreto pode ser suplementada por uma única instância de subtipo: seja um Carro , ou um Avião , ou um Barco (nunca por zero ou menos, nem por dois ou mais).
† , ‡ Empreguei nomes de espaços reservados clássicos para autorizar algumas das propriedades do tipo entidade, pois suas denominações reais não foram fornecidas na pergunta.
Layout de nível lógico expositivo
Conseqüentemente, para discutir um projeto lógico expositivo, deriguei as seguintes instruções SQL-DDL com base no diagrama IDEF1X exibido e descrito acima:
Isso foi testado neste db<>fiddle rodando no MySQL 8.0.
Conforme demonstrado, o tipo de superentidade e cada um dos tipos de subentidade são representados pela tabela base correspondente.
As colunas
CarId
,BoatId
ePlaneId
, restritas como as PKs das tabelas apropriadas, ajudam a representar a associação um-para-um em nível conceitual por meio de restrições FK § que apontam para aItemId
coluna, que é restrita como a PK daItem
tabela. Isso significa que, em um “par” real, as linhas do supertipo e do subtipo são identificadas pelo mesmo valor de PK; assim, é mais do que oportuno mencionar que§ Para evitar problemas e erros relativos às definições de restrições (especialmente FOREIGN) KEY —situação que você mencionou nos comentários—, é muito importante levar em conta a existência-dependência que ocorre entre as diferentes tabelas em questão, conforme exemplificado em a ordem de declaração das tabelas na estrutura DDL expositiva, que também forneci neste db<>fiddle .
Por exemplo, anexar uma coluna adicional com a propriedade AUTO_INCREMENT a uma tabela de um banco de dados construído em MySQL.
Considerações sobre integridade e consistência
É importante ressaltar que, em seu ambiente de negócios, você deve (1) garantir que cada linha de “supertipo” seja sempre complementada por sua correspondente “subtipo” correspondente e, por sua vez, (2) garantir que o referido linha “subtipo” é compatível com o valor contido na coluna “discriminador” da linha “supertipo”.
Seria muito elegante impor tais circunstâncias de maneira declarativa , mas, infelizmente, nenhuma das principais plataformas SQL forneceu os mecanismos adequados para fazê-lo, até onde eu sei. Portanto, recorrer ao código processual dentro de ACID TRANSACTIONS é bastante conveniente para que essas condições sejam sempre atendidas em seu banco de dados. Outra opção seria empregar GATILHOS, mas eles tendem a desarrumar as coisas, por assim dizer.
Declarando visualizações úteis
Tendo um desenho lógico como o explicado acima, seria muito prático criar uma ou mais visões, ou seja, tabelas derivadas que compreendem colunas pertencentes a duas ou mais tabelas base relevantes. Dessa forma, você pode, por exemplo, SELECT diretamente dessas visualizações sem ter que escrever todos os JOINs toda vez que precisar recuperar informações “combinadas”.
Dados de amostra
A esse respeito, digamos que as tabelas base são “preenchidas” com os dados de amostra mostrados abaixo:
Então, uma visualização vantajosa é aquela que reúne colunas de
Item
,Car
eUserProfile
:Naturally, a similar approach can be followed so that you can as well SELECT the “full”
Boat
andPlane
information straight FROM one single table (a derived one, in these cases).After that —if you do not mind about the presence of NULL marks in result sets— with the following VIEW definition, you can, e.g., “collect” columns from the tables
Item
,Car
,Boat
,Plane
andUserProfile
:The code of the views here shown is only illustrative. Of course, doing some testing exercises and modifications might help to accelerate the (physical) execution of the queries at hand. In addition, you might need to remove or add columns to said views as the business needs dictate.
The sample data and all the view definitions are incorporated into this db<>fiddle so that they can be observed “in action”.
Data manipulation: Application program(s) code and column aliases
The usage of application program(s) code (if that is what you mean by “server-side specific code”) and column aliases are other significant points that you brought up in the next comments:
It is opportune to indicate that while using application program code is a very fitting resource to handle the presentation or graphical features —i.e., the external level of representation of a computerized information system— of data sets, it is paramount that you avoid carrying out data retrieval on a row-by-row basis to prevent execution speed issues. The objective should be to “fetch” the pertinent data sets in toto by means of the robust data manipulation instruments provided by the (precisely) set engine of the SQL platform so that you can optimize the behaviour of your system.
Furthermore, utilizing aliases to rename one or more columns within a certain scope may appear stressing but, personally, I see such resource as a very powerful tool that helps to (i) contextualize and (ii) disambiguate the meaning and intention ascribed to the concerning columns; hence, this is an aspect that should be thoroughly pondered with respect to the manipulation of the data of interest.
Similar scenarios
You might as well find of help this series of posts and this group of posts which contain my take on two other cases that include supertype-subtype associations with mutually exclusive subtypes.
I have also proposed a solution for a business environment involving a supertype-subtype cluster where the subtypes are not mutually exclusive in this (newer) answer.
Endnotes
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 umastandard in December 1993 by the U.S. National Institute of Standards and Technology (NIST). It is solidly based on (a) some of the theoretical works authored by the sole originator of the relational model, i.e., Dr. E. F. Codd; on (b) the entity-relationship view, developed by Dr. P. P. Chen; and also on (c) the Logical Database Design Technique, created by Robert G. Brown.
2 Em IDEF1X, um nome de função é um rótulo distintivo atribuído a uma propriedade (ou atributo) FK para expressar o significado que ela possui dentro do escopo de seu respectivo tipo de entidade.
3 O padrão IDEF1X define a migração de chave como “O processo de modelagem de colocar a chave primária de uma entidade pai ou genérica em sua entidade filha ou categoria como uma chave estrangeira”.
Vamos chamar a tabela principal de Produtos. Isso hospeda os atributos compartilhados. Então, digamos que temos uma mesa de carro, uma mesa de avião e uma mesa de barco. Essas três tabelas teriam uma chave ProductID com uma restrição FK na linha ID da tabela Product. Se você quiser todos eles - junte-se a eles. Se você quiser apenas os carros, junte carros à esquerda com produtos (ou junte produtos e carros à direita, mas prefiro sempre usar junções à esquerda).
Isso é chamado de modelo de dados hierárquico. Para um número baixo de subtabelas, pode fazer sentido em uma tabela longa (milhões de produtos).