Implementación de sistema de versiones con MySQL


15

Sé que esto se ha preguntado aquí y aquí , pero tengo la misma idea con una implementación posible diferente y necesito ayuda.

Inicialmente tenía mi blogstoriesmesa con esta estructura:

| 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()             |

Después de que decidí que quería implementar un sistema de versiones para cada historia en el blog, lo primero que se me ocurrió fue crear una tabla diferente para guardar las ediciones ; después de eso, pensé que podría modificar la tabla existente para contener versiones en lugar de ediciones . Esta es la estructura que me vino a la 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                                  |

Las razones por las que vine aquí:

  • El uidcampo de la tabla inicial era ÚNICO en la tabla. Ahora, el story_idya no es único. ¿Cómo debería lidiar con eso? (Pensé que podría abordar story_id = xy luego encontrar la última versión, pero eso parece consumir muchos recursos, así que por favor dame tu consejo)
  • author_idEl valor del campo se repite en cada una de las filas de la tabla. ¿Dónde y cómo debo guardarlo?

Editar

El proceso único de generación de códigos está en la CreateUniqueCodefunción:

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;
  }
}

El código está escrito en Hack , y originalmente fue escrito en PHP por @Scott en su respuesta .

Los campos author_idy editor_id pueden ser diferentes, porque hay usuarios con permisos suficientes para editar las historias de cualquier persona.

Respuestas:


23

Analizando el escenario, que presenta características asociadas con el tema conocido como bases de datos temporales , desde una perspectiva conceptual, se puede determinar que: (a) una versión de historia de blog “presente” y (b) una versión de historia de blog “pasada” , aunque muy parecido, son entidades de diferentes tipos.

Además de eso, cuando se trabaja en el nivel lógico de abstracción, los hechos (representados por filas) de distintos tipos deben conservarse en tablas distintas. En el caso bajo consideración, incluso cuando es bastante similar, (i) los hechos sobre las versiones "presentes" son diferentes de (ii) los hechos sobre las versiones "pasadas" .

Por eso recomiendo gestionar la situación mediante dos tablas:

  • uno dedicado exclusivamente para las versiones "actual" o "presente" de las historias del blog , y

  • uno que está separado, pero también vinculado con el otro, para todas las versiones "anteriores" o "pasadas" ;

cada uno con (1) un número ligeramente distinto de columnas y (2) un grupo diferente de restricciones.

Volviendo a la capa conceptual, considero que, en su entorno empresarial, Autor y Editor son nociones que se pueden delinear como Roles que puede desempeñar un Usuario , y estos aspectos importantes dependen de la derivación de datos (a través de operaciones de manipulación de nivel lógico) e interpretación (llevada a cabo por los lectores y escritores de Blog Stories , en el nivel externo del sistema de información computarizado, con la asistencia de uno o más programas de aplicación).

Detallaré todos estos factores y otros puntos relevantes de la siguiente manera.

Reglas del negocio

Según tengo entendido de sus requisitos, las siguientes formulaciones de reglas de negocio (juntas en términos de los tipos de entidad relevantes y sus tipos de interrelaciones) son especialmente útiles para establecer el esquema conceptual correspondiente :

  • Un usuario escribe cero-uno o muchos BlogStories
  • Un BlogStory contiene cero-uno-o-muchos BlogStoryVersions
  • Un usuario escribió cero-uno o muchos BlogStoryVersions

Diagrama IDEF1X expositivo

En consecuencia, para exponer mi sugerencia en virtud de un dispositivo gráfico, he creado un IDEF1X de muestra, un diagrama que se deriva de las reglas comerciales formuladas anteriormente y otras características que parecen pertinentes. Se muestra en la Figura 1 :

Figura 1 - Diagrama de IDEF1X de las versiones de la historia del blog

¿Por qué BlogStory y BlogStoryVersion se conceptualizan como dos tipos de entidades diferentes?

