Desvendando o SQL - Parte IX - Procedimentos Armazenados
Galera Anapropéguas, nossa série
de artigos está de volta. Esta já é a nona parte, as anteriores mostraram um
pouco de alguns pontos da linguagem SQL, vimos operadores, funções de
agregação, criação de banco, tabelas e views, também vimos à alteração de
estruturas de tabelas, tipos de campo, consultas, estruturas condicionais e de
repetição, cursores e também conversão de valores. Procurei explanar até agora,
se não tudo, o que achei mais interessante para aqueles que ainda não conheciam
a linguagem SQL. Se por acaso ainda não viram os outros artigos, disponibilizo
os links no final desse artigo.
Para os que conhecem programação PROCEDIMENTOS ARMAZENADOS funcionam semelhantes a procedimentos como em qualquer linguagem. Para aqueles que não conhecimentos nos permitem:
Com uma STORED PROCEDURE podemos, por exemplo, retornar o valor de uma consulta de acordo com os parâmetros passados, chamar outros procedimentos, inserir valores em tabelas de acordo com determinada situação, dentro outras funcionalidades.
      
Um valor Default por ser usado para definir o parâmetro, inclusive valor NULL, isso permite que mesmo que o parâmetro não seja passado à execução da procedure é possível;
OUT ou OUTPUT indica que o parâmetro é de saída, ou seja, será devolvido ao final da execução da procedure. Lembrando que parâmetros do tipo text, ntext e image não podem ser usados com OUTPUT;
Parâmetros marcados como READONLY não podem ser atualizados ou modificados no corpo do procedimento. Se o tipo de parâmetro for um tipo com valor de tabela, deverá ser especificado READONLY;
O BEGIN (Inicio) e END (Fim) limitam o corpo da procedure, como também as instruções que estiver dentro do corpo podem ser limitadas pelo BEGIN/END;
Algumas práticas são recomendadas para a PROCEDURE e para não deixar o artigo muito extenso sugerimos consultar a página do MSDN.
 
Uma PROCEDURE pode fazer referencia a tabelas não criadas, porque no momento da criação somente a sintaxe é verificada, somente no momento da execução os objetos referenciados são resolvidos.
Como estamos trabalhando com um usuário com permissões de banco não veremos a dificuldade na execução da PROCEDURE, mas com um banco com vários usuários as procedures somente serão executadas com permissão.
Execução
Para uma procedure ser executada colocamos os comandos EXECUTE ou EXEC antes do nome da PROCEDURE:
EXECUTE
<@Param1, @Param2,...> 
-- Or
EXEC
<@Param1, @Param2,...> 
Criando um procedimento simples;
CREATE PROCEDURE PRProduto
Mais de um conjunto de resultados;
      
Relatório de produtos. Lembra quando fizemos um cursor que mostrava um relatório com os produtos dos movimentos fechados? Agora vamos criar uma procedure para executar esse relatório sempre que necessário, só que agora passando alguns parâmetros.
      
          
              
Hoje vamos falar um pouco sobre
STORED PROCEDURES ou Procedimentos Armazenados, sua utilidade e como usá-lo,
aproveitem bem e boa leitura.
PROCEDIMENTOS ARMAZENADOS
Para os que conhecem programação PROCEDIMENTOS ARMAZENADOS funcionam semelhantes a procedimentos como em qualquer linguagem. Para aqueles que não conhecimentos nos permitem:
- Colocar parâmetros de entrada e saída;
- Conter instruções que podem executar algumas operações no banco;
- Retornar um status de execução;
Com uma STORED PROCEDURE podemos, por exemplo, retornar o valor de uma consulta de acordo com os parâmetros passados, chamar outros procedimentos, inserir valores em tabelas de acordo com determinada situação, dentro outras funcionalidades.
Diretrizes para Procedimentos Armazenados
Alguns pontos devem ser seguidos,
comecemos pela sintaxe de criação, que segue o mesmo padrão dos anteriores
objetos criados no banco, vejamos com criar uma PROCEDURE, lembre que depois de
criada, não é possível criar outra com o mesmo nome, portanto caso precisa
fazer alguma alteração é só trocar o CREATE pelo ALTER.
CREATE { PROC |
PROCEDURE }   
      <lista
de parâmetros> 
      @PARAM1 TIPO,
      @PARAM2
