Digamos que eu tenha uma tabela chamada User_FriendList
, que possui as seguintes características:
CREATE TABLE User_FriendList (
ID ...,
User_ID...,
FriendList_IDs...,
CONSTRAINT User_Friendlist_PK PRIMARY KEY (ID)
);
E suponhamos que a referida tabela contenha os seguintes dados:
+----+---------+---------------------------+ | Identificação | User_ID | Friendlist_IDs | +----+---------+---------------------------+ | 1 | 102 | 2:15:66:35:26:17: | +----+---------+---------------------------+ | 2 | 114 | 1:12:63:33:24:16:102 | +----+---------+---------------------------+ | 3 | 117 | 6:24:52:61:23:90:97:118 | +----+---------+---------------------------+
Nota: O “:” (dois pontos) é o delimitador ao explodir em PHP em um arquivo array
.
Perguntas
Então:
Esta é uma maneira conveniente de “armazenar” o
IDs
de umFriendList
?Ou, em vez disso, devo ter linhas individuais com apenas um único
FriendId
valor em cada uma delas e, quando precisar recuperar todas as linhas de uma determinada lista , simplesmente realizar uma consulta comoSELECT * FROM UserFriendList WHERE UserId = 1
?
Gerenciando uma informação individual
Supondo que, no domínio do seu negócio,
então, cada dado específico reunido na
Friendlist_IDs
coluna de valores múltiplos representa uma informação separada que carrega um significado muito exato. Portanto, a referida colunaResposta curta
Conseqüentemente, você deve reter cada um dos
Friendlist_IDs
valores em (a) uma coluna que aceita exclusivamente um único valor por linha em (b) uma tabela que representa o tipo de associação em nível conceitual que pode ocorrer entre Usuários , ou seja, uma Amizade —como Vou exemplificar nas seções seguintes—.Desta forma, você poderá tratar (i) a referida tabela como uma relação matemática e (ii) a referida coluna como um atributo de relação matemática —tanto quanto o MySQL e seu dialeto SQL permitirem, é claro—.
Por quê?
Porque o modelo relacional de dados , criado pelo Dr. E. F. Codd , exige tabelas compostas por colunas que contenham exatamente um valor do domínio ou tipo aplicável por linha; assim, declarar uma tabela com uma coluna que pode conter mais de um valor do domínio ou tipo em questão (1) não representa uma relação matemática e (2) não permitiria obter as vantagens propostas no referido referencial teórico.
Modelando Amizades entre Usuários : Definindo primeiro as regras do ambiente de negócios
Recomendo fortemente começar a estruturar um banco de dados delimitando — antes de tudo — o esquema conceitual correspondente em virtude da definição das regras de negócio relevantes que, entre outros fatores, devem descrever os tipos de inter-relações existentes entre os distintos aspectos de interesse, ou seja, , os tipos de entidades aplicáveis e suas propriedades ; por exemplo:
Diagrama IDEF1X expositivo
Dessa forma, consegui derivar o diagrama IDEF1X 1 mostrado na Figura 1 , que integra a maioria das regras formuladas anteriormente:
Conforme ilustrado, Solicitante e Destinatário são denotações que expressam os Papéis desempenhados pelos Usuários específicos que participam de uma determinada Amizade .
Sendo assim, o tipo de entidade Friendship retrata um tipo de associação de razão de cardinalidade muitos-para-muitos (M:N) que pode envolver diferentes ocorrências de um mesmo tipo de entidade, ou seja, User . Como tal, é um exemplo da construção clássica conhecida como “lista de materiais” ou “explosão de peças”.
1 Definição de integração para modelagem de informações ( IDEF1X ) é uma técnica altamente recomendável que foi estabelecida como padrão em dezembro de 1993 pelo Instituto Nacional de Padrões e Tecnologia (NIST) dos EUA. É solidamente baseado em (a) o material teórico inicial de autoria do único criador do modelo relacional, ou seja, Dr. EF Codd ; sobre (b) a visão entidade-relacionamento de dados, desenvolvida pelo Dr. PP Chen ; e também sobre (c) a Logical Database Design Technique, criada por Robert G. Brown.
Projeto lógico ilustrativo de SQL-DDL
Então, a partir do diagrama IDEF1X apresentado acima, declarar um arranjo DDL como o que segue é muito mais “natural”:
Desta forma:
Vantagens de uma coluna de valor único
Como demonstrado, você pode, por exemplo:
Aproveite a integridade referencial imposta pelo sistema de gerenciamento de banco de dados (DBMS para abreviação) para a
Friendship.AddresseeId
coluna, pois restringi-la como uma FOREIGN KEY (FK para abreviar) que faz uma referência àUserProfile.UserId
coluna garante que todos os valores apontem para uma linha existente .Crie uma PRIMARY KEY (PK) composta composta pela combinação de colunas
(Friendship.RequesterId, Friendship.AddresseeId)
, ajudando a distinguir elegantemente todas as linhas INSERTadas e, naturalmente, proteger sua unicidade .Claro, isso significa que a anexação de uma coluna extra para valores substitutos atribuídos pelo sistema (por exemplo, uma configurada com a propriedade IDENTITY no Microsoft SQL Server ou com o atributo AUTO_INCREMENT no MySQL) e o ÍNDICE auxiliar é totalmente supérfluo .
Restrinja os valores retidos
Friendship.AddresseeId
a um tipo de dados preciso c (que deve corresponder, por exemplo, ao estabelecido paraUserProfile.UserId
, neste caso INT), deixando que o SGBD se encarregue da validação automática pertinente.Esse fator também pode ajudar a (a) utilizar as funções de tipo incorporadas correspondentes e (b) otimizar o uso do espaço em disco .
Otimize a recuperação de dados no nível físico configurando pequenos e rápidos ÍNDICES subordinados para a
Friendship.AddresseeId
coluna, pois esses elementos físicos podem auxiliar substancialmente na agilização das consultas que envolvem a referida coluna.Certamente, você pode, por exemplo, colocar um ÍNDICE de uma coluna para
Friendship.AddresseeId
sozinho, um de várias colunas que englobaFriendship.RequesterId
eFriendship.AddresseeId
, ou ambos.Evite a complexidade desnecessária introduzida pela “busca por” valores distintos que são coletados juntos dentro da mesma coluna (muito provavelmente duplicados, digitados incorretamente etc.), um curso de ação que acabaria por desacelerar o funcionamento do seu sistema, porque você têm que recorrer a métodos não relacionais que consomem tempo e recursos para realizar essa tarefa.
Portanto, existem vários motivos que exigem uma análise cuidadosa do ambiente de negócios relevante para marcar o tipo d de cada coluna da tabela com precisão.
Conforme exposto, o papel desempenhado pelo designer de banco de dados é fundamental para fazer o melhor uso de (i) os benefícios de nível lógico oferecidos pelo modelo relacional e (ii) os mecanismos físicos fornecidos pelo SGBD de escolha.
a , b , c , d Evidentemente, ao trabalhar com plataformas SQL (por exemplo, Firebird e PostgreSQL ) que suportam a criação de DOMAIN (um recurso relacional distinto ), você pode declarar colunas que aceitam apenas valores que pertencem a seus respectivos (adequadamente restritos e às vezes compartilhado) DOMÍNIOS.
Um ou mais programas aplicativos que compartilham o banco de dados em consideração
Quando você tiver que empregar
arrays
no código do(s) programa(s) aplicativo(s) que acessa(m) o banco de dados, basta recuperar o(s) conjunto(s) de dados relevante(s) completo (s) e então “vinculá-lo(s) à estrutura de código em questão ou executar o processo(s) de aplicativo(s) associado(s) que deve(m) ocorrer.Outros benefícios das colunas de valor único: As extensões da estrutura do banco de dados são muito mais fáceis
Outra vantagem de manter o
AddresseeId
ponto de dados em sua coluna reservada e devidamente tipada é que facilita consideravelmente a extensão da estrutura do banco de dados, conforme exemplificarei a seguir.Progressão do cenário: Incorporando o conceito de Friendship Status
Uma vez que Friendships podem evoluir com o tempo, você pode ter que acompanhar tal fenômeno, então você teria que (1) expandir o esquema conceitual e (2) declarar mais algumas tabelas no layout lógico. Então, vamos organizar as próximas regras de negócio para delinear as novas incorporações:
Diagrama IDEF1X estendido
Sucessivamente, o diagrama IDEF1X anterior pode ser estendido para incluir os novos tipos de entidades e tipos de inter-relacionamento descritos acima. Um diagrama representando os elementos anteriores associados aos novos é apresentado na Figura 2 :
Adições de estrutura lógica
Depois, podemos alongar o layout DDL com as seguintes declarações:
A criação deste design DDL, junto com o trecho anterior, foi testada neste db<>fiddle que roda no SQL Server 2019.
Assim, toda vez que o Status de uma determinada Amizade precisar ser atualizado, os Usuários teriam apenas que INSERIR uma nova
FriendshipStatus
linha, contendo:os valores adequados
RequesterId
eAddresseeId
—retirados da respectiva linha—Friendship
;o valor novo e significativo
StatusCode
—extraído de—MyStatus.StatusCode
;o instante exato da INSERção, ou seja,
SpecifiedDateTime
—de preferência usando uma função de servidor para que você possa recuperá-la e retê-la de maneira confiável—; eo
SpecifierId
valor que indicaria o respectivoUserId
que inseriu o novoFriendshipStatus
no sistema —idealmente, com o auxílio das facilidades de seu(s) app(s)—.Nesse sentido, vamos supor que a
MyStatus
tabela inclua os seguintes dados — com valores PK que são (a) amigáveis ao usuário final, programador de aplicativos e DBA e (b) pequenos e rápidos em termos de bytes no nível de implementação física —:Assim, a
FriendshipStatus
tabela pode conter dados como mostrado abaixo:Como você pode ver, pode-se dizer que a
FriendshipStatus
tabela serve para compor uma série temporal .Respostas aos comentários
Manipulação de dados
First of all, there is nothing wrong with your question and, although this is a design Q & A and data manipulation is a separate and subsequent task, explaining how to get the current or most-recent Status of a given Friendship can definitely assist in showing the advantages of the proposed layout (and therefore it can help a future reader).
Here the key is approaching the relevant data in terms of sets, since that is one of the main puroposes of adopting a relational frame of mind.
Deriving the current Status of a Friendship
Let us suppose that we are working with the following information in the
FriendhipStatus
table:And say we want to obtain the current Status of the Friendship between the User primarily identified by the UserId 1750 and the User primarily identified by the UserId 1748.
A very good option is employing what is commonly known as a subquery, i.e., a nested DML operation (in this case a SELECT):
In this manner, we would be exclusively deriving the
CurrentStatusCode
—contextual alias forFriendshipStatus.StatusCode
— in a scalar column in the main SELECT operation. The nested SELECT gets the latestFriendshipStatus.SpecifiedDateTime
value —via the MAX() function— for the corresponding Friendship, i.e., theRequesterId
andAddresseeId
values assigned in the main SELECT, and these three values are in turn used as conditions in the main WHERE clause. As a result, we derive the following column and value for the Requester 1750 and the Addressee 1748:Which means that one of the Friends involved blocked the other.
And if you want to obtain more information about that Friendship along with the current Status, you simply have to incorporate more relevant columns to the main SELECT:
Having all those data points you would know, e.g., who “blocked” who and when.
Views
If you are interested in simplifying future code regarding Status data retrieval (and make it more “readable”), you can create a view (i.e. a derived table) that, e.g., “hides” the subquery, and you can subsequently SELECT directly from said view.
Physical-level settings
Of course, in order to speed up data manipulation performance you have to take care of the design settings at the physical level of abstraction; e.g., the involved tables should be supported by single- and/or multi-column INDEXes, taking into account, e.g., the order of the columns involved in WHERE conditions. Then you should evaluate the data manipulation tendencies with respect to all the relevant INSERT, UPDATE, SELECT and DELETE operations.
You could as well evaluate the implementation of “materialized” views.
All the DML code samples shown above, the sample data and other relevant aspects are included in this db<>fiddle so that you can see these exercises “in action”.
Data constraints
CHECK constraint
Yes, you certainly can prevent the values retained in the columns
Friendship.RequesterId
andFriendship.AddresseeId
from being identical in the same row without triggers (which, as you know, should be left as a very last resort). In this case I would make use of a CHECK constraint (that i just added to the DDL logical layout shown in prior sections), which allows handling this requirement declaratively (in contrast to the sub-optimal procedural option with TRIGGERs):So, if you want to INSERT, e.g., the row that follows:
…the DBMS (in your case SQL Server) should reject that row and throw a message like this one:
Existence dependencies and TRANSACTIONs
An important aspect that should be treated carefully when creating the database structure and the related constraints has to do with (1) the order in the creation of the tables and (2) the order of the creation of the constraints, since these factors are instrumental in the representation of the existence dependencies that take place in the business context of concern, so please make sure that you first create the “independent” tables, then continue creating the ones that depend on the existence and constraints of those and so on. As demonstrated in this db<>fiddle running on SQL Server 2019, no issue came about when creating the database structure and constraints.
In that same regard, if you have to DELETE a row that has FK references, instead of using ON DELETE CASCADE actions (which tend to consume a lot of DBMS resources) I suggest that you carry out the required manipulation operations via TRANSACTIONs, and in order to optimize DBMS resources you also have to take into account the existence dependencies of the concerning data. In this way, you would first DELETE the “dependent” rows (in the conceptual hierarchy) and once all those “dependent” rows have been deleted then you carry out the DELETE of the “main” row of interest, all of this in a single TRANSACTION. Similar considerations apply for UPDATES, thus the data hierarchy also has to be respected in order to optimize the data manipulation processes at the physical level.
Let us assume that due to a certain reason you have to DELETE a certain UserProfile and all of its related info, so you would proceed deleting
FriendshipStatus
table, thenFriendship
table, and after thatUserProfile
table.All of the above in that specific order, inside the same TRANSACTION, so as to guarantee that all the operations are either committed or discarded as a single unit of work by the DBMS.
Note: In agreement with the previous deliberations, it is very important to point out that all the INSERT, UPDATE and DELETE operations of a given database should be performed exclusively within TRANSACTIONs inside stored procedures which only some privileged database users can execute. This strategy optimizes data integrity protection in particular and database management in general.
Further constraints
Generally, in scenarios like the one discussed here, a designer has to set up further data constraints, but I am not going to address those aspects because it can be a very didactic endeavour for future readers, and as you know, constraints can change from one business context to the other.
Additional considerations on the
Friendship
andFriendshipStatus
tablesIf I address your inquiries here in the answer body I hit the “limit of 30000 characters” within just a few more paragraphs, and the comment box is not well suited for a proper reply (comments are meant to be transient, offer very poor formatting options, permit even fewer characters, etc.), therefore I have uploaded my reply to your questions in this external file.
Relevant posts
You might as well be interested in: