Tabelas Temporais – Parte 2: Alteração de schema e Indexação

No post anterior, comecei a falar sobre um novo recurso presente no SQL Server 2016: System-Versioned Temporal Tables. Abordei a criação de tabelas temporais seja por alteração de uma tabela existente como por criação de uma nova tabela. Mencionei também que quando uma tabela temporal é criada, uma tabela de histórico (pré-existente ou não) é vinculada a ela. No entanto, deixei de dizer que, se a tabela de histórico já existe no momento desta vinculação, são realizadas verificações de consistência. A primeira é uma verificação de schema. A segunda verificação é realizada nos dados (DATA CONSISTENCY CHECK), mas não é obrigatória. Então, antes de entrar no tópico original desta parte, falarei sobre este assunto.

Vale ressaltar que, no momento em que este post foi escrito, a versão disponível do SQL Server é a 2016 CTP2.2. Logo, alguns detalhes podem mudar com as novas versões lançadas. Farei um esforço para manter este material atualizado de acordo com todas atualizações, mas isso vai depender da minha disponibilidade.

VERIFICAÇÃO DE SCHEMA

Algumas das verificações eu já mencionei no post anterior como a necessidade das colunas DATETIME2 NOT NULL para delimitação da validade do registro e a chave primária na tabela principal. Outras são bem óbvias: o número de colunas e os tipos de dados da tabela principal devem ser iguais aos da tabela de histórico, a tabela de histórico não pode estar num filegroup definido como READ ONLY e não pode existir uma coluna IDENTITY na history table. Além dessas verificações, a history table não pode possuir:

  • Chave primária;
  • Chaves estrangeiras;
  • Triggers;
  • Constraints de tabela ou coluna, exceto default constraints (que nunca serão usadas);
  • Change Tracking ou CDC ativados;

Além disso, se as colunas que definem o período de validade do registro na tabela principal tiverem a opção HIDDEN, na history table elas também devem possuir essa propriedade. Caso a verificação falhe em algum desses requisitos, você receberá uma mensagem de erro. Como qualquer curioso atento a detalhes, vi que não é mencionado nada sobre o nome das colunas e resolvi verificar se colunas equivalentes podem ter nomes diferentes.

CREATE TABLE [dbo].[Tabela] (
	[id] INT PRIMARY KEY,
	[colunaX] CHAR(10),
	[inicio_registro] DATETIME2(0) NOT NULL,
	[fim_registro] DATETIME2(0) NOT NULL
);
CREATE TABLE [dbo].[TabelaHistorico] (
	[id] INT NOT NULL,
	[colunaY] CHAR(10),
	[inicio_registro] DATETIME2(0) NOT NULL,
	[fim_registro] DATETIME2(0) NOT NULL
);
ALTER TABLE [dbo].[Tabela] ADD PERIOD FOR SYSTEM_TIME ([inicio_registro], [fim_registro]);
GO
ALTER TABLE [dbo].[Tabela] SET ( SYSTEM_VERSIONING = ON ( HISTORY_TABLE = [dbo].[TabelaHistorico] ) );
GO

Para minha surpresa, recebi a seguinte mensagem de erro:

Ahn... Nope.

Ahn… Nope.

Em outras palavras, apesar do BOL não mencionar, os nomes das colunas equivalentes na history table devem ser iguais aos da tabela principal.

VERIFICAÇÃO DE CONSISTÊNCIA DOS DADOS

Como havia mencionado no post anterior, no momento em que é ativado o versionamento por sistema, é realizada uma verificação de consistência dos dados. Esta verificação é realizada por padrão, mas você pode optar por ignorá-la com a opção DATA_CONSISTENCY_CHECK = OFF. Nesta verificação o SQL Server irá avaliar se a coluna “inicio_registro” é menor ou igual à “fim_registro” e se há sobreposição da validade dos registros.

Se acompanhar esta verificação da tabela Disciplina (criada no post anterior) você verá algo como a consulta abaixo:

SELECT 
	[inicio_registro],
	[fim_registro],
	T.S,
	[$tabType]