TIPO
      [
= default ] [ OUT | OUTPUT | [READONLY]
AS
BEGIN
END
GO
Nome
Lembre-se de utilizar um nome
funcional para procedure, por padrão vamos utilizar um prefixo “PR” seguido de
nome relativo à funcionalidade da Procedure.
Parâmetros
A lista de parâmetros segue
também determinadas regras a serem seguidas.
Sempre utilize no começo do
parâmetro o caractere ‘@’ seguido do nome escolhido;
Todos os tipos de dados
Transact-SQL podem ser usados como parâmetros (Desvendando
o SQL - Parte II);
Um valor Default por ser usado para definir o parâmetro, inclusive valor NULL, isso permite que mesmo que o parâmetro não seja passado à execução da procedure é possível;
OUT ou OUTPUT indica que o parâmetro é de saída, ou seja, será devolvido ao final da execução da procedure. Lembrando que parâmetros do tipo text, ntext e image não podem ser usados com OUTPUT;
Parâmetros marcados como READONLY não podem ser atualizados ou modificados no corpo do procedimento. Se o tipo de parâmetro for um tipo com valor de tabela, deverá ser especificado READONLY;
O BEGIN (Inicio) e END (Fim) limitam o corpo da procedure, como também as instruções que estiver dentro do corpo podem ser limitadas pelo BEGIN/END;
Algumas práticas são recomendadas para a PROCEDURE e para não deixar o artigo muito extenso sugerimos consultar a página do MSDN.
Limitações
Apesar da extensa gama de instruções
que podem ser utilizadas no corpo da procedure, alguns comandos são restritos e
não podem ser usados. Vejam abaixo:
| 
CREATE AGGREGATE | 
CREATE SCHEMA | 
| 
SET SHOWPLAN_TEXT | 
CREATE DEFAULT | 
| 
CREATE ou ALTER TRIGGER | 
SET SHOWPLAN_XML | 
| 
CREATE ou ALTER FUNCTION | 
CREATE ou ALTER VIEW | 
| 
USE database_name | 
CREATE ou ALTER PROCEDURE | 
| 
SET PARSEONLY | 
CREATE RULE | 
| 
SET SHOWPLAN_ALL | 
Uma PROCEDURE pode fazer referencia a tabelas não criadas, porque no momento da criação somente a sintaxe é verificada, somente no momento da execução os objetos referenciados são resolvidos.
Segurança
Como estamos trabalhando com um usuário com permissões de banco não veremos a dificuldade na execução da PROCEDURE, mas com um banco com vários usuários as procedures somente serão executadas com permissão.
Execução
Para uma procedure ser executada colocamos os comandos EXECUTE ou EXEC antes do nome da PROCEDURE:
EXECUTE
GO
-- Or
EXEC
GO
EXEMPLOS
Mostraremos agora alguns exemplos
de criação e execução de procedures, a princípio sem parâmetros e em seguida
vamos incluindo alguns parâmetros e algumas outras opções de acordo com o que
já vimos nos artigos anteriores.
Criando um procedimento simples;
CREATE PROCEDURE PRProduto
AS
BEGIN
SELECT CD_REFERENCIA, DESC_PRODUTO, ST_PRODUTO, NR_SERIE                                        FROM TBPRODUTO
END
GO
Para executar é só digitar o
comando abaixo:                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                            
EXECUTE PRProduto
Mais de um conjunto de resultados;
CREATE PROCEDURE PRProdutoCategoria
AS
BEGIN
      SELECT CD_REFERENCIA, DESC_PRODUTO,
ST_PRODUTO, NR_SERIE                                        FROM TBPRODUTO
SELECT P.DESC_PRODUTO, C.DESC_CATEGORIA, S.DESC_SUB
FROM 
      TBPRODUTO         AS P INNER JOIN 
      TBSUBCATEGORIA    AS S ON S.CD_SUB = P.CD_SUB INNER JOIN 
      TBCATEGORIA       AS C ON C.CD_CATEGORIA = S.CD_CATEGORIA
WHERE
      C.DESC_CATEGORIA = 'BEBIDAS'
END
GO
Relatório de produtos. Lembra quando fizemos um cursor que mostrava um relatório com os produtos dos movimentos fechados? Agora vamos criar uma procedure para executar esse relatório sempre que necessário, só que agora passando alguns parâmetros.
CREATE PROCEDURE PRRelatProdMovimento (@ST_MOV CHAR(1), @DT_INIMOV DATETIME, @DT_FINMOV DATETIME) 
AS
BEGIN
      --O EXEMPLO A
SEGUIR MOSTRA COMO CURSORES PODEM SER ANINHADOS PARA PRODUZIR RELATÓRIOS
COMPLEXOS. O CURSOR INTERNO É DECLARADO PARA CADA PRODUTO.
      SET NOCOUNT ON;
      DECLARE 
            @CD_MOVIMENTO     INT,
            @DESC_PRODUTO     VARCHAR(50),
            @MENSAGEM         VARCHAR(200),
            @TP_MOVIMENTO     CHAR(1),
            @NR_DOCUMENTO     INT,
            @QTD              DECIMAL(10,4), 
            @UNIDADE          VARCHAR(5)
      PRINT '-------- RELATÓRIO DE PRODUTOS DO MOVIMENTO --------';
      DECLARE
MOV_CURSOR CURSOR FOR
      SELECT
CD_MOVIMENTO, TP_MOVIMENTO, NR_DOCUMENTO
      FROM TBMOVIMENTODIARIO
      WHERE 
            ST_MOVIMENTO
= @ST_MOV AND
            DTH_MOVIMENTO
BETWEEN @DT_INIMOV AND
@DT_FINMOV
      ORDER BY TP_MOVIMENTO;
      OPEN MOV_CURSOR
      FETCH NEXT FROM MOV_CURSOR 
      INTO @CD_MOVIMENTO,
@TP_MOVIMENTO, @NR_DOCUMENTO
      WHILE @@FETCH_STATUS
= 0
      BEGIN
            PRINT ' '
            SELECT @MENSAGEM = '----- PRODUTOS DO
MOVIMENTO: ' + 
                  @NR_DOCUMENTO + ' - ' +
                  CASE
@TP_MOVIMENTO --(E - ENTRADA, S - SAIDA, T -
TRANSFERENCIA, D - DEVOLUCAO)
                        WHEN 'E'
THEN 'ENTRADA'
                        WHEN 'S' THEN 'SAÍDA'
                        WHEN 'T' THEN 'TRANSFERÊNCIA'
                        WHEN 'D' THEN 'DEVOLUÇÃO'
                  END
            PRINT @MENSAGEM
            -- DECLARE AN INNER CURSOR BASED   
            -- ON VENDOR_ID FROM THE OUTER CURSOR.
            DECLARE PROD_CURSOR CURSOR FOR 
            SELECT
P.DESC_PRODUTO,
M.QTD_PRODUTO,
U.SGL_UNIDADE
            FROM 
                  TBMOVIMENTOPRODUTO
M INNER JOIN
                  TBPRODUTO
P ON P.CD_PRODUTO
= M.CD_PRODUTO INNER JOIN
                  TBUNIDADE U ON U.CD_UNIDADE = M.CD_UNIDADE
            WHERE
M.CD_MOVIMENTO =
@CD_MOVIMENTO
            OPEN
PROD_CURSOR
            FETCH NEXT
FROM PROD_CURSOR 
            INTO @DESC_PRODUTO,
@QTD, @UNIDADE
            IF @@FETCH_STATUS <> 0 
                  PRINT '         <>'      
            WHILE @@FETCH_STATUS
= 0
            BEGIN
                  SELECT @MENSAGEM = '         ' + @DESC_PRODUTO + ' - ' + CAST(@QTD AS VARCHAR(10)) + '-' + @UNIDADE
                  PRINT @MENSAGEM
                  FETCH NEXT FROM PROD_CURSOR 
                  INTO @DESC_PRODUTO,
@QTD, @UNIDADE
            END
            CLOSE PROD_CURSOR
            DEALLOCATE PROD_CURSOR
                  -- GET THE NEXT VENDOR.
            FETCH NEXT FROM MOV_CURSOR 
            INTO @CD_MOVIMENTO,
@TP_MOVIMENTO, @NR_DOCUMENTO
      END 
      CLOSE MOV_CURSOR;
      DEALLOCATE MOV_CURSOR;
END
Essa é somente uma das muitas
utilidades das Procedures, é bem interessante quando temos processos pesados e
queremos tirar isso de nossa aplicação e jogá-la no servidor. Nem sempre
precisando devolver um resultado, podemos usá-la somente pra executar
procedimentos. 
Tente também fazer outras
procedures com outros processamentos que deseje, e caso tenha alguma dúvida, é
só comentar que terei o prazer de responder. 
No próximo artigo falaremos um
pouco sobre as FUNCTION ou Funções no SQL. Não perca.
Até a próxima.


Comentários