Estoy usando SQL Server 2008 Standard, que no tiene una SEQUENCE
función.
Un sistema externo lee datos de varias tablas dedicadas de la base de datos principal. El sistema externo mantiene una copia de los datos y verifica periódicamente los cambios en los datos y actualiza su copia.
Para que la sincronización sea eficiente, quiero transferir solo las filas que se actualizaron o insertaron desde la sincronización anterior. (Las filas nunca se eliminan). Para saber qué filas se actualizaron o insertaron desde la última sincronización, hay una bigint
columna RowUpdateCounter
en cada tabla.
La idea es que cada vez que se inserte o actualice una fila, el número en su RowUpdateCounter
columna cambiaría. Los valores que entran en la RowUpdateCounter
columna deben tomarse de una secuencia de números cada vez mayor. Los valores en la RowUpdateCounter
columna deben ser únicos y cada nuevo valor almacenado en una tabla debe ser mayor que cualquier valor anterior.
Consulte los scripts que muestran el comportamiento deseado.
Esquema
CREATE TABLE [dbo].[Test](
[ID] [int] NOT NULL,
[Value] [varchar](50) NOT NULL,
[RowUpdateCounter] [bigint] NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
[ID] ASC
))
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_RowUpdateCounter] ON [dbo].[Test]
(
[RowUpdateCounter] ASC
)
GO
INSERTAR algunas filas
INSERT INTO [dbo].[Test]
([ID]
,[Value]
,[RowUpdateCounter])
VALUES
(1, 'A', ???),
(2, 'B', ???),
(3, 'C', ???),
(4, 'D', ???);
Resultado Esperado
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | C | 3 |
| 4 | D | 4 |
+----+-------+------------------+
Los valores generados en RowUpdateCounter
pueden ser diferentes, por ejemplo, 5, 3, 7, 9
. Deben ser únicos y deben ser mayores que 0, ya que comenzamos desde una tabla vacía.
INSERTAR y ACTUALIZAR algunas filas
DECLARE @NewValues TABLE (ID int NOT NULL, Value varchar(50));
INSERT INTO @NewValues (ID, Value) VALUES
(3, 'E'),
(4, 'F'),
(5, 'G'),
(6, 'H');
MERGE INTO dbo.Test WITH (HOLDLOCK) AS Dst
USING
(
SELECT ID, Value
FROM @NewValues
)
AS Src ON Dst.ID = Src.ID
WHEN MATCHED THEN
UPDATE SET
Dst.Value = Src.Value
,Dst.RowUpdateCounter = ???
WHEN NOT MATCHED BY TARGET THEN
INSERT
(ID
,Value
,RowUpdateCounter)
VALUES
(Src.ID
,Src.Value
,???)
;
Resultado Esperado
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | E | 5 |
| 4 | F | 6 |
| 5 | G | 7 |
| 6 | H | 8 |
+----+-------+------------------+
RowUpdateCounter
para las filas con ID1,2
debe permanecer como está, porque estas filas no se modificaron.RowUpdateCounter
para las filas con ID3,4
deberían cambiar, porque se actualizaron.RowUpdateCounter
para las filas con ID5,6
deberían cambiar, porque se insertaron.RowUpdateCounter
para todas las filas modificadas debe ser mayor que 4 (el últimoRowUpdateCounter
de la secuencia).
El orden en que 5,6,7,8
se asignan los nuevos valores ( ) a las filas modificadas realmente no importa. Los nuevos valores pueden tener huecos, por ejemplo 15,26,47,58
, pero nunca deberían disminuir.
Hay varias tablas con dichos contadores en la base de datos. No importa si todos usan la secuencia global única para sus números, o si cada tabla tiene su propia secuencia individual.
No quiero usar una columna con un sello de fecha y hora en lugar de un contador entero, porque:
El reloj en el servidor puede saltar hacia adelante y hacia atrás. Especialmente cuando está en una máquina virtual.
Los valores devueltos por las funciones del sistema
SYSDATETIME
son iguales para todas las filas afectadas. El proceso de sincronización debería poder leer los cambios en lotes. Por ejemplo, si el tamaño del lote es de 3 filas, luego delMERGE
paso anterior, el proceso de sincronización solo leería filasE,F,G
. Cuando se ejecute el proceso de sincronización la próxima vez, continuará desde la filaH
.
La forma en que lo hago ahora es bastante fea.
Como no existe SEQUENCE
en SQL Server 2008, emulo SEQUENCE
una tabla dedicada con la IDENTITY
que se muestra en esta respuesta . Esto en sí mismo es bastante feo y se ve exacerbado por el hecho de que necesito generar no uno solo, sino un lote de números a la vez.
Luego, tengo un INSTEAD OF UPDATE, INSERT
activador en cada tabla con RowUpdateCounter
y genero los conjuntos de números necesarios allí.
En las consultas INSERT
, UPDATE
y MERGE
configuro RowUpdateCounter
a 0, que se reemplaza por los valores correctos en el disparador. El ???
en las consultas anteriores son 0
.
Funciona, pero ¿hay una solución más fácil?
rowversion
no me daría esta posibilidad, si entiendo correctamente lo que es ... ¿Se garantiza que aumentará?
rowversion
. Se ve muy tentador. Mi única preocupación es que todos los ejemplos de su uso que he visto hasta ahora giran en torno a detectar si una sola fila cambió. Necesito una forma eficiente de saber qué conjunto de filas cambió desde un momento determinado. Además, ¿es posible perder una actualización?
A
actualiza una fila, su versión de la fila cambia a 123, A
aún no se ha confirmado. time = 2: la transacción B
actualiza otra fila, su versión de fila cambia a 124. time = 3: B
commits. time = 4: el proceso de sincronización se ejecuta y recupera todas las filas con rowversion> 122, lo que significa que las filas se actualizan solo por B
. tiempo = 5: A
confirmaciones. Resultado: los cambios por A
nunca serán recogidos por el proceso de sincronización. ¿Me equivoco? Tal vez algún uso inteligente de MIN_ACTIVE_ROWVERSION
ayudará?