J'utilise SQL Server 2008 Standard, qui n'a pas de SEQUENCEfonctionnalité.
Un système externe lit les données de plusieurs tables dédiées de la base de données principale. Le système externe conserve une copie des données et vérifie périodiquement les modifications des données et actualise sa copie.
Pour rendre la synchronisation efficace, je souhaite transférer uniquement les lignes qui ont été mises à jour ou insérées depuis la synchronisation précédente. (Les lignes ne sont jamais supprimées). Pour savoir quelles lignes ont été mises à jour ou insérées depuis la dernière synchronisation, il y a une bigintcolonne RowUpdateCounterdans chaque table.
L'idée est qu'à chaque fois qu'une ligne est insérée ou mise à jour, le nombre dans sa RowUpdateCountercolonne change. Les valeurs qui entrent dans la RowUpdateCountercolonne doivent être tirées d'une séquence de nombres toujours croissante. Les valeurs de la RowUpdateCountercolonne doivent être uniques et chaque nouvelle valeur stockée dans une table doit être supérieure à toute valeur précédente.
Veuillez consulter les scripts qui montrent le comportement souhaité.
Schéma
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
INSÉRER quelques lignes
INSERT INTO [dbo].[Test]
([ID]
,[Value]
,[RowUpdateCounter])
VALUES
(1, 'A', ???),
(2, 'B', ???),
(3, 'C', ???),
(4, 'D', ???);
Résultat attendu
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | C | 3 |
| 4 | D | 4 |
+----+-------+------------------+
Les valeurs générées dans RowUpdateCounterpeuvent être différentes, par exemple 5, 3, 7, 9. Ils doivent être uniques et supérieurs à 0, car nous sommes partis d'une table vide.
INSÉRER et METTRE À JOUR certaines lignes
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
,???)
;
Résultat attendu
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | E | 5 |
| 4 | F | 6 |
| 5 | G | 7 |
| 6 | H | 8 |
+----+-------+------------------+
RowUpdateCounterpour les lignes avec ID1,2doivent rester telles quelles, car ces lignes n'ont pas été modifiées.RowUpdateCounterpour les lignes avec ID3,4doit changer, car elles ont été mises à jour.RowUpdateCounterpour les lignes avec ID5,6doit changer, car elles ont été insérées.RowUpdateCounterpour toutes les lignes modifiées doit être supérieur à 4 (le dernierRowUpdateCounterde la séquence).
L'ordre dans lequel les nouvelles valeurs ( 5,6,7,8) sont attribuées aux lignes modifiées n'a pas vraiment d'importance. Les nouvelles valeurs peuvent présenter des lacunes, par exemple 15,26,47,58, mais elles ne devraient jamais diminuer.
Il existe plusieurs tables avec de tels compteurs dans la base de données. Peu importe si tous utilisent la seule séquence globale pour leurs numéros, ou si chaque table a sa propre séquence individuelle.
Je ne veux pas utiliser une colonne avec un tampon datetime au lieu d'un compteur entier, car:
L'horloge du serveur peut sauter en avant et en arrière. Surtout quand il est sur une machine virtuelle.
Les valeurs renvoyées par les fonctions système comme
SYSDATETIMEsont les mêmes pour toutes les lignes affectées. Le processus de synchronisation doit pouvoir lire les modifications des lots. Par exemple, si la taille du lot est de 3 lignes, après l'MERGEétape ci-dessus, le processus de synchronisation ne lira que les lignesE,F,G. Lorsque le processus de synchronisation est exécuté la prochaine fois, il continuera à partir de la ligneH.
La façon dont je le fais maintenant est plutôt moche.
Puisqu'il n'y en a pas SEQUENCEdans SQL Server 2008, j'émule le SEQUENCEpar une table dédiée avec IDENTITYcomme indiqué dans cette réponse . Cela en soi est assez moche et exacerbé par le fait que je dois générer non pas un seul, mais un lot de nombres à la fois.
Ensuite, j'ai un INSTEAD OF UPDATE, INSERTdéclencheur sur chaque table avec RowUpdateCounteret j'y génère les ensembles de nombres requis.
Dans les requêtes INSERT, UPDATEet MERGEj'ai défini RowUpdateCounter0, qui est remplacé par les valeurs correctes dans le déclencheur. Les ???dans les requêtes ci-dessus sont 0.
Cela fonctionne, mais existe-t-il une solution plus simple?
rowversionne me donnerait pas cette possibilité, si je comprends bien ce qu'il est ... Est - il sûr d'être de plus en plus?
rowversion. Ça a l'air très tentant. Ma seule préoccupation est que tous les exemples d'utilisation que j'ai vus jusqu'à présent tournent autour de la détection d'une modification d'une seule ligne. J'ai besoin d'un moyen efficace de savoir quel ensemble de lignes a changé depuis un certain moment. D'ailleurs, est-il possible de manquer une mise à jour?
Amet à jour une ligne, sa version de ligne passe à 123, An'est pas encore validée . time = 2: la transaction Bmet à jour une autre ligne, sa version de ligne passe à 124. time = 3: Bvalide. time = 4: le processus de synchronisation s'exécute et récupère toutes les lignes avec rowversion> 122, ce qui signifie que les lignes sont mises à jour uniquement par B. time = 5: Avalide. Résultat: les modifications apportées par Ane seront jamais détectées par le processus de synchronisation. Ai-je tort? Peut-être qu'une utilisation intelligente de MIN_ACTIVE_ROWVERSIONsera utile?