Como utilizar diversos tipos de dados em uma única coluna no SQL Server Pessoal, tudo bem? Recentemente foi questionado por alguns alunos, em relação a capacidade do SQL Server em reconhecer cada tipo de dados existente, como também, se existe a possibilidade de armazenar em uma mesma coluna diferentes tipos de informações. No momento desta pergunta, me veio a cabeça, um simples exemplo: “Utilize um campo do tipo Varchar() ou Char(), desta forma, você poderá armazenar tanto valores numéricos como também valores caracteres”. Até ai tudo bem. Mas a dúvida persistiu e outro aluno me questionou. Galvão, imagine a seguinte situação. “Uma coluna por exemplo denominada Valores_Diversos, e nesta mesma coluna o SQL Server permitir que sejam armazenados, dados do tipo, Integer, Varchar, Char, Float entre outros. Existe essa possibilidade?”. Pensei, pensei, pensei e devolvi a resposta: “Olha acredito que exista pois o SQL Server possui um mecanismo de armazenamento de dados em colunas muito versátil e volátil, mas eu não me lembro o nome deste recurso ou tipo de dados. Vou procurar e volto com a resposta.” Pois bem, seguindo alguns conselhos, informações, críticas e principalmente querendo postar algum de conteúdo didático e que possa ajudar, comecei durante estes dias estudar um pouco mais sobre as estruturas de armazenamento utilizadas pelo SQL Server. Dentre as estruturas, formas de armazenamento e tipos de dados, me deparei com o chamado SQL_Variant. Pensei, SQL_Variant(Variante SQL), esta ai a resposta para estas perguntas. Sendo assim me aprofundei no assunto, o que acabou resultando neste artigo. Comecei então conhecer este tipo de dados e consegui identificar a possibilidade de ser utilizado em diversos tipos de dados distintos, mas nem todos os tipos mais conhecidos são possíveis de serem aplicados diretamente. Por outro lado, tipos de dados, como Char, Int e Binary podem ser armazenados naturalmente. Conhecendo o SQL_Variant: O SQL_Variant foi introduzido no SQL Server a partir da versão 2000, com objetivo de possibilitar a utilização em uma mesma coluna um conjunto distinto e variado de dados relacionados a tipos e formatos diferentes. A partir do momento em que utilizamos este tipo de dados, estamos forçando o SQL Server a trabalhar de uma forma variável para realizar os armazenamento dos dados sobre estas colunas, o que poderá resultar em pequenos aumentos no tempo de gravação dos dados, além do que, podemos gerar um nível de fragmentação bem maior que se estivessemos utilizando colunas separadas para armazenar cada dado. Vale ressaltar que uma coluna do tipo SQL_Variant, pode armazenar no máximo 8.016 bytes de dados, isso incluindo: Informações do tipo da base de dados que esta sendo utilizada; Informações sobre o valor que esta sendo armazenado. Com isso o tamanho máximo para armazenar dados para colunas SQL_Variant é de no máximo 8.000 bytes. Utilizar o SQL_Variant é bem simples, desde o processo de criação de uma table com colunas SQL_Variant como também nos demais comandos utilizados pela Linguagem DML(Data Manipulation Language – Linguagem de Manipulação de Dados). O SQL_Variant trabalha de forma similar ao tipo de dados variant existente Microsoft Visual Basic. Este tipo de dados, pode ser utilizado como: Colunas; Parâmetros; e Variáveis. Incompatibilidades apresentadas pelo SQL_Variant: Como tudo no mundo, nada é perfeito, o SQL_Variant também apresenta as suas limitações, dentre elas diversos tipos de dados que não podem ser utilizados no processo de inserção de informações para colunas SQL_Variant. A tabela 1, a seguir apresenta a relação dos tipos de dados incompatíveis com o SQL_Variant. varchar(max) varbinary(max) nvarchar(max) Xml text Ntext image Timestamp sql_variant Geography hierarchyid Geometry Tabela 1 – Tipos de dados incompatíveis e não suportados pelo SQL_Variant. Não somente tipos de dados o SQL_Variant não suporta, muitas das mais básicas e comuns funções utilizadas pelo SQL Server, também não são suportadas por este tipo de dados. A tabela 2, apresenta esta relação: AVG RADIANS STDEV[P] IDENTITY ROUND SUM ISNUMERIC SIGN VAR[P] POWER Tabela 2 – Funções que não oferecem suporte ao SQL_Variant. Por outro lado funções como , CAST e CONVERT oferecem suporte, além disso, uma nova função de sistema conhecida como SQL_VARIANT_PROPERTY(), poderá ser utilizada como forma de obtenção de informações relacionadas as propriedades e valores armazenadas sobre uma coluna SQL_Variant. Importante: As colunas SQL_Variant não possuem suporte a índices Full-Text, alémd disso, não podem ser utilizadas em conjunto com as funções, CONTAINSTABLE e FREETEXTTABLE. Trabalhando com o SQL_Variant: Agora que já conhecemos um pouco sobre este tipo de dados, suas características, desvantagens e recursos nativos. Vamos começar a trabalhar diretamente com ele, utilizando um cenário fictício, mas que poderá ser de grande aprendizagem. Para este cenário vou criar uma tabela, utilizando algumas colunas dentre elas uma com o tipo SQL_Variant. Posteriormente faremos a inserção dos dados nos formatos Int, Char, Varchar e Float. Em seguida, consultaremos os dados armazenados e por último será realizado uma nova inserção de dados, que nos mostrará algumas surpresas existentes no SQL_Variant. Montando o ambiente e fazendo uso do SQL_Variant: Bom começamos a montar o nosso cenário, através da criação de uma base de dados chamada Exemplos, em seguida criamos a tabela Produtos, nesta tabela faremos uso do tipo de dados SQL_Variant aplicado a coluna Lote_Fabricação. Vale ressaltar que não é necessário nenhuma configuração adicional para trabalhar com o SQL_Variant, muito menos mudança no nível de compatibilidade do banco de dados. O Código 1 apresenta o script de criação do banco de dados e da tabela Produtos. Código 1 – Criando o Banco de Dados Exemplos e a Tabela Produtos: CREATE DATABASE EXEMPLOS GO Use EXEMPLOS Go CREATE TABLE PRODUTOS (CODIGO INT IDENTITY(1,1) Primary Key, DESCRICAO VARCHAR(100), LOTE_FABRICACAO SQL_VARIANT) ON [PRIMARY] Com o banco de dados e a tabela criada vamos inserir alguns registros em nossa tabela. Observe que neste primeiro bloco de inserção de registros, estamos trabalhando com dados no formatos: Varchar(), Char(), Int e Float. Um detalhe importante que gostaria de destacar, esta relacionada a veracidade dos dados, em nenhum registro foi respeito qualquer tipo de regra ou convenção que possa existir quando se referimos a Lote de Fabricação de um Produto. O objetivo é expor e apresentar a compatibilidade e armazenamento dos dados em diversos tipos e formatos. O Código 2 apresentado a seguir ilustra o primeiro bloco de inserção de dados. Código 2 – Inserindo o primeiro bloco de registros: Insert Into PRODUTOS Values('Arroz','ABR-1009'); Insert Into PRODUTOS Values('Feijão','FEI-2010'); Go Insert Into PRODUTOS Values('Bolacha 1',209310); Insert Into PRODUTOS Values('Bolacha 2',93133); Insert Into PRODUTOS Values('Bolacha 3',392873); Insert Into PRODUTOS Values('Bolacha 5',209310); Go Insert Into PRODUTOS Values('Biscoito 1',5.25); Insert Into PRODUTOS Values('Biscoito 2',2.00); Insert Into PRODUTOS Values('Biscoito 3',3.250); Insert Into PRODUTOS Values('Biscoito 4',1.553); Go Durante o processo os processos de manipulação de dados, oriundos dos comandos Insert, Update e Delete. O SQL Server realiza uma análise de comparação para identificar o tipo de dados e formato que esta sendo passado para inserção. Neste processo o SQL Server mapeia os valores passados como parâmetro, com isso é possível estabelecer a compatibilidade do valor ao tipo de dado ao qual ele pertence. Todos os dados foram mapeados e inseridos de forma natural sem necessitar de qualquer tipo de tratamento ou conversão implicita, para ilustrar a inserção dos dados, a Figura 1 apresenta a Tabela Produtos e o primeiros registros inseridos. Figura 1 – Dados inseridos na Tabela Produtos. Com esta primeira massa de dados inseridas, vamos continuar nosso processo de inserção de dados, agora trabalhando com dados no formato DateTime. Nesta parte, poderá evidenciar as surpresas existentes pelo SQL_Variant, no processos de mapeamento dos dados. Para isso, vamos utilizar o Código 3, responsável pela inserção de dados no formato DateTime. Código 3 – Inserindo o segundo bloco de registros, utilizando o formato DateTime Insert Into PRODUTOS Values('Biscoito',2011-02-24); Insert Into PRODUTOS Values('Biscoito',2011-02-25); Insert Into PRODUTOS Values('Biscoito',2011-02-26); Insert Into PRODUTOS Values('Biscoito',2011-02-27); Insert Into PRODUTOS Values('Biscoito',2011-02-28); Go Nossos novos dados já foram inseridos, aparentemente sem nenhum problema, mas se utilizarmos o comando Select para apresentar o atual conjunto de registros de nossa tabela Produtos, teremos a surpresa. Os valores declarados para o campo Lote_Fabricacao no comando Insert utilizado pelo Código 3, foram convertidas para o formato e tipo de dados Integer, onde todos os valores de data foram recalculados e somandos para valores que indicam um ano em nosso calendário. A Figura 2 apresenta o resultado do comando Select. Figura 2 – Dados inseridos no formato DateTime convertidos para o formato Integer. Após observarmos os registros inseridos em nossa tabela e o campo Lote_Fabricacão para as linhas convertidos em valores Integer. Nos apresenta alguns questionamentos: Por qual motivo o SQL Server realizou a conversão dos Dados? Existe alguma incompatibilidade em se utilizar tipos de dados DateTime? Talvez seja necessário utilizar alguma função para mapeamento dos tipos de dados e vincular aos valores especificados no comando Insert? Para todas estas perguntas existem respostas que poderam esclarecer o que aconteceu durante o processo de inserção. Trata-se de uma resposta simples e direta. O SQL Server realizou a conversão dos valores para o tipo de dados Int, por entender que estes valores podem ser armazenados neste formato, realizando um cálculado para definir o valor que será armazenado, mesmo que o valor e seu formato esteja especificado como DateTime. Para contornar esta característica do SQL_Variant, é necessário utilizar as funções de conversão de dados implicitas Convert e Cast. Não existe incompatibilidade do SQL_Variant para armazenar dados no formato DateTime, existe sim uma atenção para se estabelecer o mapeamento do dado ao seu formato, por isso devemos utilizar as funções de conversão implicatas. Esta conversão pode parecer estranha ou sem sentido, mas na verdade não é basta analisar a forma com que os valores estão sendo informados, o que força ainda mais o SQL Server a entender que estamos trabalhando com uma fórmula matemática, como por exemplo: 2011-02-24, se analisarmos podemos dizer que estamos fazendo a seguinte equação: =2011-02-24 = 1985. A tabela 3 apresentada a seguir demonstra o resultado de cada valor inserido como DateTime mas convertido para Int. Ano 2011 2011 2011 2011 2011 Mês 2 2 2 2 2 Dia 24 25 26 27 28 Equação Resultados =2011-2-24 1985 =2011-2-25 1984 =2011-2-26 1983 =2011-2-27 1982 =2011-2-28 1981 Tabela 3 – Resultados da conversão de DateTime para Integer. Neste momento o que nos resta a fazer é realizar novamente a inserção dos dados no formato DateTime, mas fazendo uso da função Convert para que o mapeamento seja realizado de forma compatível com o formato desejado. O Código 4 apresentado abaixo demonstra como podemos fazer esta nova inserção. Código 4 – Inserindo o segundo bloco de registros, utilizando o formato DateTime e função Convert. Insert Into PRODUTOS Values('Biscoito',Convert(DateTime,2011-02-24)); Insert Into PRODUTOS Values('Biscoito', Convert(DateTime,2011-02-25)); Insert Into PRODUTOS Values('Biscoito', Convert(DateTime,2011-02-26)); Insert Into PRODUTOS Values('Biscoito', Convert(DateTime,2011-02-27)); Insert Into PRODUTOS Values('Biscoito', Convert(DateTime,2011-02-28)); Go Após esta nova inserção de dados utilizando a função de conversão Convert nossos dados estão armazenados de forma correta respeitos seu formato e tipo de dados. Conclusão: Utilizar uma coluna do tipo SQL_Variant nos apresenta diversas vantagens quando temos a necessidade de armazenar informações em uma tabela sem se preocupar com o tipo de dados, formato ou se este dado é compatível com a coluna a qual esta sendo inserido. Por outro lado, nem sempre temos esta possibilidade e como podemos observar alguns tipos de dados, dentre eles, o Datetime necessitou de um tratamento antes de ser armazenado, fazendo-se uso de funções de conversão implicitas de dados Convert e Cast. Espero que este artigo posso ter respondido as dúvidas dos meus alunos e apresentado para você leitor como podemos trabalhar com dados distintos na mesma área de armazenado. Agradeço a sua atenção, nos vemos no próximo artigo, até mais.