FROM (SELECT
		[inicio_registro],
		[fim_registro],
		LAG([fim_registro]) OVER (PARTITION BY [id_disciplina] ORDER BY [inicio_registro]) AS S,
		[$tabType]
	FROM (SELECT
			[id_disciplina],
			[inicio_registro],
			[fim_registro],
			0 AS [$tabType]
		FROM [dbo].[DisciplinaHistorico]
		UNION ALL
		SELECT
			[id_disciplina],
			[inicio_registro],
			[fim_registro],
			1 AS [$tabType]
		FROM [dbo].[Disciplina]) AS R
	) AS T

Com isso você já pode concluir que, sem uma indexação adequada, esta consulta pode sofrer. No caso de VLTs esta verificação pode apresentar um obstáculo, fazendo jus à utilização da opção DATA_CONSISTENCY_CHECK = OFF.

ALTERAÇÃO DE SCHEMA

Suponhamos que você criou a tabela Disciplina com o versionamento de sistema com o script abaixo:

CREATE TABLE [dbo].[Disciplina] (
    [id_disciplina] INT CONSTRAINT pk_Disciplina PRIMARY KEY,
    [nome_disciplina] VARCHAR(50) NOT NULL,
    [creditos] INT NOT NULL,
    [inicio_registro] DATETIME2(0) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
    [fim_registro] DATETIME2(0) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
    PERIOD FOR SYSTEM_TIME ([inicio_registro],[fim_registro])
)WITH(SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[DisciplinaHistorico]));

Após um determinado tempo, foi solicitado que a coluna “valor_credito” seja acrescentada à tabela. Ao tentar adicionar a coluna, você é surpreendido com o seguinte erro:

Mas eu sou sysadmin!?

Ok…e agora? De acordo com o BOL:

In order the change the schema of a temporal table, you must first temporarily turn off SYSTEM_VERSIONING to enable the schema change (schema changes are blocked while SYSTEM_VERSIONING is ON to prevent the two tables becoming out of sync. While SYSTEM_VERSIONING is OFF, you can make the schema change to both tables independently and then re-enable SYSTEM_VERSIONING. Perform the above steps within a transaction to maintain data consistency.

Tirando o parêntese que nunca será fechado, este trecho explica que alterações de schema são bloqueadas enquanto o versionamento de sistema estiver habilitado e que, para manter a consistência dos dados, estas alterações devem ocorrer dentro de uma transação. Aplicando estes princípios temos:

BEGIN TRAN 
-- Desabilitar o versionamento de sistema 
ALTER TABLE [dbo].[Disciplina] SET (SYSTEM_VERSIONING = OFF);
-- Adicionar coluna a tabela principal
ALTER TABLE [dbo].[Disciplina] ADD [valor_credito] MONEY NULL;
-- Realizar mesma operação na tabela histórica
ALTER TABLE [dbo].[DisciplinaHistorico] ADD [valor_credito] MONEY NULL;
-- Habilitar o versionamento de sistema 
ALTER TABLE [dbo].[Disciplina] SET (SYSTEM_VERSIONING = ON);
--
COMMIT TRAN

Então você executa esse script e a mensagem de executado com sucesso aparece. Logo, você dá continuidade à sua rotina até que, em algum momento, esbarra com a tabela no object explorer.

WTF...?!

WTF…?!

Opa! Tem algo errado aí. Se você leu o post anterior, já identificou que a nova tabela histórica foi criada pelo SQL Server. Isso acontece quando não é determinada uma history table no momento da ativação do versionamento de sistema. Portanto, o script correto seria:

BEGIN TRAN 
-- Desabilitar o versionamento de sistema 
ALTER TABLE [dbo].[Disciplina] SET (SYSTEM_VERSIONING = OFF);
-- Adicionar coluna a tabela principal
ALTER TABLE [dbo].[Disciplina] ADD [valor_credito] MONEY NULL;
-- Realizar mesma operação na tabela histórica
ALTER TABLE [dbo].[DisciplinaHistorico] ADD [valor_credito] MONEY NULL;
-- Habilitar o versionamento de sistema 
ALTER TABLE [dbo].[Disciplina] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[DisciplinaHistorico]));
--
COMMIT TRAN