Porque:

  • Una instancia de BlogStoryVersion (es decir, una "pasada") siempre tiene un valor para una propiedad UpdatedDateTime , mientras que una ocurrencia de BlogStory (es decir, una "presente") nunca lo tiene.

  • Además, las entidades de esos tipos se identifican de forma exclusiva por los valores de dos conjuntos distintos de propiedades: BlogStoryNumber (en el caso de las ocurrencias de BlogStory ) y BlogStoryNumber más CreatedDateTime (en el caso de las instancias de BlogStoryVersion ).


Una definición de integración para el modelado de información ( IDEF1X ) es una técnica de modelado de datos altamente recomendable que fue establecida como estándar en diciembre de 1993 por el Instituto Nacional de Estándares y Tecnología de los Estados Unidos(NIST). Se basa en el material teórico temprano escrito por el único autor del modelo relacional , es decir, el Dr. EF Codd ; en lavista de datos Entidad-Relación , desarrollada por el Dr. PP Chen ; y también en la técnica de diseño de bases de datos lógicas, creada por Robert G. Brown.


Diseño ilustrativo de SQL-DDL lógico

Luego, con base en el análisis conceptual presentado anteriormente, declaró el diseño de nivel lógico a continuación:

-- 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.
);

Probado en este SQL Fiddle que se ejecuta en MySQL 5.6.

La BlogStorymesa

Como puede ver en el diseño de demostración, he definido la BlogStorycolumna PRIMARY KEY (PK por brevedad) con el tipo de datos INT. En este sentido, es posible que desee corregir un proceso automático incorporado que genera y asigna un valor numérico para dicha columna en cada inserción de fila. Si no le importa dejar huecos ocasionalmente en este conjunto de valores, puede emplear el atributo AUTO_INCREMENT , comúnmente utilizado en entornos MySQL.

Al ingresar todos sus BlogStory.CreatedDateTimepuntos de datos individuales , puede utilizar la función NOW () , que devuelve los valores de fecha y hora actuales en el servidor de la base de datos en el instante exacto de la operación INSERT. Para mí, esta práctica es decididamente más adecuada y menos propensa a errores que el uso de rutinas externas.

Siempre que, como se discute en los comentarios (ahora eliminados), desea evitar la posibilidad de mantener BlogStory.Titlevalores duplicados, debe configurar una restricción ÚNICA para esta columna. Debido al hecho de que un Título dado puede ser compartido por varias (o incluso todas) las Versiones BlogStory "pasadas" , entonces no se debe establecer una restricción ÚNICA para la BlogStoryVersion.Titlecolumna.

Incluí la BlogStory.IsActivecolumna de tipo BIT (1) (aunque también se puede usar un TINYINT ) en caso de que necesite proporcionar la funcionalidad DELETE "suave" o "lógica".

Detalles sobre la BlogStoryVersionmesa

Por otro lado, el PK de la BlogStoryVersiontabla se compone de (a) BlogStoryNumbery (b) una columna llamada CreatedDateTimeque, por supuesto, marca el instante preciso en el que una BlogStoryfila se sometió a una INSERT.

BlogStoryVersion.BlogStoryNumber, además de ser parte de la PK, también está restringido como una CLAVE EXTRANJERA (FK) que hace referencia BlogStory.BlogStoryNumber, una configuración que impone integridad referencial entre las filas de estas dos tablas. A este respecto, la implementación de una generación automática de a BlogStoryVersion.BlogStoryNumberno es necesaria porque, al establecerse como FK, los valores INSERTADOS en esta columna deben "extraerse" de los ya incluidos en la BlogStory.BlogStoryNumbercontraparte relacionada .

La BlogStoryVersion.UpdatedDateTimecolumna debe retener, como se esperaba, el momento en el que BlogStoryse modificó una fila y, como consecuencia, se agregó a la BlogStoryVersiontabla. Por lo tanto, también puede usar la función NOW () en esta situación.

