Cómo controlar la versión de un registro en una base de datos


176

Digamos que tengo un registro en la base de datos y que tanto el administrador como los usuarios normales pueden hacer actualizaciones.

¿Alguien puede sugerir un buen enfoque / arquitectura sobre cómo controlar la versión de cada cambio en esta tabla para que sea posible revertir un registro a una revisión anterior.

Respuestas:


164

Supongamos que tiene una FOOtabla que los administradores y los usuarios pueden actualizar. La mayoría de las veces puede escribir consultas en la tabla FOO. Días felices.

Entonces, crearía una FOO_HISTORYtabla. Esto tiene todas las columnas de la FOOtabla. La clave primaria es la misma que FOO más una columna RevisionNumber. Hay una clave foránea de FOO_HISTORYa FOO. También puede agregar columnas relacionadas con la revisión, como UserId y RevisionDate. Rellene los números de revisión de forma cada vez mayor en todas las *_HISTORYtablas (es decir, a partir de una secuencia de Oracle o equivalente). No confíe en que solo haya un cambio en un segundo (es decir, no lo coloque RevisionDateen la clave principal).

Ahora, cada vez que actualice FOO, justo antes de realizar la actualización, inserte los valores anteriores enFOO_HISTORY . Haces esto a un nivel fundamental en tu diseño para que los programadores no puedan omitir accidentalmente este paso.

Si desea eliminar una fila FOO, tiene algunas opciones. En cascada y elimine todo el historial, o realice una eliminación lógica marcando FOOcomo eliminado.

Esta solución es buena cuando está interesado en gran medida en los valores actuales y solo ocasionalmente en la historia. Si siempre necesita el historial, puede poner fechas de inicio y finalización efectivas y mantener todos los registros en FOOsí mismo. Cada consulta debe verificar esas fechas.


1
Puede actualizar la tabla de auditoría con activadores de base de datos si su capa de acceso a datos no lo admite directamente. Además, no es difícil construir un generador de código para hacer los disparadores que utilizan la introspección del diccionario de datos del sistema.
Preocupado por

44
Le recomiendo que realmente inserte los datos nuevos , no los anteriores, para que la tabla de historial contenga todos los datos. Aunque almacena datos de redyundent, elimina los casos especiales necesarios para tratar con la búsqueda en ambas tablas cuando se requieren datos históricos.
Nerdfest

66
Personalmente, recomendaría no eliminar nada (diferir esto a una actividad de limpieza específica) y tener una columna de "tipo de acción" para especificar si es insertar / actualizar / eliminar. Para una eliminación, copie la fila normalmente, pero coloque "eliminar" en la columna de tipo de acción.
Neil Barnwell

3
@Hydrargyrum Una tabla que contiene los valores actuales funcionará mejor que una vista de la tabla histórica. También es posible que desee definir claves externas que hagan referencia a los valores actuales.
WW.

2
There is a foreign key from FOO_HISTORY to FOO': mala idea, me gustaría eliminar registros de foo sin cambiar el historial. la tabla de historial debe ser de solo inserción en uso normal.
Jasen

46

Creo que está buscando versionar el contenido de los registros de la base de datos (como lo hace StackOverflow cuando alguien edita una pregunta / respuesta). Un buen punto de partida podría ser mirar algún modelo de base de datos que use el seguimiento de revisiones .

El mejor ejemplo que viene a la mente es MediaWiki, el motor de Wikipedia. Compare el diagrama de la base de datos aquí , particularmente la tabla de revisión .

Dependiendo de las tecnologías que esté utilizando, tendrá que encontrar algunos buenos algoritmos de diferenciación / fusión.

Verifique esta pregunta si es para .NET.


30

En el mundo de BI, puede lograr esto agregando un startDate y endDate a la tabla que desea versionar. Cuando inserta el primer registro en la tabla, startDate se llena, pero endDate es nulo. Cuando inserta el segundo registro, también actualiza endDate del primer registro con startDate del segundo registro.

Cuando desee ver el registro actual, seleccione aquel donde endDate sea nulo.

Esto a veces se denomina dimensión tipo 2 que cambia lentamente . Ver también TupleVersioning


