23 de março de 2012

Trabalhando com rotinas T-SQL no SQL Server

O SQL (Structured Query Language) é muito mas que apenas uma linguagem para manutenções num banco de dados. Ela não se restringe às quatro operações básicas (INSERT, DELETE, UPDATE e SELECT) ou àquelas funções para gerenciar tabelas ou transações. O SQL Server da Microsoft, por exemplo, implementa uma extensão do padrão SQL chamada T-SQL (Transact SQL) que permite construir funções complexas. Elas combinam queries com elementos típicos das linguagens de programação comuns, fornecendo uma sintaxe que inclui variáveis, loops e controle de fluxo.

Com essa linguagem, você pode construir Triggers, Stored Procedures, Functions ou simplesmente executar comandos pelo SQL Server Management Studio (função antigamente disponível no Query Analyser). Neste post, vou usar essa ferramenta para mostrar alguns dos recursos do T-SQL.

Para montarmos o exemplo básico, considere que vamos gerar uma lista de preços baseada nas informações contidas numa tabela chamada LISTAPR, criada de acordo com o comando a seguir:
-- Em SQL Server
create table LISTAPR
(
ID int not null,
PRODUTO varchar(25) null,
DESCRICAO varchar(255) null,
PRECO numeric(15,4) null,
DATA_VLD smalldatetime null
)

Como toda linguagem, o T-SQL possui comandos de declaração de variáveis para armazenar valores ao longo do programa. Basicamente, os tipos de dados permitidos são os mesmos que são válidos para criação de campos num tabela, com exceção dos tipos que representam BLOBs (text, ntext e image). A declaração em si é feita com a palavra chave DECLARE seguida de um arroba (@) acompanhando o nome da variável e, para finalizar, o tipo de dado que essa variável será capaz de armazenar. Exemplos:
DECLARE @produto varchar(25)
DECLARE @descricao varchar(255)
DECLARE @preco numeric(15,4)
DECLARE @linha varchar(300)
DECLARE @nro_regs int

SELECT @nro_regs = 0

A forma como os valores são recuperados do banco de dados nos obriga a declarar variáveis para os campos que pretendemos manipular. Assim, no trecho acima eu declarei uma variável para cada campo que será impresso. Como se vê também, o comando SELECT pode ser usado para atribuir manualmente valores para as variáveis.

Há um tipo especial de variável para ser usado na obtenção de um result set (lista de registros). O nome desse tipo é CURSOR e é através dele que estabelecemos quais dados serão recuperados do banco, qual o tipo de navegação será permitida nos registros (apenas avançar ou ir pra frente e pra trás) e também se será possível atualizar os dados. Para o cenário proposto neste post, precisaremos de uma coleção read-only que contenha todos os registros da tabela LISTAPR que estejam dentro da validade. A declaração abaixo atende esses requisitos:
DECLARE lista CURSOR SCROLL FOR
SELECT produto, descricao, preco
FROM LISTAPR
WHERE DATA_VLD > GETDATE() OR DATA_VLD IS NULL

O tipo da navegação é irrelevante aqui mas eu inclui a palavra SCROLL na declaração, possibilitando a navegação pelos registros em qualquer direção que queiramos. Outro detalhe é que eu estipulei quais campos eu quero recuperar. Isso é uma boa prática pois, conforme veremos adiante, deixa claro quais campos corresponderão a quais variáveis.

Agora, podemos abrir o cursor criado, navegar pela lista de registros obtidos, processar os dados conforme necessário e, finalmente, encerrar o cursor. O quadro abaixo traz o código com os passos citados:
OPEN lista
-- Primeiro registro
FETCH FIRST FROM lista INTO @produto, @descricao, @preco

WHILE @@FETCH_STATUS = 0
BEGIN
-- O processamento do registro atual será feito aqui

-- Próximo registro
FETCH NEXT FROM lista INTO @produto, @descricao, @preco
END

CLOSE lista
DEALLOCATE lista

Vamos destrinchar esse código. O comando OPEN apenas abre o cursor, submetendo a query ao banco de dados. O comando FETCH FIRST posiciona no primeiro registro e transfere para as variáveis os valores obtidos. Veja que a ordem das variáveis na cláusula INTO segue a mesma ordem estabelecida na cláusula SELECT.

A instrução WHILE é um laço tradicional, repetindo os comandos entre o BEGIN e o END enquanto o valor da expressão após o WHILE for verdadeira. Neste caso, @@FETCH_STATUS é uma variável nativa do T-SQL. Ela é afetada pela execução de comandos FETCH, assumindo valor 0 (zero) quando há um registro válido posicionado. Com isso, o comando FETCH NEXT, é executado enquanto ainda houver registros a recuperar no cursor.

Para encerrar, o cursor é fechado com o comando CLOSE e os recursos usados por ele são devolvidos ao sistema através do comando DEALLOCATE. Variáveis do tipo CURSOR são as únicas que requerem a desalocação explícita.

Ficou faltando a parte do processamento interno do laço. Como planejamos simplesmente imprimir os valores das variáveis, podemos usar o comando PRINT para isso. Ele aceita um texto como parâmetro, de modo que teremos que fazer a conversão dos tipos numéricos antes de imprimir.
SELECT @nro_regs = @nro_regs + 1, @linha = ''

SELECT @linha = @linha + @produto + REPLICATE (' ', 21 - LEN (@produto))
SELECT @linha = @linha + @descricao + REPLICATE (' ', 21 - LEN (@descricao))
SELECT @linha = @linha + STR (@preco, 10, 2)

PRINT @linha

Cada chamada ao PRINT gera uma nova linha na janela de saída do Management Studio, razão pela qual concateno os valores num único texto antes de imprimí-lo. Assim, cada produto terá sua própria linha. A ginástica com o REPLICATE e o LEN é para garantir que produto e descrição apareçam sempre com a mesma quantidade de caracteres, mantendo o resultado alinhado, mais fácil de ler. O STR converte o preço em um texto para a impressão.

É claro que, dada a simplicidade do exemplo, poderíamos ter resolvido o problema apenas executando a query. Mas o objetivo aqui era mostrar a flexibilidade da linguagem já que ela permite conter cursores aninhados, trabalhar com decisões de fluxo (instrução IF), cálculos complexos e outras interações. Aqui na ABC71, nós aproveitamos essa flexibilidade do T-SQL mesclada a queries que obtêm informações sobre tabelas do banco de dados para gerar certos tipos de codigo fonte auxiliares de nosso ERP.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.