Sei que isso já foi perguntado aqui e aqui , mas tenho a mesma ideia com uma possível implementação diferente e preciso de ajuda.
Inicialmente eu tinha minha blogstories
tabela com essa estrutura:
| Column | Type | Description |
|-----------|-------------|------------------------------------------------|
| uid | varchar(15) | 15 characters unique generated id |
| title | varchar(60) | story title |
| content | longtext | story content |
| author | varchar(10) | id of the user that originally wrote the story |
| timestamp | int | integer generated with microtime() |
Depois que decidi que queria implementar algum sistema de versionamento para cada história do blog, a primeira coisa que me veio à cabeça foi criar uma tabela diferente para guardar as edições ; depois disso, pensei que poderia modificar a tabela existente para conter versões em vez de edições . Esta é a estrutura que me veio à mente:
| Column | Type | Description |
|------------ |------------- |------------------------------------------------ |
| story_id | varchar(15) | 15 characters unique generated id |
| version_id | varchar(5) | 5 characters unique generated id |
| editor_id | varchar(10) | id of the user that commited |
| author_id | varchar(10) | id of the user that originally wrote the story |
| timestamp | int | integer generated with microtime() |
| title | varchar(60) | current story title |
| content | longtext | current story text |
| coverimg | varchar(20) | cover image name |
As razões pelas quais eu vim aqui:
- O
uid
campo da tabela inicial era UNIQUE na tabela. Agora, ostory_id
não é mais exclusivo. Como devo lidar com isso? (Achei que poderia abordarstory_id = x
e encontrar a versão mais recente, mas isso parece consumir muitos recursos, então, por favor, dê seu conselho) author_id
o valor do campo está se repetindo em cada linha da tabela. Onde e como devo guardá-lo?
Editar
O processo de geração de códigos únicos está na CreateUniqueCode
função:
trait UIDFactory {
public function CryptoRand(int $min, int $max): int {
$range = $max - $min;
if ($range < 1) return $min;
$log = ceil(log($range, 2));
$bytes = (int) ($log / 8) + 1;
$bits = (int) $log + 1;
$filter = (int) (1 << $bits) - 1;
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
$rnd = $rnd & $filter;
} while ($rnd >= $range);
return $min + $rnd;
}
public function CreateUID(int $length): string {
$token = "";
$codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
$codeAlphabet.= "0123456789";
$max = strlen($codeAlphabet) - 1;
for ($i=0; $i < $length; $i++) {
$token .= $codeAlphabet[$this->CryptoRand(0, $max)];
}
return $token;
}
}
O código está escrito em Hack e foi originalmente escrito em PHP por @Scott em sua resposta .
Os campos author_id
e editor_id
podem ser diferentes, pois existem usuários com permissões suficientes para editar as histórias de qualquer pessoa.
Analisando o cenário — que apresenta características associadas ao assunto conhecidas como bancos de dados temporais — sob uma perspectiva conceitual, pode-se determinar que: (a) uma versão “atual” da história do blog e (b) uma versão “passada” da história do blog , embora muito semelhantes, são entidades de diferentes tipos.
Além disso, ao trabalhar no nível lógico de abstração, fatos (representados por linhas) de tipos distintos devem ser retidos em tabelas distintas. No caso em consideração, mesmo quando bastante semelhantes, (i) os fatos sobre as versões “atuais” são diferentes dos (ii) fatos sobre as versões “passadas” .
Portanto, recomendo gerenciar a situação por meio de duas tabelas:
um dedicado exclusivamente para as Versões “atuais” ou “presentes” das Histórias do Blog , e
uma que é separada, mas também ligada à outra, para todas as Versões “anteriores” ou “passadas” ;
cada um com (1) um número ligeiramente distinto de colunas e (2) um grupo diferente de restrições.
De volta à camada conceitual, considero que —em seu ambiente de negócios— Autor e Editor são noções que podem ser delineadas como Papéis que podem ser desempenhados por um Usuário , e esses aspectos importantes dependem da derivação de dados (via operações de manipulação de nível lógico) e interpretação (efetuada pelos leitores e escritores do Blog Stories , no nível externo do sistema informatizado de informação, com o auxílio de um ou mais programas aplicativos).
Vou detalhar todos esses fatores e outros pontos relevantes a seguir.
Regras do negócio
De acordo com meu entendimento de seus requisitos, as seguintes formulações de regras de negócios (reunidas em termos dos tipos de entidade relevantes e seus tipos de inter-relações) são especialmente úteis para estabelecer o esquema conceitual correspondente:
Diagrama IDEF1X expositivo
Consequentemente, para expor minha sugestão em virtude de um dispositivo gráfico, criei um exemplo de IDEF1X, um diagrama derivado das regras de negócios formuladas acima e de outros recursos que parecem pertinentes. É mostrado na Figura 1 :
Por que BlogStory e BlogStoryVersion são conceituados como dois tipos de entidades diferentes?
Porque:
Uma instância de BlogStoryVersion (ou seja, uma “passada”) sempre contém um valor para uma propriedade UpdatedDateTime , enquanto uma ocorrência de BlogStory (ou seja, uma “presente”) nunca o contém.
Além disso, as entidades desses tipos são identificadas exclusivamente pelos valores de dois conjuntos distintos de propriedades: BlogStoryNumber (no caso das ocorrências de BlogStory ) e BlogStoryNumber mais CreatedDateTime (no caso das instâncias de BlogStoryVersion ).
uma 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 (NIST) dos Estados Unidos. Baseia-se no material teórico inicial de autoria do único criador do modelo relacional , ou seja, Dr. EF Codd ; na visão Entidade-Relacionamento de dados, desenvolvida pelo Dr. PP Chen ; e também na Logical Database Design Technique, criada por Robert G. Brown.
Layout lógico SQL-DDL ilustrativo
Então, com base na análise conceitual apresentada anteriormente, declarei o design de nível lógico abaixo:
Testado neste SQL Fiddle que roda no MySQL 5.6.
a
BlogStory
mesaComo você pode ver no design de demonstração, defini a
BlogStory
coluna PRIMARY KEY (PK para abreviar) com o tipo de dados INT. A esse respeito, você pode corrigir um processo automático integrado que gera e atribui um valor numérico para essa coluna em cada inserção de linha. Se você não se importa em deixar lacunas ocasionalmente neste conjunto de valores, então você pode empregar o atributo AUTO_INCREMENT , comumente usado em ambientes MySQL.Ao inserir todos os seus
BlogStory.CreatedDateTime
pontos de dados individuais, você pode utilizar a função NOW() , que retorna os valores de data e hora atuais no servidor de banco de dados no exato instante da operação INSERT. Para mim, essa prática é decididamente mais adequada e menos propensa a erros do que o uso de rotinas externas.Desde que, conforme discutido nos comentários (agora removidos), você queira evitar a possibilidade de manter
BlogStory.Title
valores duplicados, é necessário configurar uma restrição UNIQUE para esta coluna. Devido ao fato de que um determinado Título pode ser compartilhado por várias (ou mesmo todas as) BlogStoryVersions “anteriores” , uma restrição UNIQUE não deve ser estabelecida para aBlogStoryVersion.Title
coluna.Incluí a
BlogStory.IsActive
coluna do tipo BIT(1) (embora um TINYINT também possa ser usado) caso você precise fornecer a funcionalidade DELETE “soft” ou “lógica”.Detalhes sobre a
BlogStoryVersion
mesaPor outro lado, o PK da
BlogStoryVersion
tabela é composto por (a)BlogStoryNumber
e (b) uma coluna nomeadaCreatedDateTime
que, claro, marca o instante preciso em que umaBlogStory
linha sofreu um INSERT.BlogStoryVersion.BlogStoryNumber
, além de fazer parte da PK, também é restrita como uma FOREIGN KEY (FK) que referenciaBlogStory.BlogStoryNumber
, configuração que reforça a integridade referencial entre as linhas dessas duas tabelas. Nesse sentido,BlogStoryVersion.BlogStoryNumber
não é necessário implementar a geração automática de a, pois, sendo configurado como FK, os valores INSERIDOS nesta coluna devem ser “extraídos” dos já incluídos na respectivaBlogStory.BlogStoryNumber
contraparte.A
BlogStoryVersion.UpdatedDateTime
coluna deve reter, como esperado, o ponto no tempo em que umaBlogStory
linha foi modificada e, como consequência, adicionada àBlogStoryVersion
tabela. Portanto, você também pode usar a função NOW() nessa situação.O Intervalo compreendido entre
BlogStoryVersion.CreatedDateTime
eBlogStoryVersion.UpdatedDateTime
expressa todo o Período durante o qual umaBlogStory
linha esteve “presente” ou “atual”.Considerações para uma
Version
colunaPode ser útil pensar como a coluna que contém o valor que representa uma determinada versão
BlogStoryVersion.CreatedDateTime
“passada” de um BlogStory . Considero isso muito mais benéfico do que um ou , pois é mais amigável no sentido de que as pessoas tendem a estar mais familiarizadas com os conceitos de tempo . Por exemplo, os autores ou leitores do blog podem se referir a uma BlogStoryVersion de maneira semelhante à seguinte:VersionId
VersionCode
1750
foi criada em ”.26 August 2015
9:30
As funções do autor e do editor : derivação e interpretação de dados
Com essa abordagem, você pode distinguir facilmente quem detém o “original”
AuthorId
de um BlogStory concreto SELECIONANDO a versão “mais antiga” de um determinadoBlogStoryId
FROM daBlogStoryVersion
tabela em virtude da aplicação da função MIN() aBlogStoryVersion.CreatedDateTime
.Desta forma, cada
BlogStoryVersion.AuthorId
valor contido em todas as linhas das Versões “posteriores” ou “sucessivas” indica, naturalmente, o identificador do Autor da respectiva Versão em questão, mas pode-se dizer também que tal valor está, ao mesmo tempo, denotando o Papel desempenhado pelo Usuário envolvido como Editor da Versão “original” de um BlogStory .Sim, um determinado
AuthorId
valor pode ser compartilhado por váriasBlogStoryVersion
linhas, mas na verdade essa é uma informação que diz algo muito significativo sobre cada Version , portanto, a repetição desse dado não é um problema.O formato das colunas DATETIME
Quanto ao tipo de dados DATETIME, sim, você está certo, “ MySQL recupera e exibe valores DATETIME no
YYYY-MM-DD HH:MM:SS
formato ' ' ”, mas você pode inserir com segurança os dados pertinentes dessa maneira e, quando precisar realizar uma consulta, basta faça uso das funções DATE e TIME incorporadas para, entre outras coisas, mostrar os valores relativos no formato apropriado para seus usuários. Ou você certamente pode realizar esse tipo de formatação de dados por meio do código do(s) seu(s) programa(s) aplicativo(s).Implicações das
BlogStory
operações UPDATESempre que uma
BlogStory
linha sofrer um UPDATE, você deve garantir que os valores correspondentes que estavam “presentes” até a modificação serem inseridos naBlogStoryVersion
tabela. Assim, sugiro cumprir essas operações dentro de uma única ACID TRANSACTION para garantir que sejam tratadas como uma Unidade de Trabalho indivisível. Você também pode empregar GATILHOS, mas eles tendem a tornar as coisas desarrumadas, por assim dizer.Apresentando uma coluna
VersionId
ouVersionCode
Se você optar (devido a circunstâncias comerciais ou preferências pessoais) por incorporar uma coluna
BlogStory.VersionId
ou para distinguir as BlogStoryVersions , deverá ponderar as seguintes possibilidades:BlogStory.VersionCode
A
VersionCode
could be required to be UNIQUE in (i) the wholeBlogStory
table and also in (ii)BlogStoryVersion
.Therefore, you have to implement a carefully tested and totally reliable method in order to generate and assign each
Code
value.Maybe, the
VersionCode
values could be repeated in differentBlogStory
rows, but never duplicated along with the sameBlogStoryNumber
. E.g., you could have:3
- Version83o7c5c
and, simultaneously,86
- Version83o7c5c
and958
- Version83o7c5c
.The later possibility opens another alternative:
Keeping a
VersionNumber
for theBlogStories
, so there could be:23
- Versions1, 2, 3…
;650
- Versions1, 2, 3…
;2254
- Versions1, 2, 3…
;Holding “original” and “subsequent” versions in a single table
Although maintaining all the BlogStoryVersions in the same individual base table is possible, I suggest not to do it because you would be mixing two distinct (conceptual) types of facts, which thus has undesirable side-effects on
But, on condition that you choose to follow that course of action, you can still take advantage of many of the ideas detailed above, e.g.:
BlogStoryNumber
) and a DATETIME column (CreatedDateTime
);Seeing that, by proceeding with such an approach, a
BlogStoryNumber
value will be duplicated as soon as “newer” Versions are added, an option that and that you could evaluate (which is very alike to those mentioned in the previous section) is establishing aBlogStory
PK composed of the columnsBlogStoryNumber
andVersionCode
, in this manner you would be able to uniquely identify each Version of a BlogStory. And you can try with a combination ofBlogStoryNumber
andVersionNumber
too.Similar scenario
You may find my answer to this question of help, since I as well propose enabling temporal capabilities in the concerning database to deal with a comparable scenario.
Uma opção é usar a Versão Normal Form (vnf). As vantagens incluem:
Um benefício adicional no seu caso, como os dados com versão são identificados exclusivamente tornando a data efetiva (a data em que a alteração foi feita) parte da chave, um campo version_id separado não é necessário.
Aqui está uma explicação para um tipo muito semelhante de entidade.
Mais detalhes podem ser encontrados em uma apresentação de slides aqui e um documento não totalmente completo aqui
sua relação
não está na 3ª forma normal. Para cada versão de sua história, o author_id é o mesmo. Então você precisa de duas relações para superar isso
A chave da primeira relação é
story_id
, a chave da segunda relação é a chave combinada(story_id, version_id)
. Se você não gosta de chave combinada, pode usar apenasversion_id
como chave