Vale ressaltar que você poderia utilizar a opção DATA_CONSISTENCY_CHECK = OFF neste exemplo sem problemas. Afinal de contas, você está isolado dentro de uma transação.

Enquanto o SYSTEM_VERSIONING = ON, as operações a seguir são proibidas tanto para tabela principal quanto para tabela histórica:

  • DROP TABLE;
  • ALTER TABLE…ADD COLUMN;
  • ALTER TABLE…ALTER COLUMN;
  • ALTER TABLE…DROP COLUMN;

No entanto, existem mais restrições específicas para cada tabela.

Tabela principal:

  • ALTER TABLE…DROP PERIOD não é permitido;
  • ALTER TABLE…SWITCH IN é permitido;

Tabela histórica:

  • ALTER TABLE…ADD PERIOD não é permitido;
  • ALTER TABLE…SWITCH OUT é permitido;

Tirando essas restrições, algumas das operações que podemos fazer com o SYSTEM_VERSIONING = ON são:

  • ALTER SCHEMA…TRANSFER;
  • ALTER TABLE…REBUILD;
  • CREATE INDEX;
  • CREATE STATISTICS;

INDEXAÇÃO DE TABELAS TEMPORAIS

Na documentação da Microsoft, é possível encontrar uma recomendação sobre indexação bem direta:

An optimal indexing strategy will include a clustered columns store index and / or a B-tree rowstore index on the current table and a clustered columnstore index on the history table for optimal storage size and performance. If you create / use your own history table, we strongly recommend that you create such an index that consists of the primary keys from the current table plus the period columns to speed up temporal querying as well as the queries that are part of the data consistency check. If your history table is a rowstore, we recommend a clustered rowstore index. The default history table has a clustered rowstore index created for you based on the primary key from the current table and the period columns. At a minimum, a non-clustered rowstore index is recommended.

Em outras palavras, as recomendações sugerem três situações distintas:

  1. Tabela principal e tabela histórica com índices clustered rowstore;
  2. Tabela principal com índice clustered rowstore e tabela histórica com índice clustered columnstore;
  3. Tabela principal e tabela histórica com índices clustered columnstore.

Pensei comigo: Excelente deixa para um benchmark! Então, comecei a preparar os scripts e acabei encontrando uma série de posts do Alexandr Volok (site|twitter) com o benchmark muito similar ao que pensei. Pude reproduzir os testes que resultaram em dados muito próximos aos dele, por isso vou apenas descrever o procedimento e divulgar seus posts e resultados. Convido você a ler a série na integra (links na seção de referência).

Na primeira parte do post, Volok define os processos a serem realizados e o escopo de cada análise. Os processos são:

  • Criar uma tabela principal com 1 milhão de registros;
  • Gerar duas séries de UPDATE e duas de DELETE, totalizando 400 mil linhas afetadas (resultando em 400 mil linhas na tabela histórica e 800 mil na principal);
  • Duas consultas de agregação (SUM) de 100 mil linhas: uma normal e outra temporal;
  • Duas consultas de agregação (SUM) através de um filtro pré-determinado: uma normal e outra temporal;

O escopo da análise destes processos se dá da seguinte forma:

  • Uso de disco (espaço de armazenamento);
  • Duração das operações de DML;
  • Duração das 4 consultas de agregação;

Os resultados da análise mostram que a situação 3, onde ambas as tabelas possuem índices clustered columnstore, atinge os melhores resultados tanto em uso de disco (aproximadamente 10 vezes menor que a situação 2) quanto em duração das consultas (de 2 a 70 vezes mais rápido que a situação 2). A situação 1 foi a pior das três analisadas, mas ainda é a única solução para quem não possui a edição Enterprise. A situação 2 foi melhor apenas na duração das operações de DML. No entanto, essa duração subiu drasticamente com o aumento do número de registros, enquanto a situação 3 se manteve muito eficiente.

