Analisando o cenário - que apresenta características associadas ao assunto conhecido como bancos de dados temporais - de uma perspectiva conceitual, pode-se determinar que: (a) uma versão “presente” da história do blog e (b) uma versão “anterior” 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) fatos sobre as versões "presentes" são diferentes de (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
um que é separado, mas também vinculado ao outro, para todas as versões “anteriores” ou “passadas” ;
cada uma 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 funções que podem ser desempenhadas por um usuário , e esses aspectos importantes dependem da derivação de dados (através de operações de manipulação no nível lógico) e interpretação (realizada pelos leitores e escritores do Blog Stories , no nível externo do sistema informatizado de informações, com a assistência de um ou mais programas aplicativos).
Vou detalhar todos esses fatores e outros pontos relevantes da seguinte forma.
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 entidades relevantes e seus tipos de inter-relações) são especialmente úteis no estabelecimento do esquema conceitual correspondente :
- Um usuário grava zero ou um ou muitos BlogStories
- Um BlogStory contém zero-um-ou-muitos BlogStoryVersions
- Um usuário escreveu zero-um-ou-muitos BlogStoryVersions
Diagrama do IDEF1X expositivo
Consequentemente, para expor minha sugestão em virtude de um dispositivo gráfico, criei uma amostra IDEF1X, um diagrama derivado das regras de negócios formuladas acima e de outros recursos que parecem pertinentes. É mostrado na Figura 1 :
Por que o BlogStory e o BlogStoryVersion são conceituados como dois tipos diferentes de entidade?
Porque:
Uma instância do BlogStoryVersion (por exemplo, uma "passada") sempre mantém um valor para uma propriedade UpdatedDateTime , enquanto uma ocorrência do BlogStory (por exemplo, uma "presente") nunca a manté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 do BlogStory ) e BlogStoryNumber mais CreatedDateTime (no caso das instâncias do 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 dos Estados Unidos(NIST). Baseia-se no material teórico inicial criado pelo único autordo modelo relacional , ou seja, Dr. EF Codd ; navisão Entity-Relationship of Data, desenvolvida pelo Dr. PP Chen ; e também na Logical Database Design Technique, criada por Robert G. Brown.
Layout SQL-DDL lógico ilustrativo
Então, com base na análise conceitual apresentada anteriormente, declarei o design de nível lógico abaixo:
-- 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 at the physical level.
-- As one would expect, you are free to make use of
-- your preferred (or required) naming conventions.
CREATE TABLE UserProfile (
UserId INT NOT NULL,
FirstName CHAR(30) NOT NULL,
LastName CHAR(30) NOT NULL,
BirthDate DATETIME 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,
BirthDate,
GenderCode
),
CONSTRAINT UserProfile_AK2 UNIQUE (UserName) -- ALTERNATE KEY.
);
CREATE TABLE BlogStory (
BlogStoryNumber INT NOT NULL,
Title CHAR(60) NOT NULL,
Content TEXT NOT NULL,
CoverImageName CHAR(30) NOT NULL,
IsActive BIT(1) NOT NULL,
AuthorId INT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
--
CONSTRAINT BlogStory_PK PRIMARY KEY (BlogStoryNumber),
CONSTRAINT BlogStory_AK UNIQUE (Title), -- ALTERNATE KEY.
CONSTRAINT BlogStoryToUserProfile_FK FOREIGN KEY (AuthorId)
REFERENCES UserProfile (UserId)
);
CREATE TABLE BlogStoryVersion (
BlogStoryNumber INT NOT NULL,
CreatedDateTime DATETIME NOT NULL,
Title CHAR(60) NOT NULL,
Content TEXT NOT NULL,
CoverImageName CHAR(30) NOT NULL,
IsActive BIT(1) NOT NULL,
AuthorId INT NOT NULL,
UpdatedDateTime DATETIME NOT NULL,
--
CONSTRAINT BlogStoryVersion_PK PRIMARY KEY (BlogStoryNumber, CreatedDateTime), -- Composite PK.
CONSTRAINT BlogStoryVersionToBlogStory_FK FOREIGN KEY (BlogStoryNumber)
REFERENCES BlogStory (BlogStoryNumber),
CONSTRAINT BlogStoryVersionToUserProfile_FK FOREIGN KEY (AuthorId)
REFERENCES UserProfile (UserId),
CONSTRAINT DatesSuccession_CK CHECK (UpdatedDateTime > CreatedDateTime) --Let us hope that MySQL will finally enforce CHECK constraints in a near future version.
);
Testado neste SQL Fiddle que roda no MySQL 5.6.
A BlogStory
mesa
Como você pode ver no design da demonstração, eu defini a BlogStory
coluna PRIMARY KEY (PK for brevity) com o tipo de dados INT. Nesse sentido, convém corrigir um processo automático interno 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 nesse conjunto de valores, 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 instante exato 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ê deseje evitar a possibilidade de manter BlogStory.Title
valores duplicados, precisará 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 "passadas" , uma restrição ÚNICA não deve ser estabelecida para a BlogStoryVersion.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 "leve" ou "lógica".
Detalhes sobre a BlogStoryVersion
tabela
Por outro lado, a PK da BlogStoryVersion
tabela é composta por (a) BlogStoryNumber
e (b) uma coluna denominada CreatedDateTime
que, é claro, marca o instante exato em que uma BlogStory
linha passou por um INSERT.
BlogStoryVersion.BlogStoryNumber
, além de fazer parte do PK, também é restrito como uma FOREIGN KEY (FK) que faz referência BlogStory.BlogStoryNumber
, uma configuração que impõe integridade referencial entre as linhas dessas duas tabelas. Nesse sentido, a implementação de uma geração automática de a BlogStoryVersion.BlogStoryNumber
não é necessária porque, sendo definido como um FK, os valores INSERTed nesta coluna devem ser "extraídos" daqueles já incluídos na BlogStory.BlogStoryNumber
contraparte relacionada .
A BlogStoryVersion.UpdatedDateTime
coluna deve reter, como esperado, o momento em que uma BlogStory
linha foi modificada e, como conseqüência, adicionada à BlogStoryVersion
tabela. Portanto, você também pode usar a função NOW () nessa situação.
O Intervalo compreendeu BlogStoryVersion.CreatedDateTime
e BlogStoryVersion.UpdatedDateTime
expressa todo o Período durante o qual uma BlogStory
linha estava "presente" ou "atual".
Considerações para uma Version
coluna
Pode ser útil pensar BlogStoryVersion.CreatedDateTime
como a coluna que contém o valor que representa uma versão "passada" específica de uma BlogStory . Considero isso muito mais benéfico do que um VersionId
ou VersionCode
, uma vez que é mais amigável no sentido em 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 uma maneira semelhante à seguinte:
- "Quero ver a versão específica do BlogStory identificada pelo número
1750
que foi criado em 26 August 2015
às 9:30
".
O Autor e Editor de Funções: derivação de dados e interpretação
Com esta abordagem, você pode facilmente distinguir quem detém o “original” AuthorId
de um concreto BlogStory selecionando a opção “primeira” versão de um determinado BlogStoryId
a partir da BlogStoryVersion
tabela, em virtude da aplicação da função MIN () para BlogStoryVersion.CreatedDateTime
.
Dessa forma, cada BlogStoryVersion.AuthorId
valor contido em todas as linhas "posteriores" ou "subsequentes" das versões indica, naturalmente, o identificador de autor da respectiva versão em questão, mas também se pode dizer que esse valor está, ao mesmo tempo, denotando o papel desempenhado pelo usuário envolvido como editor da versão "original" de uma BlogStory .
Sim, um determinado AuthorId
valor pode ser compartilhado por várias BlogStoryVersion
linhas, mas essa é realmente uma informação que diz algo muito significativo sobre cada Versão , portanto, a repetição do referido dado não é um problema.
O formato das colunas DATETIME
Quanto ao tipo de dados DATETIME, sim, você está certo: “O MySQL recupera e exibe valores DATETIME no YYYY-MM-DD HH:MM:SS
formato ' ' ' , mas é possível inserir com segurança os dados pertinentes dessa maneira e, quando você precisar executar uma consulta, basta use as funções incorporadas de DATA e HORA para, entre outras coisas, mostrar os valores relativos no formato apropriado para seus usuários. Ou você certamente poderia realizar esse tipo de formatação de dados por meio do código dos programas de aplicativos.
Implicações das BlogStory
operações UPDATE
Toda vez que uma BlogStory
linha sofre um UPDATE, você deve garantir que os valores correspondentes que estavam "presentes" até a modificação ocorreu sejam INSERTed na BlogStoryVersion
tabela. Portanto, sugiro realizar essas operações em uma única TRANSAÇÃO ÁCIDA para garantir que elas sejam tratadas como uma Unidade de Trabalho indivisível. Você também pode empregar TRIGGERS, mas eles tendem a tornar as coisas desarrumadas, por assim dizer.
Introduzindo uma VersionId
ou VersionCode
coluna
Se você optar por (devido às circunstâncias comerciais ou preferência pessoal) incorporar uma BlogStory.VersionId
ou BlogStory.VersionCode
coluna para distinguir as Versões do BlogStory , considere as seguintes possibilidades:
Uma VersionCode
poderia ser necessária para ser único no (i) o conjunto BlogStory
tabela e também em (ii) BlogStoryVersion
.
Portanto, você deve implementar um método cuidadosamente testado e totalmente confiável para gerar e atribuir cada Code
valor.
Talvez, os VersionCode
valores possam ser repetidos em BlogStory
linhas diferentes , mas nunca duplicados junto com o mesmo BlogStoryNumber
. Por exemplo, você poderia ter:
- um BlogStoryNumber
3
- versão83o7c5c
e, simultaneamente,
- um BlogStoryNumber
86
- Versão83o7c5c
e
- um BlogStoryNumber
958
- Versão83o7c5c
.
A possibilidade posterior abre outra alternativa:
Mantendo um VersionNumber
para BlogStories
, para que possa haver:
- BlogStoryNumber
23
- versões1, 2, 3…
;
- BlogStoryNumber
650
- versões1, 2, 3…
;
- BlogStoryNumber
2254
- versões1, 2, 3…
;
- etc.
Mantendo as versões "original" e "subsequente" em uma única tabela
Embora seja possível manter todas as BlogStoryVersions na mesma tabela base individual , sugiro não fazer isso porque você misturaria dois tipos distintos (conceituais) de fatos, que, portanto, têm efeitos colaterais indesejáveis no
- restrições e manipulação de dados (no nível lógico), juntamente com
- o processamento e armazenamento relacionados (na camada física).
Mas, desde que você opte por seguir esse curso de ação, ainda poderá tirar proveito de muitas das idéias detalhadas acima, por exemplo:
- um composto de PK que consiste de uma coluna INT (
BlogStoryNumber
) e uma coluna DATETIME ( CreatedDateTime
);
- o uso das funções do servidor para otimizar os processos pertinentes e
- Funções derivadas do autor e do editor .
Visto que, ao prosseguir com essa abordagem, um BlogStoryNumber
valor será duplicado assim que versões "mais recentes" forem adicionadas, uma opção que você pode avaliar (que é muito parecida com as mencionadas na seção anterior) está estabelecendo uma BlogStory
PK composto pelas colunas BlogStoryNumber
e VersionCode
, dessa maneira, você seria capaz de identificar exclusivamente cada versão de uma BlogStory . E você pode tentar com uma combinação de BlogStoryNumber
e VersionNumber
também.
Cenário semelhante
Você pode encontrar minha resposta para essa pergunta de ajuda, pois também proponho permitir que recursos temporais no banco de dados relacionado lidem com um cenário comparável.
author_id
valor do campo da consulta está se repetindo em cada linha da tabela. Onde e como devo guardá-lo '?