Estou usando o SQL Server 2008 Standard, que não possui um SEQUENCErecurso.
Um sistema externo lê dados de várias tabelas dedicadas do banco de dados principal. O sistema externo mantém uma cópia dos dados e verifica periodicamente as alterações nos dados e atualiza sua cópia.
Para tornar a sincronização eficiente, desejo transferir apenas linhas que foram atualizadas ou inseridas desde a sincronização anterior. (As linhas nunca são excluídas). Para saber quais linhas foram atualizadas ou inseridas desde a última sincronização, há uma bigintcoluna RowUpdateCounterem cada tabela.
A ideia é que sempre que uma linha for inserida ou atualizada, o número em sua RowUpdateCountercoluna mudará. Os valores que entram na RowUpdateCountercoluna devem ser obtidos de uma sequência cada vez maior de números. Os valores na RowUpdateCountercoluna devem ser exclusivos e cada novo valor armazenado em uma tabela deve ser maior que qualquer valor anterior.
Por favor, veja os scripts que mostram o comportamento desejado.
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
INSERIR algumas linhas
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 |
+----+-------+------------------+
Os valores gerados em RowUpdateCounterpodem ser diferentes, digamos 5, 3, 7, 9,. Eles devem ser únicos e maiores que 0, desde que começamos da tabela vazia.
INSERT e UPDATE algumas linhas
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 |
+----+-------+------------------+
RowUpdateCounterpara linhas com ID1,2deve permanecer como está, porque essas linhas não foram alteradas.RowUpdateCounterpara linhas com ID3,4deve mudar, porque foram atualizadas.RowUpdateCounterpara linhas com ID5,6deve mudar, porque foram inseridas.RowUpdateCounterpara todas as linhas alteradas deve ser maior que 4 (a últimaRowUpdateCounterda sequência).
A ordem na qual novos valores ( 5,6,7,8) são atribuídos a linhas alteradas não importa realmente. Os novos valores podem ter lacunas, por exemplo 15,26,47,58, mas nunca devem diminuir.
Existem várias tabelas com esses contadores no banco de dados. Não importa se todos eles usam a única seqüência global para seus números ou se cada tabela possui sua própria sequência individual.
Não quero usar uma coluna com um carimbo de data e hora em vez de um contador inteiro, porque:
O relógio no servidor pode pular para frente e para trás. Especialmente quando está em uma máquina virtual.
Os valores retornados pelas funções do sistema
SYSDATETIMEsão iguais para todas as linhas afetadas. O processo de sincronização deve poder ler as alterações nos lotes. Por exemplo, se o tamanho do lote for de 3 linhas, após aMERGEetapa acima, o processo de sincronização lerá apenas as linhasE,F,G. Quando o processo de sincronização for executado na próxima vez, continuará da linhaH.
O jeito que estou fazendo isso agora é feio.
Como não existe SEQUENCEno SQL Server 2008, eu emulo o SEQUENCEpor uma tabela dedicada, IDENTITYconforme mostrado nesta resposta . Isso por si só é bastante feio e exacerbado pelo fato de eu precisar gerar não apenas um, mas um lote de números de uma só vez.
Então, eu tenho um INSTEAD OF UPDATE, INSERTgatilho em cada tabela com RowUpdateCounteroe gerar conjuntos necessários de números lá.
Nas consultas e INSERT, defino como 0, que é substituído pelos valores corretos no gatilho. As consultas acima são .UPDATEMERGERowUpdateCounter???0
Funciona, mas existe uma solução mais fácil?
rowversionnão iria me dar essa possibilidade, se eu entendi corretamente o que é ... É garantida a ser cada vez maior?
rowversion. Parece muito tentador. Minha única preocupação é que todos os exemplos de uso que eu vi até agora giram em torno de detectar se uma única linha foi alterada. Preciso de uma maneira eficiente de saber que conjunto de linhas mudou desde um determinado momento. Além disso, é possível perder uma atualização?
Aatualiza uma linha, sua versão de linha é alterada para 123, Aainda não foi confirmada. time = 2: a transação Batualiza outra linha, sua versão de linha muda para 124. time = 3: Bconfirma. time = 4: o processo de sincronização é executado e busca todas as linhas com versão> 122, o que significa que as linhas são atualizadas apenas por B. time = 5: Aconfirma. Resultado: as alterações de Anunca serão captadas pelo processo de sincronização. Estou errado? Talvez algum uso inteligente de MIN_ACTIVE_ROWVERSIONajude?