Pregunta

Estoy buscando implementar la versión de objetos con el giro adicional de la necesidad de tener objetos en vivo y borradores, y podría usar las ideas de alguien con experiencia en esto, ya que estoy empezando a preguntarme si es posible sin hacks potencialmente horribles.

Lo desglosaré en publicaciones con etiquetas en aras del ejemplo, pero mi caso de uso es un poco más general (que implica dimensiones que cambian lentamente, http://en.wikipedia.org/wiki/slowly_changing_dimension).

Supongamos que tienes una tabla de publicaciones, una tabla de etiquetas y una tabla posterior a la2tag:

posts (
 id
)

tags (
 id
)

post2tag (
 post_id fkey posts(id),
 tag_id fkey tags(id)
)

Necesito un par de cosas:

  1. Ser capaz de mostrar exactamente cómo se veía una publicación en una hora de fecha arbitraria, incluso para filas eliminadas.
  2. Mantenga un seguimiento de quién está editando qué, para una pista de auditoría completa.
  3. Necesita un conjunto de vistas materializadas (tablas "en vivo") en aras de mantener la integridad referencial (es decir, el registro debe ser transparente para los desarrolladores).
  4. Necesita ser apropiadamente rápido para vivir y las últimas filas de draft.
  5. Poder tener un borrador posterior a Coexist con una publicación en vivo.

He estado investigando varias opciones. Hasta ahora, lo mejor que se me ocurrió (sin puntos #4/ #5) se parece un poco a la configuración SCD Type6-Hybrid, pero en lugar de tener un booleano actual hay una vista materializada para la fila actual. Para todos los efectos, se ve así:

posts (
 id pkey,
 public,
 created_at,
 updated_at,
 updated_by
)

post_revs (
 id,
 rev pkey,
 public,
 created_at,
 created_by,
 deleted_at
)

tags (
 id pkey,
 public,
 created_at,
 updated_at,
 updated_by
)


tag_revs (
 id,
 public,
 rev pkey,
 created_at,
 created_by,
 deleted_at
)

post2tag (
 post_id fkey posts(id),
 tag_id fkey tags(id),
 public,
 created_at,
 updated_at,
 updated_by
)

post2tag_revs (
 post_id,
 tag_id,
 post_rev fkey post_revs(rev), -- the rev when the relation started
 tag_rev fkey tag_revs(rev), -- the rev when the relation started
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (post_rev, tag_rev)
)

Estoy usando PG_Temporal para mantener índices en el período (creado_at, deleted_at). Y mantengo las diversas tablas sincronizadas usando desencadenantes. Yada yada yada ... Creé los desencadenantes que permiten cancelar una edición a publicaciones/etiquetas de tal manera que el borrador se almacene en las revoluciones sin ser publicados. Funciona muy bien.

Excepto Cuando necesito preocuparme por las relaciones relacionadas con la fila de draft en Post2Tag. En ese caso, todo el infierno se desata, y esto me insinúa que tengo algún tipo de problema de diseño allí. Pero me estoy quedando sin ideas ...

He considerado la introducción de la duplicación de datos (es decir, las filas posteriores al2TAG introducidas para cada borrador de revisión). Este tipo de funciona, pero tiende a ser mucho más lento de lo que me gustaría que fuera.

He considerado presentar las tablas de borradores para el "último borrador", pero esto rápidamente tiende a volverse muy feo.

He considerado todo tipo de banderas ...

Entonces, Pregunta: ¿Existe un medio generalmente aceptado para administrar filas vivos frente a las no vivos en un entorno controlado por la versión de fila? Y si no, ¿con qué has intentado y con el que has tenido un éxito razonable?

¿Fue útil?

Solución 4

Creo que lo clavé. Básicamente, agrega un campo de borrador (único) a las tablas relevantes, y trabaja en los borradores como si fueran una nueva publicación/etiqueta/etc.:

posts (
 id pkey,
 public,
 created_at stamptz,
 updated_at stamptz,
 updated_by int,
 draft int fkey posts (id) unique
)

post_revs (
 id,
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (id, created_at)
)

tags (
 id pkey,
 public,
 created_at,
 updated_at,
 updated_by,
 draft fkey tags (id) unique
)


tag_revs (
 id,
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (id, created_at)
)

post2tag (
 post_id fkey posts(id),
 tag_id fkey tags(id),
 public,
 created_at,
 updated_at,
 updated_by,
 pkey (post_id, tag_id)
)

post2tag_revs (
 post_id,
 tag_id,
 public,
 created_at,
 created_by,
 deleted_at,
 pkey (post_id, tag_id, created_at)
)

Otros consejos

Modelado de anclaje es una buena manera de implementar una base de datos temporal: vea el Artículo de Wikipedia también. Se tarda un tiempo en acostumbrarse, pero funciona bien. Hay un Herramienta de modelado en línea y si carga el archivo XML suministrado [File -> Load Model from Local File]Deberías ver algo como esto, también usa [Layout --> Togle Names].

enter image description here

los [Generate --> SQL Code] producirá DDL para tablas, vistas y funciones de punto en el tiempo. El código es bastante largo, por lo que no lo estoy publicando aquí. Verifique el código: es posible que deba modificarlo para su DB.

Aquí está el archivo para cargar en la herramienta de modelado.

<schema>
<knot mnemonic="EXP" descriptor="Expired" identity="smallint" dataRange="char(1)">
<identity generator="true"/>
<layout x="713.96" y="511.22" fixed="true"/>
</knot>
<anchor mnemonic="US" descriptor="User" identity="int">
<identity generator="true"/>
<attribute mnemonic="USN" descriptor="UserName" dataRange="varchar(32)">
<layout x="923.38" y="206.54" fixed="true"/>
</attribute>
<layout x="891.00" y="242.00" fixed="true"/>
</anchor>
<anchor mnemonic="PO" descriptor="Post" identity="int">
<identity generator="true"/>
<attribute mnemonic="TIT" descriptor="Title" dataRange="varchar(2)">
<layout x="828.00" y="562.00" fixed="true"/>
</attribute>
<layout x="855.00" y="471.00" fixed="true"/>
</anchor>
<anchor mnemonic="TG" descriptor="Tag" identity="int">
<identity generator="true"/>
<attribute mnemonic="TGT" descriptor="TagText" dataRange="varchar(32)">
<layout x="551.26" y="331.69" fixed="true"/>
</attribute>
<layout x="637.29" y="263.43" fixed="true"/>
</anchor>
<anchor mnemonic="BO" descriptor="Body" identity="int">
<identity generator="true"/>
<attribute mnemonic="BOT" descriptor="BodyText" dataRange="varchar(max)">
<layout x="1161.00" y="491.00" fixed="true"/>
</attribute>
<layout x="1052.00" y="465.00" fixed="true"/>
</anchor>
<tie timeRange="datetime">
<anchorRole role="IsTagged" type="PO" identifier="true"/>
<anchorRole role="IsAttached" type="TG" identifier="true"/>
<anchorRole role="BYAuthor" type="US" identifier="false"/>
<knotRole role="Until" type="EXP" identifier="false"/>
<layout x="722.00" y="397.00" fixed="true"/>
</tie>
<tie timeRange="datetime">
<anchorRole role="Contains" type="PO" identifier="true"/>
<anchorRole role="ContainedIn" type="BO" identifier="false"/>
<layout x="975.00" y="576.00" fixed="true"/>
</tie>
<tie>
<anchorRole role="CreatedBy" type="TG" identifier="true"/>
<anchorRole role="Author" type="US" identifier="false"/>
<layout x="755.10" y="195.17" fixed="true"/>
</tie>
<tie>
<anchorRole role="CreatedBy" type="PO" identifier="true"/>
<anchorRole role="Author" type="US" identifier="false"/>
<layout x="890.69" y="369.09" fixed="true"/>
</tie>
<tie>
<anchorRole role="ModifiedBy" type="BO" identifier="true"/>
<anchorRole role="Author" type="US" identifier="false"/>
<layout x="1061.81" y="322.34" fixed="true"/>
</tie>
</schema>

He implementado una base de datos temporal utilizando reglas y desencadenantes de PostgreSQL SCD y PostgreSQL, y la envolví en un paquete autónomo para Activerecord: http://github.com/ifad/chronomodel

Sin embargo, el diseño es independiente del lenguaje / marco: puede crear reglas y desencadenantes manualmente y la base de datos se encargará del resto. Mira esto https://github.com/ifad/chronomodel/blob/master/readme.sql.

También la indexación y consulta eficiente de datos temporales utilizando operadores geométricos se incluye como una bonificación. :-)

Post2tag_revs tiene un problema en que está tratando de expresar 2 conceptos fundamentalmente diferentes.

Una etiqueta aplicada a un borrador de revisión posterior a la publicación solo se aplica a esa revisión, a menos que se publique la revisión.

Una vez que se publica una etiqueta (es decir, asociada con una revisión publicada publicada), se aplica a cada revisión futura de la publicación hasta que se revoca.

Y asociarse con una revisión publicada, o no asociación, no es necesariamente simultánea con una revisión que se publica, a menos que lo haga cumplir artificialmente clonando una revisión solo para que pueda asociar adiciones o eliminaciones de etiquetas ...

Cambiaría el modelo haciendo post2tag_revs.post_rev solo relevante para etiquetas de borrador. Una vez que se publique la revisión (y la etiqueta está en vivo), usaría una columna de sello de tiempo para marcar el principio y el final de la validez publicada. Es posible que desee o no una nueva entrada post2tag_revs para representar este cambio.

Como señala, esto hace esta relación bi-temporal. Puede mejorar el rendimiento en el caso "normal" agregando un booleano al post2TAG para indicar que la etiqueta está actualmente asociada con la publicación.

Use solo 3 tablas: publicaciones, etiquetas y post2tag.

Agregue las columnas Start_time y End_Time a todas las tablas. Agregue un índice único para la tecla, inicio_time y end_time. Agregue un índice único para la clave donde end_time es nulo. Agrega trigers.

Para la corriente:

SELECT ... WHERE end_time IS NULL

En el momento:

WHERE (SELECT CASE WHEN end_time IS NULL
THEN (start_time <= at_time)
ELSE (start_time <= at_time AND end_time > at_time)
END)

La búsqueda de datos actuales no es lenta debido al índice funcional.

Editar:

CREATE UNIQUE INDEX ... ON post2tag (post_id, tag_id) WHERE end_time IS NULL;
CREATE UNIQUE INDEX ... ON post2tag (post_id, tag_id, start_time, end_time);

FOREIGN KEY (post_id, start_time, end_time) REFERENCES posts (post_id, start_time, end_time) ON DELETE CASCADE ON UPDATE CASCADE;
FOREIGN KEY (tag_id, start_time, end_time) REFERENCES tags (tag_id, start_time, end_time) ON DELETE CASCADE ON UPDATE CASCADE;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top