Gestión eficaz de los cambios de datos.

Tengo una mesa llamada Reservas. Esta tabla contiene datos que representan una reserva realizada para un servicio en particular, con muchas variables.

Hace un tiempo me encontré con un problema con mi estructura de datos actual por la cual cualquier cambio en la reserva que afectara los horarios, fechas o precios afectaría otros registros financieros asociados, listas de reservas para fechas, etc.

Mi solución en ese momento era crear una tabla de Modificaciones que rastreara cualquier cambio realizado en una Reserva. Luego, cada vez que se le pidiera al modelo de reserva que devolviera una reserva, agregaría las Modificaciones realizadas (en la callback de afterFind() Cake) y presentaría la versión más actualizada de la reserva, algo como esto (disculpe el dibujo de Paint ):

introduzca la descripción de la imagen aquí

Este método funciona bien cuando le pide al modelo de reserva que devuelva la reserva # 1234. Devuelve la representación más actualizada de la reserva, incluidas todas las modificaciones (superpuestas entre sí), incluida una matriz que contiene todas las modificaciones y los datos de reserva originales como referencia.

Mi problema es que recientemente me di cuenta de que necesito poder consultar este modelo con condiciones personalizadas, y si una de esas condiciones se realizó en una de las modificaciones, el resultado no coincidirá porque el modelo está buscando el original. registro en lugar del registro finalmente presentado. Ejemplo en el que solicito al modelo que devuelva filas donde abc es azul (no gris):

introduzca la descripción de la imagen aquí

En ese ejemplo, el modelo mira directamente los datos originales de las filas donde abc es azul y no devuelve este resultado, porque el valor azul está en una Modificación que se adjunta después de que se encuentran los resultados originales.

Lo que he hecho ahora es poner una consulta en el beforeFind() del modelo de reserva para buscar modificaciones que coincidan con los criterios dados, uniéndose a la reserva para asegurarse de que cualquier otro criterio aún coincida. Cuando devuelve el azul en el ejemplo anterior, almacena ese resultado en una matriz como una propiedad de clase y continúa con la función de find() , pero excluye que no se devuelva el ID de la reserva (porque hemos encontrado una versión más actualizada). verison de ello). Luego los fusionará, los ordenará de nuevo, etc. en el siguiente afterFind() .

Esto funciona, aunque es un poco más largo de lo que esperaba.

Después de todo eso, me he dado cuenta de que en otras partes de esta aplicación, hay modelos que se unen manualmente a la tabla de reservas y buscan reservas. Así que ahora necesito una manera de poder incorporar las modificaciones en todas esas uniones manuales directamente a la tabla en MySQL sin afectar los datos originales y, preferiblemente, sin cambiar demasiado mi código.

Mi opinión fue que necesito eliminar la unión manual y crear una asociación modelo en su lugar. ¿ beforeFind() y afterFind() del modelo de reserva cuando pregunte el modelo del cliente que tiene muchas reservas (para aplicar las modificaciones a cada reserva)?

Mi otra opción era devolver más filas de MySQL de las necesarias eliminando cualquier criterio que pudiera estar contenido en las modificaciones, luego usar PHP para filtrar los resultados según mis criterios de búsqueda. Esta opción me asustó un poco porque el conjunto de resultados tiene el potencial de ser masivo sin ese criterio …


¿Cómo puedo lograr esta estructura de datos? Mis requisitos clave siguen siendo que no quiero cambiar el registro de reserva original, sino agregar registros de modificación en la parte superior, pero necesito poder consultar las reservas (incluidas las modificaciones) a través del modelo.

Quiero intentar mantener la mayor parte de esta integración entre bambalinas lo más posible para no tener que revisar toda la aplicación para cambiar n número de consultas que se parecen a esto:

 $get_blue = $this->Booking->find('all', array( 'conditions' => array( 'Booking.abc' => 'blue' ) )); 

Quiero poder incluir implícitamente cualquier modificación hecha a las reservas para que la reserva actualizada se devuelva en la consulta anterior.