El intervalo comprendido entre BlogStoryVersion.CreatedDateTimey BlogStoryVersion.UpdatedDateTimeexpresa todo el período durante el cual una BlogStoryfila estuvo "presente" o "actual".

Consideraciones para una Versioncolumna

Puede ser útil pensar BlogStoryVersion.CreatedDateTimeen la columna que contiene el valor que representa una versión "pasada" particular de un BlogStory . Considero que esto es mucho más beneficioso que un VersionIdo VersionCode, ya que es más fácil de usar en el sentido de que las personas tienden a estar más familiarizadas con los conceptos de tiempo . Por ejemplo, los autores o lectores del blog pueden referirse a una BlogStoryVersion de manera similar a la siguiente:

  • “Quiero ver lo específico Versión del BlogStory identificado por número 1750 que fue creada en 26 August 2015a 9:30”.

El autor y el editor Roles: la derivación de datos e interpretación

Con este enfoque, se puede distinguir fácilmente que ostenta el “original” AuthorIdde un hormigón BlogStory seleccionando la opción “más antiguo” versión de un determinado BlogStoryIdDESDE la BlogStoryVersionmesa, en virtud de la aplicación de la función MIN () a BlogStoryVersion.CreatedDateTime.

De esta manera, cada BlogStoryVersion.AuthorIdvalor contenido en todas las filas de Versiones "posteriores" o "posteriores" indica, naturalmente, el identificador de Autor de la respectiva Versión en cuestión, pero también se puede decir que dicho valor es, al mismo tiempo, denotar el papel desempeñado por el usuario involucrado como editor de la versión "original" de un BlogStory .

Sí, un AuthorIdvalor dado puede ser compartido por varias BlogStoryVersionfilas, pero esta es en realidad una información que dice algo muy significativo sobre cada versión , por lo que la repetición de dicho dato no es un problema.

El formato de las columnas DATETIME

En cuanto al tipo de datos DATETIME, sí, tiene razón, “ MySQL recupera y muestra los valores DATETIME en YYYY-MM-DD HH:MM:SSformato ' ' ”, pero puede ingresar con confianza los datos pertinentes de esta manera, y cuando tiene que realizar una consulta solo tiene que utilice las funciones DATE y TIME integradas para, entre otras cosas, mostrar los valores correspondientes en el formato apropiado para sus usuarios. O bien, podría llevar a cabo este tipo de formateo de datos a través del código de sus programas de aplicación.

Implicaciones de las BlogStoryoperaciones de ACTUALIZACIÓN

Cada vez que una BlogStoryfila sufre una ACTUALIZACIÓN, debe asegurarse de que los valores correspondientes que estaban "presentes" hasta que se realizó la modificación se INSERTEN en la BlogStoryVersiontabla. Por lo tanto, sugiero realizar estas operaciones dentro de una sola TRANSACCIÓN ÁCIDA para garantizar que sean tratadas como una Unidad de Trabajo indivisible. También puede emplear DISPARADORES, pero tienden a poner las cosas desordenadas, por así decirlo.

Introduciendo una VersionIdo VersionCodecolumna

Si opta (debido a circunstancias comerciales o preferencias personales) para incorporar una BlogStory.VersionIdo BlogStory.VersionCodecolumna para distinguir las versiones de BlogStory , debe considerar las siguientes posibilidades:

  1. Se VersionCodepodría requerir que A sea ÚNICO en (i) toda la BlogStorytabla y también en (ii) BlogStoryVersion.

    Por lo tanto, debe implementar un método cuidadosamente probado y totalmente confiable para generar y asignar cada Codevalor.

  2. Tal vez, los VersionCodevalores podrían repetirse en diferentes BlogStoryfilas, pero nunca duplicarse junto con el mismo BlogStoryNumber. Por ejemplo, podrías tener:

    • un BlogStoryNumber 3- Versión83o7c5c y, simultáneamente,
    • un BlogStoryNumber 86- Versión83o7c5c y
    • un BlogStoryNumber 958- Versión83o7c5c .