¿Mi mesa no crecerá bastante usando este enfoque?
Niels Bosma

1
Sí, pero puede lidiar con eso indexando y / o particionando la tabla. Además, solo habrá un pequeño puñado de mesas grandes. La mayoría será mucho más pequeña.
Preocupado por

Si no me equivoco, la única desventaja aquí es que limita los cambios a una vez por segundo, ¿correcto?
pimbrouwers

@pimbrouwers sí, en última instancia, depende de la precisión de los campos y la función que los llena.
Dave Neeley

9

Actualice a SQL 2008.

Intente usar el Seguimiento de cambios de SQL, en SQL 2008. En lugar de la marca de tiempo y los hacks de columnas de lápidas, puede usar esta nueva función para realizar un seguimiento de los cambios en los datos de su base de datos.

Seguimiento de cambios de MSDN SQL 2008


7

Solo quería agregar que una buena solución a este problema es usar una base de datos temporal . Muchos proveedores de bases de datos ofrecen esta característica ya sea de forma inmediata o mediante una extensión. He utilizado con éxito la extensión de tabla temporal con PostgreSQL pero otros también la tienen. Cada vez que actualiza un registro en la base de datos, la base de datos también conserva la versión anterior de ese registro.


6

Dos opciones:

  1. Tener una tabla de historial: inserte los datos antiguos en esta tabla de historial siempre que se actualice el original.
  2. Tabla de auditoría: almacene los valores de antes y después, solo para las columnas modificadas en una tabla de auditoría junto con otra información como quién se actualizó y cuándo.

5

Puede realizar auditorías en una tabla SQL mediante activadores SQL. Desde un disparador puede acceder a 2 tablas especiales ( insertadas y eliminadas ). Estas tablas contienen las filas exactas que se insertaron o eliminaron cada vez que se actualiza la tabla. En el SQL de activación, puede tomar estas filas modificadas e insertarlas en la tabla de auditoría. Este enfoque significa que su auditoría es transparente para el programador; No requiere ningún esfuerzo de ellos o cualquier conocimiento de implementación.

La ventaja adicional de este enfoque es que la auditoría se realizará independientemente de si la operación sql se realizó a través de sus DLL de acceso a datos o mediante una consulta SQL manual; (ya que la auditoría se realiza en el propio servidor).


3

No dice qué base de datos, y no lo veo en las etiquetas de publicación. Si es para Oracle, puedo recomendar el enfoque integrado en Designer: usar tablas de diario . Si es para cualquier otra base de datos, bueno, básicamente también recomiendo lo mismo ...

La forma en que funciona, en caso de que quiera replicarlo en otra base de datos, o tal vez si solo quiere entenderlo, es que para una tabla también se crea una tabla sombra, solo una tabla de base de datos normal, con las mismas especificaciones de campo , además de algunos campos adicionales: como qué acción se realizó por última vez (cadena, valores típicos "INS" para insertar, "UPD" para actualizar y "DEL" para eliminar), fecha y hora para cuándo se llevó a cabo la acción e identificación de usuario para quién lo hizo eso.

A través de los disparadores, cada acción en cualquier fila de la tabla inserta una nueva fila en la tabla del diario con los nuevos valores, qué acción se tomó, cuándo y por qué usuario. Nunca elimine ninguna fila (al menos no en los últimos meses). Sí, crecerá grande, fácilmente millones de filas, pero puede rastrear fácilmente el valor de cualquier registro en cualquier momento desde que comenzó el registro en el diario o las últimas filas del diario se purgaron por última vez, y quién realizó el último cambio.

En Oracle, todo lo que necesita se genera automáticamente como código SQL, todo lo que tiene que hacer es compilarlo / ejecutarlo; y viene con una aplicación CRUD básica (en realidad solo "R") para inspeccionarla.


2

También estoy haciendo lo mismo. Estoy haciendo una base de datos para planes de lecciones. Estos planes necesitan flexibilidad de versiones de cambio atómico. En otras palabras, cada cambio, por pequeño que sea, a los planes de la lección debe permitirse, pero la versión anterior también debe mantenerse intacta. De esa forma, los creadores de las lecciones pueden editar los planes de las lecciones mientras los estudiantes los usan.