El otro problema es cuando el modelo de reserva se une manualmente a una consulta de búsqueda, como este:

 $get_transactions_on_blue_bookings = $this->Transaction->find('all', array( 'joins' => array( array( 'table' => 'sql_bookings_table', // non-standard Cake format, I know - it's an example 'alias' => 'Booking', 'type' => 'LEFT', 'conditions' => 'Booking.booking_id = Transaction.booking_id' ) ), 'conditions' => array( 'Booking.abc' => 'blue' ) )); 

Como puede ver, la consulta anterior no incluirá la modificación en mi ejemplo de MSPaint anterior, porque se está uniendo manualmente a la tabla en SQL (la integración de la modificación se encuentra en las funciones de callback before y después de la afterFind() del modelo de reserva).

Cualquier ayuda en esto sería apreciada grandemente

Editar

Sé que esto ya es lo suficientemente largo, pero pensé que agregaría que la razón por la que quiero hacer un seguimiento de estos cambios y no actualizar el registro original es que el aspecto financiero no puede cambiar, ya que afectará a los informes.

La solución más rápida y sencilla que puedo ver hasta ahora es aplicar modificaciones directamente a la reserva original en todos los casos, excepto cuando afecta a la información financiera, que aún se registra como una modificación (porque actualmente no necesito buscar en base a esta información). ).

Parece que estás intentando implementar una base de datos temporal . El soporte temporal fue una de las principales adiciones al estándar ANSI / ISO SQL: 2011. MySQL (como la mayoría de RDBMS) se queda atrás del estándar. Piense en la base de datos temporal como el equivalente a DBMS de CVS / SVN / Git.

Por el contrario, la base de datos tradicional que utilizamos sin características temporales puede denominarse Base de datos actual .

En una base de datos actual , si intenta implementar soporte temporal, puede fallar de muchas maneras con diferentes enfoques:

  • El enfoque de una sola mesa. Cuando necesite realizar modificaciones, haga UPDATEs en sus registros originales y, a menos que tenga algún tipo de lógica de activación / auditoría de origen, el historial no estará presente. Incluso si tiene un registro de auditoría / cambio, tendría que hacer algunas excavaciones feas para reconstruir el historial de cambios.

  • El enfoque de dos mesas. En lugar de realizar modificaciones en el lugar, divide sus datos en dos tablas, una con los registros básicos / originales (por ejemplo, la reserva), y otra tabla para sus cambios / modificaciones / deltas. Entonces, al menos, conservará sus datos originales, pero nuevamente tendrá que escribir lógica compleja para ver los datos originales con modificaciones en capas. Se pone aún peor si solo desea aplicar algunas de las modificaciones.

  • El enfoque de la tabla resultante precalculada . Mantiene 3 o más tablas: los registros base, las modificaciones, y también una tabla que intenta tener siempre el resultado (mantiene las modificaciones + base actualizadas). Buena suerte al escribir los desencadenantes y los procedimientos para realizar este cálculo cada vez que haga INSERTs , y Heaven lo ayudará si se necesita una UPDATE o DELETE . La configuración es frágil y podría desincronizarse, como puntos muertos y retrotracción. Si no hace esto dentro de la base de datos con desencadenantes / procedimientos, podría intentar implementar el cálculo resultante en el código de la aplicación, pero tenga buena suerte en eso, y podría ponerse feo con los consumidores de múltiples subprocesos. Y aún así, no tiene fácil acceso a los resultados con solo algunas modificaciones aplicadas.

Conclusión: si no está limitado a MySQL, realmente debería considerar el uso de una base de datos que tenga soporte temporal incorporado. De lo contrario, vas a volver a implementar la rueda.

En lugar de aplicar las modificaciones al registro original, ¿qué pasaría si hiciera lo contrario y aplicara el registro original a las modificaciones? Puede modificar la tabla de modificaciones (o una nueva tabla) para mantener el registro original con las modificaciones aplicadas y dirigir sus búsquedas allí.

Otra idea es que si los datos financieros son todo lo que necesita preservarse, ¿por qué no guardarlos en otro campo o tabla y hacer referencia a ellos cuando los necesite? Estoy de acuerdo en que un rediseño es probablemente el enfoque mejor / más inteligente para una solución a largo plazo, pero pensé que pondría mis ideas sobre la mesa en caso de que puedan ayudar.

¿Qué sucede si usó una tabla de respaldo para almacenar datos de la tabla original antes de modificar la tabla original? luego podría usar una función de restauración para restaurar los datos a un estado anterior.

Aquí hay un diagtwig de flujo del proceso de actualización de mi base de datos: http://sofes.miximages.com/php/BookingFlowchartinsert_zps5c2d55f8.png

Aquí hay un diagtwig de flujo de mi proceso de selección: http://sofes.miximages.com/php/BookingFlowchartselect_zps702fa902.png

Espero que esto ayude, solo otra forma de verlo.

PS Para mantener la información financiera sin cambios, puede escribir sus funciones de actualización para contar el número de columnas que se actualizarán (según su matriz de actualización de nombres de columnas) y proporcionar variables para mantener valores específicos solo para esas columnas. puede hacer referencia a los índices de matriz ($ array [‘index’]) en la statement SQL para hacerlo dynamic.

Me parece que lo que necesitas es un tipo de historial de una tabla para que puedas saber qué sucede en el tiempo.

Por lo general, logro este enfoque creando una tabla paralela llamada como la _history original que se le _history . Bookings_history en tu caso. La estructura sería similar a la original pero anteponiendo las columnas:

a) timestamp , que se guarda cuando ocurrió la modificación

b) id , para identificar la fila en la tabla original

Se crearía un índice único en estas dos columnas.

Cada vez que se produce una modificación, antes de aplicar la copia, se copia la fila original en la tabla de historial. Luego se aplica la modificación en la tabla original. Al hacerlo, la tabla de historial actúa como una stack en la que se guardan instantáneas de los datos originales.

Me gusta especialmente este modelo porque unir tablas y aplicar motores de búsqueda en la tabla de historial se puede hacer de una manera similar a como lo hizo con la tabla original, porque la estructura es bastante similar. Además, si desea conocer las modificaciones, solo necesita comparar las filas de la tabla de historial.

Espero que esto ayude.

A partir de las respuestas que ya obtuvo, es evidente que haga lo que haga, requerirá un rediseño o más.

Una de las soluciones que aún no veo y que he usado en el pasado para resolver este tipo de problemas (es decir, los pedidos que se modifican) es mantener todo en la misma tabla y usar los campos para diferenciarlos.

Puede cambiar la tabla de bookings para agregar un entero incrementado por reserva (es decir, número de version_number ) y un campo is_latest . De esta manera, puede consultar con is_latest=true para obtener el registro actual y su número de version_number . Si es 0, no hubo cambios, si es> 0, entonces hay cambios (ese número será igual al número de cambios). Podrá “rebobinar” o “reproducir” el historial si pasa de la última versión a 0 o al revés y cada vez tendrá un registro completo que su aplicación entiende sin modificaciones.

Si is_latest está indexada, la velocidad de consulta será (casi) igual a la velocidad de consulta de la tabla original y, por supuesto, puede agregar más booleanos como is_original si necesita obtener la reserva original muchas veces.

Esto tiene la ventaja de que probablemente requerirá que cambie solo el modelo de Booking , pero eso dependerá de su código.

EDITAR: Creo que este enfoque será más compatible con su requisito sobre informes y registros financieros, ya que siempre tendrá el registro original (versión 0) fácilmente disponible.