La posibilidad posterior abre otra alternativa:

  1. Manteniendo un VersionNumberpara el BlogStories, por lo que podría haber:

    • BlogStoryNumber 23- Versiones1, 2, 3… ;
    • BlogStoryNumber 650- Versiones1, 2, 3… ;
    • BlogStoryNumber 2254- Versiones1, 2, 3… ;
    • etc.

Mantener versiones "originales" y "posteriores" en una sola tabla

Aunque es posible mantener todas las versiones de BlogStory en la misma tabla base individual , sugiero no hacerlo porque estaría mezclando dos tipos distintos (conceptuales) de hechos, lo que tiene efectos secundarios indeseables en

  • restricciones y manipulación de datos (a nivel lógico), junto con
  • el procesamiento y almacenamiento relacionados (en el nivel físico).

Pero, a condición de que elija seguir ese curso de acción, aún puede aprovechar muchas de las ideas detalladas anteriormente, por ejemplo:

  • un compuesto PK que consiste en una columna INT ( BlogStoryNumber) y una columna DATETIME ( CreatedDateTime);
  • el uso de las funciones del servidor para optimizar los procesos pertinentes, y
  • el autor y el editor Roles derivables .

Al ver que, al proceder con este enfoque, BlogStoryNumberse duplicará un valor tan pronto como se agreguen las versiones "más nuevas" , una opción que y que podría evaluar (que es muy similar a las mencionadas en la sección anterior) está estableciendo un BlogStoryPK compuesto por las columnas BlogStoryNumbery VersionCode, de esta manera, podrá identificar de forma única cada versión de un BlogStory . Y puedes probar con una combinación de BlogStoryNumbery VersionNumbertambién.

Escenario similar

Puede encontrar mi respuesta a esta pregunta de ayuda, ya que yo también propongo habilitar capacidades temporales en la base de datos correspondiente para hacer frente a un escenario comparable.


2

Una opción es usar el formulario normal de versión (vnf). Las ventajas incluyen:

  • Los datos actuales y todos los datos pasados ​​residen en la misma tabla.
  • La misma consulta se utiliza para recuperar datos actuales o datos que estaban actualizados en una fecha en particular.
  • Las referencias de clave externa a datos versionados funcionan de la misma manera que los datos no versionados.

Un beneficio adicional en su caso, ya que los datos versionados se identifican de manera única al hacer que la fecha efectiva (la fecha en que se realizó el cambio) sea parte de la clave, no se requiere un campo version_id separado.

Aquí hay una explicación para un tipo de entidad muy similar.

Puede encontrar más detalles en una presentación de diapositivas aquí y en un documento no completamente completado aquí


1

Su relacion

(story_id, version_id, editor_id, author_id, timestamp, title, content, coverimg)

no está en 3ra forma normal. Para cada versión de su historia, el author_id es el mismo. Entonces necesitas dos relaciones para superar esto

(story_id, author_id)
(story_id, version_id, editor_id, timestamp, title, content, coverimg)

La clave de la primera relación es story_id, la clave de la segunda relación es la clave combinada (story_id, version_id). Si no le gusta la clave combinada, puede usarla solo version_idcomo clave


2
Esto no parece resolver mi problema, solo los enfatiza
Victor

Por lo tanto, ni siquiera responde, el author_id valor del campo de la consulta se repite en cada una de las filas de la tabla. ¿Dónde y cómo debo guardarlo ?
milagro173

2
Realmente no entiendo lo que dice tu respuesta. Puede ser porque no soy un hablante nativo de inglés, ¿podría intentar explicarlo en palabras más simples y simples, por favor?
Victor

Significa que debe evitar la repetición del número author_id (si el story_id es igual para dos filas, su author_id también es igual) y dividir su tabla en las dos tablas como se describe en mi publicación. Para que pueda evitar la repetición de author_id.
milagro173
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.