La forma en que funcionaría es que una vez que un alumno ha completado una lección, sus resultados se adjuntan a la versión que completaron. Si se realiza un cambio, sus resultados siempre apuntarán a su versión.

De esta manera, si se elimina o mueve un criterio de lección, sus resultados no cambiarán.

La forma en que lo estoy haciendo actualmente es manejando todos los datos en una tabla. Normalmente solo tendría un campo de identificación, pero con este sistema, estoy usando una identificación y un sub_id. El sub_id siempre permanece con la fila, a través de actualizaciones y eliminaciones. La identificación se incrementa automáticamente. El software del plan de lección se vinculará con el nuevo sub_id. Los resultados del alumno se vincularán a la identificación. También he incluido una marca de tiempo para rastrear cuándo ocurrieron los cambios, pero no es necesario manejar el control de versiones.

Una cosa que podría cambiar, una vez que lo haya probado, es que podría usar la idea nula endDate mencionada anteriormente. En mi sistema, para encontrar la versión más nueva, tendría que encontrar el max (id). El otro sistema solo busca endDate = null. No estoy seguro si los beneficios tienen otro campo de fecha.

Mis dos centavos.


2

Mientras que @WW. la respuesta es una buena respuesta, otra forma es hacer una columna de versión y mantener todas sus versiones en la misma tabla.

Para un enfoque de mesa, usted:

  • Use una bandera para indicar la última versión de Word Press
  • O hacer una versión desagradable mayor que outer join.

Un ejemplo de SQL del outer joinmétodo que usa números de revisión es:

SELECT tc.*
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- path in this case is our natural id.

La mala noticia es que lo anterior requiere una outer joiny las uniones externas pueden ser lentas. La buena noticia es que crear nuevas entradas es teóricamente más barato porque puede hacerlo en una operación de escritura sin transacciones (suponiendo que su base de datos sea atómica).

Un ejemplo para hacer una nueva revisión '/stuff'podría ser:

INSERT INTO text_content (id, path, data, revision, revision_comment, enabled, create_time, update_time)
(
SELECT
(md5(random()::text)) -- {id}
, tc.path
, 'NEW' -- {data}
, (tc.revision + 1)
, 'UPDATE' -- {comment}
, 't' -- {enabled}
, tc.create_time
, now() 
FROM text_content tc
LEFT OUTER JOIN text_content mc ON tc.path = mc.path
AND mc.revision > tc.revision
WHERE mc.revision is NULL 
AND tc.path = '/stuff' -- {path}
)

Insertamos utilizando los datos antiguos. Esto es particularmente útil si dice que solo desea actualizar una columna y evitar el bloqueo optimista y / o las transacciones.

El enfoque de marca y el enfoque de tabla de historial requieren que se inserten / actualicen dos filas.

La otra ventaja con el outer joinenfoque de número de revisión es que siempre puede refactorizar el enfoque de tabla múltiple más adelante con desencadenadores porque su desencadenador esencialmente debería hacer algo como lo anterior.


2

Alok sugirió Audit tableanteriormente, me gustaría explicarlo en mi publicación.

Adopté este diseño de tabla única sin esquema en mi proyecto.

Esquema:

  • id - INCREMENTO AUTOMÁTICO INTEGER
  • nombre de usuario - STRING
  • nombre de tabla - STRING
  • valor antiguo - TEXTO / JSON
  • nuevovalor - TEXTO / JSON
  • createdon - DATETIME

Esta tabla puede contener registros históricos de cada tabla en un solo lugar, con el historial completo de los objetos en un solo registro. Esta tabla puede rellenarse utilizando activadores / ganchos donde los datos cambian, almacenando una instantánea de valor antigua y nueva de la fila de destino.

Pros con este diseño:

  • Menos cantidad de tablas para administrar para la gestión del historial.
  • Almacena la instantánea completa de cada fila de estado antiguo y nuevo.
  • Fácil de buscar en cada mesa.
  • Puede crear particiones por tabla.
  • Puede definir la política de retención de datos por tabla.

Contras con este diseño:

  • El tamaño de los datos puede ser grande, si el sistema tiene cambios frecuentes.
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.