No último post da série, a massa de dados é aumentada em 10 vezes. Desta vez, a situação 3 é indiscutivelmente a melhor solução de todas.

Numa análise sobre o benchmark realizado, vejo que o espaço de armazenamento da solução com dois índices clustered columnstore é impressionante e quase de fato determinante na escolha dos índices. A duração das operações DML também representa um fato significativo na escolha, apesar de não refletir um ambiente OLTP onde são realizadas várias transações pequenas. A duração das consultas é relevante quando se trata de agregações, no entanto o benchmark não realiza outro tipo de consultas. Esta análise fica para outro post.

CONCLUSÃO

Esta nova feature certamente traz diversas considerações sobre soluções implantadas “à mão” nos dias de hoje. No entanto, certos detalhes, como a necessidade dos mesmos nomes de coluna para ambas as tabelas, ainda não estão documentados. A verificação de consistência de dados pode ter um grande impacto em VLTs, principalmente quando a estratégia de indexação não for a ideal; E a alteração no esquema de uma tabela temporal possui detalhes importantes para a adoção correta desta tecnologia.

No próximo post desta série, irei falar como funcionam as operações DML numa tabela temporal e como eles são refletidos na tabela de histórico. Até lá!

REFERÊNCIAS / LINKS

[1] Temporal Tables (https://msdn.microsoft.com/en-us/library/dn935015.aspx)
[2] SQL Server 2016: Temporal Tables. Indexing. Part I. Rowstore Tables (http://www.alexvolok.com/2015/06/sql-2016-temporal-tables-indexing-part-i/)
[3] SQL Server 2016: Temporal Tables. Indexing. Part II. Columnstore Tables (http://www.alexvolok.com/2015/06/sql-2016-temporal-tables-indexing-part-ii/)
[4] SQL Server 2016: Temporal Tables. Indexing. Part III. Enlarged Dataset (http://www.alexvolok.com/2015/06/sql-2016-temporal-tables-indexing-part-i/)

LEITURA ADICIONAL

[1] Temporal Tables (http://sqlwithmanoj.com/tag/temporal-tables/)
[2] SQL Server 2016: Temporal Tables (http://www.infoq.com/news/2015/06/SQL-Server-Temporal)
[3] Time traveling with SQL Server 2016: Temporal Tables (https://devjef.wordpress.com/2015/07/13/time-traveling-with-sql-server-2016-temporal-tables/)

Tabelas Temporais – Parte 1: Criação de tabelas

No SQL Server 2016 encontramos uma feature baseada na ISO/ANSI SQL:2011 chamada System-Versioned Temporal Tables [1]. Essa “nova” propriedade permite criar ou alterar uma tabela para que suas alterações ao longo do tempo sejam registradas numa tabela de histórico. Isso possibilita que sejam realizadas consultas num determinado momento, como se viajássemos no tempo e executássemos a consulta naquele instante. Isso entre muitas outras capacidades legais que abordarei nesta série de posts. Digo “nova” pois seus concorrentes diretos já possuem esta funcionalidade há um certo tempo… O DB2 v10 já possui este recurso desde 2010, enquanto o Oracle 12c desde 2013 (ou desde 2005 se considerar as Flashback Queries). Sem deixar de mencionar a comunidade do PostgreSQL que vem discutindo isso desde 2012… Sem problemas. Chegamos lá.

Nesta primeira parte irei abordar o básico sobre a criação de uma tabela temporal e algumas considerações acerca do assunto.

Vale ressaltar que, no momento em que este post foi escrito, a versão disponível do SQL Server é a 2016 CTP2.2. Logo, alguns detalhes podem mudar com as novas versões lançadas. Farei um esforço para manter este material atualizado de acordo com todas atualizações, mas isso vai depender da minha disponibilidade.

CRIANDO TABELAS TEMPORAIS

Para criar uma tabela temporal, existem alguns requisitos que devem ser atendidos.

  • A tabela principal deve possuir uma chave primária;
  • A tabela principal deve possuir duas colunas do tipo DATETIME2 (não importa a precisão) que sejam NOT NULL para controlar o início e o fim da validade do registro;
  • A coluna que determina o início da validade do registro deve conter a opção GENERATED ALWAYS AS ROW START;
  • A coluna que determina o fim da validade do registro deve conter a opção GENERATED ALWAYS AS ROW END;
  • Um período de validade do registro deve ser definido com a opção PERIOD FOR SYSTEM_TIME utilizando as duas colunas DATETIME2 criadas;
  • Uma tabela de histórico deve ser associada à tabela principal;
  • A opção de tabela SYSTEM_VERSIONING deve ser definida como ON;

As demonstrações dessa série de posts serão executadas no banco TemporalTestDB. Primeiro vamos criar o banco e apagar as tabelas caso elas existam.

USE [master];
GO
IF DB_ID(N'TemporalTestDB') IS NULL
CREATE DATABASE [TemporalTestDB]
GO
USE [TemporalTestDB];
GO
SET NOCOUNT ON;
IF OBJECT_ID(N'dbo.Disciplina',N'U') IS NOT NULL
BEGIN
IF OBJECTPROPERTY(OBJECT_ID(N'dbo.Disciplina',N'U'),N'TableTemporalType') = 2 -- É Temporal
ALTER TABLE [dbo].[Disciplina] SET (SYSTEM_VERSIONING = OFF);
DROP TABLE [dbo].[Disciplina];
END
IF OBJECT_ID(N'dbo.DisciplinaHistorico',N'U') IS NOT NULL
DROP TABLE [dbo].[DisciplinaHistorico];

A tabela criada inicialmente será a tabela temporal Disciplina que guardará todas alterações na tabela de histórico DisciplinaHistorico. Se preferir consultar a documentação oficial, veja o artigo do BOL CREATE TABLE (Transact-SQL) [2]. Nota: Esta tabela de histórico será criada pelo SQL Server neste exemplo.

CREATE TABLE [dbo].[Disciplina] (
	[id_disciplina] INT CONSTRAINT pk_Disciplina PRIMARY KEY,
	[nome_disciplina] VARCHAR(50) NOT NULL,
	[creditos] INT NOT NULL,
	[valor_credito] MONEY NOT NULL,
	[inicio_registro] DATETIME2(0) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL,
	[fim_registro] DATETIME2(0) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL,
	PERIOD FOR SYSTEM_TIME ([inicio_registro],[fim_registro])
)WITH(SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[DisciplinaHistorico]));

Algumas considerações que não ficam explícitas com esse comando são:

  • A tabela principal pode ser uma HEAP;
  • As colunas que determinam a validade de um registro são gerenciadas pelo SQL Server, ou seja, não é permitido atribuir valores explícitos a estes campos;
  • A tabela de histórico não precisa existir previamente;
  • A tabela de histórico não precisa estar nos mesmos schema ou filegroup que a tabela principal, é possível movê-la para outro filegroup depois;
  • Caso a tabela de histórico seja criada pelo SQL Server, ela possuirá um índice cluster baseado na chave primária e nas colunas que definem a validade do registro na tabela principal;

Vale dizer que a tabela de histórico não precisa ter índices. No entanto, suas consultas temporais sofrerão muito com isso, já que sempre será realizado um table scan na tabela de histórico. A tabela de histórico também pode existir antes da criação da tabela principal. Geralmente ela estará vazia no momento da criação do link entre a tabela principal e ela, mas isso não é obrigatório. Veremos algumas situações inusitadas em um post futuro quando eu for falar sobre DATA_CONSISTENCY_CHECK.

Um outro detalhe interessante de se mencionar é a nova opção HIDDEN para as colunas de validade dos registros. Esta opção não obrigatória faz com que essas colunas não sejam apresentadas quando se executa um SELECT *. Desta forma, as colunas só aparecerão se forem listadas explicitamente no SELECT. Uma ótima notícia para quem deseja baixo impacto na implementação desta nova tecnologia.

CONVERTENDO UMA TABELA EXISTENTE EM TABELA TEMPORAL

Agora vamos supor que você tenha migrado para o SQL Server 2016 e queria utilizar essa tecnologia numa tabela já existente. Quais os passos necessários?

A primeira pergunta é: você já tem uma tabela de histórico criada e mantida por triggers, CDC, etc? Ou você começará a registrar os dados históricos a partir de agora? Vou mostrar a forma mais comum – iniciar a captura de dados históricos do zero – neste post e futuramente explorarei o ajuste de tabelas de histórico já existentes para a ativação do SYSTEM_VERSIONING.

Imaginemos que a tabela Disciplina já existia no banco e continha alguns registros.

CREATE TABLE [dbo].[Disciplina] (
	[id_disciplina] INT CONSTRAINT pk_Disciplina PRIMARY KEY,
	[nome_disciplina] VARCHAR(50) NOT NULL,
	[creditos] INT NOT NULL,
	[valor_credito] MONEY NOT NULL
);

INSERT INTO [dbo].[Disciplina]([id_disciplina],[nome_disciplina],[creditos],[valor_credito]) 
VALUES 
		(1,'Cálculo I',4,100.00),
		(2,'Cálculo II',4,125.00),
		(3,'Cálculo III',4,150.00),
		(4,'Física Teórica I',4,100.00),
		(5,'Física Experimental I',4,75.00),
		(6,'Português',6,100.00);
GO

Para que seja possível ativar o SYSTEM_VERSIONING nesta tabela, é necessário verificar se ela possui chave primária. Como a criamos agora, sabemos que ela já tem. No entanto, poderíamos verificar isso rapidamente com o seguinte trecho de código:

SELECT OBJECTPROPERTY(OBJECT_ID(N'TemporalTestDB.dbo.Disciplina'),'TableHasPrimaryKey')

Resultado 1 significa que sim e 0 que não possui chave primária. Se o resultado for NULL, significa que o objeto não foi encontrado. Talvez por algum erro de digitação. Agora que temos certeza de que ela possui PK, temos de adicionar as duas colunas do tipo DATETIME2 que sejam NOT NULL para determinar a validade do registro. Pelo fato da tabela Disciplina ter registros e ser pequena (e eu ter preguiça) podemos adicionar as duas colunas com default constraints para que possam ter a definição NOT NULL de cara. Aproveitando o mesmo comando, podemos definir a opção PERIOD FOR SYSTEM_TIME de uma vez. Farei isso num comando só, mas é claro que podemos dividi-los em três comandos separados.

ALTER TABLE [dbo].[Disciplina] ADD
	[inicio_registro] DATETIME2(0) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL 
		CONSTRAINT dflt_inicio_registro DEFAULT SYSDATETIME(),
	[fim_registro] DATETIME2(0) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL
		CONSTRAINT dflt_fim_registro DEFAULT '9999-12-31 23:59:59',
	PERIOD FOR SYSTEM_TIME ([inicio_registro],[fim_registro]);

Sim, você pode remover essas constraints depois. Quem irá cuidar do preenchimento dessas colunas após a opção SYSTEM_VERSIONING ser habilitada é o SQL Server. Uma coisa importante de se lembrar é que os valores da coluna que define o início da validade do registro devem ser iguais ou menores que o instante de tempo em que você habilitar o versionamento. Caso contrário você pode esbarrar com um erro como o abaixo (clique para aumentar) ao tentar realizar uma operação DML na tabela.

Transação ocorreu antes do período de validade do registro?

Transação ocorreu antes do período de validade do registro?

O que é engraçado, pois você tem um registro que ainda não é válido no tempo. Ele só se tornará válido – i.e. possível de ser alterado – quando o valor da coluna “inicio_registro” (neste caso) for igual ou menor que o momento da alteração. Na minha humilde opinião, esta verificação – “inicio_registro” é menor ou igual ao momento atual – deveria estar inclusa na opção padrão que é DATA_CONSISTENCY_CHECK = ON. No entanto, para a tabela principal, esta opção apenas verifica se “inicio_registro” é menor ou igual à “fim_registro”. O que você acha?

Já o valor para a coluna “fim_registro” deveria definir uma “validade infinita”. Logo, o valor máximo do tipo DATETIME2 é um bom candidato ao nosso “infinito”. Isso não é obrigatório, claro. Só basta ser maior que a coluna “inicio_registro”. Obviamente, isso pode gerar outras situações inesperadas como registros da tabela principal que já “perderam a validade”. Desta forma, caso você faça uma consulta temporal para o momento atual, o registro que está presente na tabela principal não aparecerá. Crazy, huh?! Falarei sobre diversas situações inusitadas e até um possível bug num outro post. Então, de volta ao assunto!

Caso você possua uma VLT (Very Large Table), essa solução das default constraints pode não ser ideal. Então, outra opção seria criar as colunas de validade do registro como DATETIME2 NULL e atualizar os registros em lotes até que todas estejam com os valores devidos. Depois bastaria alterar a coluna com as opções necessárias e voilà. No entanto, esta opção não é permitida (por enquanto, eu espero). Caso você tente, receberá algo como a mensagem abaixo (clique para aumentar).

No alter column for you...

No alter column for you…

É claro que existem outras maneiras de fazer isso. Uma solução é criar uma tabela vazia com as colunas da tabela existente e todos os outros pré-requisitos de uma tabela temporal, ativar o SYSTEM_VERSIONING e transferir os dados aos poucos de uma tabela para outra enquanto uma view abstrai esta mudança para a aplicação. Obviamente, existem diversas outras formas, mas o ponto é que nada é realmente simples quando se trabalha com VLTs, certo?

Após sanarmos os pré-requisitos na tabela principal, podemos habilitar o SYSTEM_VERSIONING finalmente.

ALTER TABLE [dbo].[Disciplina] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[DisciplinaHistorico]));

Caso queira verificar a documentação oficial, basta acessar o artigo do BOL ALTER TABLE (Transact-SQL) [3]. Nota: Caso você não defina uma HISTORY_TABLE, o SQL Server criará uma para você com o nome “MSSQL_TemporalHistoryFor_<object id da tabela principal>”.

Se olharmos agora para o object explorer, veremos o seguinte:

Uuuhhh... New stuff!

Uuuhhh… New stuff!

Verificando a sys.tables (Transact-SQL) [4], vemos três colunas novas: temporal_type, temporal_type_desc e history_table_id. Esta última ainda não aparece na documentação oficial, mas representa a conexão entre a tabela principal e a tabela de histórico.

imagem04

Os valores da coluna temporal_type  também podem ser retornados usando a nova propriedade “TableTemporalType” na função OBJECTPROPERTY (Transact-SQL) [5].

Update: A função OBJECTPROPERTYEX também possui este novo parâmetro.

imagem05

Existem colunas novas também na sys.columns (Transact-SQL) [6] específicas para tabelas temporais.

imagem06

De forma análoga a função OBJECTPROPERTY, podemos obter essas informações através da COLUMNPROPERTY (Transact-SQL) [7].

imagem07

Existe também a nova view de sistema, sys.periods (Transact-SQL) [8], que contém informações sobre o período de validade do registro como quais colunas que definem o período e para qual objeto.

imagem08

CONCLUSÃO

Esta nova feature do SQL Server 2016, System-Versioned Temporal Tables, certamente é bem bacana! As possibilidades de aplicação desta funcionalidade são inúmeras e é ótimo vê-la nesta nova versão. No próximo post falarei sobre alterações de schema em tabelas temporais e estratégias de indexação. Então, até lá! Se quiser mais material publicado sobre o assunto, veja os links na seção “Leitura Adicional”.

REFERÊNCIAS / LINKS

[1] Temporal Tables (https://msdn.microsoft.com/en-us/library/dn935015.aspx)
[2] CREATE TABLE (Transact-SQL) (https://msdn.microsoft.com/en-us/library/ms174979.aspx)
[3] ALTER TABLE (Transact-SQL) (https://msdn.microsoft.com/en-us/library/ms190273.aspx)
[4] sys.tables (Transact-SQL) (https://msdn.microsoft.com/en-us/library/ms187406.aspx)
[5] OBJECTPROPERTY (Transact-SQL) (https://msdn.microsoft.com/en-us/library/ms176105.aspx)
[6] sys.columns (Transact-SQL) (https://msdn.microsoft.com/en-us/library/ms176106.aspx)
[7] COLUMNPROPERTY (Transact-SQL) (https://msdn.microsoft.com/en-us/library/ms174968.aspx)
[8] sys.periods (Transact-SQL) (https://msdn.microsoft.com/en-us/library/mt130702.aspx) 

LEITURA ADICIONAL

[1] First Look at System-Versioned Temporal Tables-Part 1: Creating Tables and Modifying Data (http://sqlmag.com/sql-server/first-look-system-versioned-temporal-tables-part-1-creating-tables-and-modifying-data)
[2] First Look at System-Versioned Temporal Tables-Part 2: Querying Data and Optimization Considerations (http://sqlmag.com/sql-server/first-look-system-versioned-temporal-tables-part-2-querying-data-and-optimization-conside)
[3] Introduction to SQL Server 2016 Temporal Tables (https://www.mssqltips.com/sqlservertip/3680/introduction-to-sql-server-2016-temporal-tables/)
[4] SQL Server 2016 T-SQL Syntax to Query Temporal Tables (https://www.mssqltips.com/sqlservertip/3682/sql-server-2016-tsql-syntax-to-query-temporal-tables/)

Instalando o novo SQL Server Management Studio

No final de Junho, foi anunciado pela Microsoft [1] uma versão independente do SQL Server Management Studio (SSMS) para download. Obviamente, isso saiu em diversos blogs/posts conhecidos [2][3][4], por isso não vou falar muito mais sobre ele aqui. Esta versão, além de só existir em Inglês, contém um instalador pequeno (aproximadamente 32MB) que torna o processo muito prático. Vale ressaltar que no momento deste post, a versão mais atual é a “July 2015 Preview”.

Se você quiser baixar este novo SSMS, basta ir para a página de download do SSMS [5] e clicar em “Download SQL Server Management Studio July 2015 Preview”. Após baixar o arquivo, basta executá-lo para que ele comece a baixar os arquivos necessários para a instalação como uma prévia do .NET Framework 4.6.

NewManagementStudio

Caso você esbarre em algum erro no processo de instalação, veja nas notas de lançamento do SSMS [6] se o erro ou comportamento é conhecido e documentado. Eu acabei não conseguindo instalar o novo SSMS na primeira tentativa, pois estava com o SQL Server 2016 CTP 2.0 instalado na minha VM.

Depois de instalado, sempre que o novo SSMS for iniciado, ele verificará se existem novas atualizações disponíveis para download. Mas caso você queria verificar manualmente, basta clicar no botão “Check for Updates…”.

NewManagementStudio_Updates

E aí, você já instalou ou tem instalado o novo SSMS? O que achou dessa novidade?

REFERÊNCIAS / LINKS

[1] Announcing SQL Server Management Studio – June 2015 Release (http://blogs.msdn.com/b/sqlreleaseservices/archive/2015/06/24/sql-server-management-studio-june-2015-release.aspx)
[2] SQL Server 2016 launches new rapid preview model with CTP 2.1 (http://blogs.technet.com/b/dataplatforminsider/archive/2015/06/24/sql-server-2016-launches-new-rapid-preview-model-with-ctp-2-1.aspx)
[3] SQL Server Management Studio Now Has Its Own Upgrade Path (http://sqlmag.com/sql-server-2016/sql-server-management-studio-now-has-its-own-upgrade-path)
[4] New SQL Server Management Studio 2015/June – with Check for Updates! (http://www.brentozar.com/archive/2015/06/new-sql-server-management-studio-2015june-with-check-for-updates/)
[5] Download SQL Server Management Studio (https://msdn.microsoft.com/en-us/library/mt238290.aspx)
[6] SQL Server Management Studio – Release Notes (https://msdn.microsoft.com/en-us/library/mt238486.aspx)