terça-feira, 19 de agosto de 2008

Executando uma procedure dinâmica usando o PREPARE e o EXECUTE no MYSQL e PHP.


Olá galera,

 Em algum momento, tenho certeza que vocês precisaram ou precisarão fazer uma procedure dinâmica, seja para fazer um múltiplo insert, seja para uma lógica só sua, aquela que dentro de um mês você vai olhar e se perguntar: “Que zorra, essa zorra faz?” ou ainda “No que eu estava pensando quando eu fiz isso?”. Só abrindo um parenteses, quando eu procurei o material para escrever este artigo eu me fiz a primeira pergunta, fecha parenteses.

 Mas em fim a dica está documentada, dada e vamos a ela.

 Um problema que me deparei quando eu precisei desse mecanismo,  é que usando o comando mysql_query do php, não conseguia executar a procedure com o prepare e excute, onde o erro retornado é “PROCEDURE artigo.pdteste can't return a result set in the given context”. No entanto se fosse executado o mesmo código na linha de comando do mysql, funcionava normalmente. Perdi algumas valiosas horas para resolver, mas resolvi.

 O ambiente onde foi rodado esse exemplo:

  • sistema operacional : linux/windows 

  • php : PHP Versão 5.2.6 

  • apache :  Apache 2.2.8

  • mysql : 5.0.67-community-nt 

 Vamos resumir o que faremos, criaremos uma tela com um campo onde colocaremos o código que completa a procedure de inserção no banco, não necessariamente nessa mesma ordem.

 Iniciaremos pelo PHP, o qual não tem nenhum segredo e está devidamente comentado. Só gostaria de chamar a atenção para uma questão no código, estamos utilizando a mysqli_connect ao invés do mysql_connect.

Segue abaixo o código PHP (teste.php): 


<?php 

 

         // endereço da máquina que está o mysql

         $hostac = 'localhost';

        // usuário do mysql

        $userac = 'root';

        // senha do mysql

        $senhaac = '';

        // o banco a ser utilizado do mysql

        $bancoac = 'artigo';

        // pega o valor do campo

        $strsql = $_GET['campo'];

       

        // se o valor do campo tiver tamanho maior que 5 caracter tenta executar

        if (strlen($strsql)>5) {

                // cria a conexão com o banco

                $conn = @mysqli_connect($hostac, $userac, $senhaac,$bancoac);

                 

                // verifica se deu erro na conexão com o banco

        if (mysqli_connect_errno()){

                // mostra a mensagem de erro

                echo "sem conexão ".mysqli_connect_errno() ;

                // retorna, ou seja pára a execução do script, podia ser exit.

                return;

        }

       

        // monta o comando sql para executar a procedure usando o campo de texto da tela.

        $strsql = "CALL pdteste(\"".$strsql."\");";

       

        // mostra o comando a ser executado.

        echo $strsql."<br>";

               

                // Executa o comando sql, que retorna verdadeiro ou falso. Já verifica, se for verdadeiro mostra a mensagem de sucesso

                if(mysqli_multi_query($conn,$strsql)){

                       

                        // mensagem de sucesso

                        echo "Procedure executada com Sucesso";

                // se for falso, mostra a mensagem de erro.       

                }else{

                       

                        // mensagem de erro

                        echo "Erro na execução da procedure";

                        // pára de executar o script.

                        exit;

                }

               

                // fecha a conexão com o banco.

                mysqli_close($conn);

        }

       

        /*Abaixo segue o formulário html que é constituido de uma linha para mostrar as mensagens.

        * Outra linha com o campo onde será colocado a parte que completa o comando sql.

        * Outra com o botão para a execução da procedure.

        */

       

?> 

<html> 

        <head>

                <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

                <title>Incluir </title>

        </head>

 

        <body>

                <form actino="teste.php">

                           <table>

                                <tr>

                                         <td>

                                                  <?=$strsql;?>

                                          </td>

                                </tr>

                                <tr>

                                         <td>

                                                  <input type="text" name="campo" id="campo" value="" size="40"/>

                                          </td>

                                </tr>                 

                                <tr>

                                         <td>

                                                  <input type="submit" name="teste" id="teste" value="executar"/>

                                          </td>

                                </tr>    

                         </table>

                </form>

        </body>

</html> 

 A nossa tela está pronta, acrescentando uma observação na linha $strsql = "CALL pdteste(\"".$strsql."\");" nota-se a \ (barra invertida) que indica que o caractere que segue logo após a mesma deve ser interpreta como sendo parte da string do php, e não finalizando a string, dessa forma ao montar a query teremos as aspas(“) que indica uma string do sql e dentro das aspas teremos a apóstrofe (') que indica um campo varchar.

 Vamos para o mysql, vamos criar o banco, a tabela e a procedure, assim:


CREATE DATABASE `artigo`; 

 

USE artigo; 

 

DELIMITER $$ 

 

DROP TABLE IF EXISTS tabela $$ 

 

CREATE TABLE `artigo`.`tabela` ( 

  `idtabela` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,

  `nome` VARCHAR(45) NOT NULL,

  `numero` INTEGER UNSIGNED NOT NULL,

  PRIMARY KEY (`idtabela`)

ENGINE = InnoDB 

CHARACTER SET utf8 COLLATE utf8_unicode_ci;$$ 

 

DROP PROCEDURE IF EXISTS pdteste $$ 

 

CREATE PROCEDURE `pdteste`( 

        IN valoresparam TEXT

BEGIN 

 

   SET @novo = CONCAT("insert into tabela(nome, numero) values ", valoresparam);

 

   PREPARE stm FROM @novo;

 

   EXECUTE stm;

   

   DEALLOCATE PREPARE stm;

 

END $$ 

DELIMITER ; 

 Calma! Não deprime! vou explicar por partes.


 Cria a tabela

 CREATE DATABASE `artigo`;


 Define o banco que iremos trabalhar, que nada mais justo que seja o que acabamos de criar.

 USE artigo;


 O delimitador de comando padrão do mysql é o “;”, mas se utilizarmos esse, teremos vários erros pois ele vai tentar executar os comandos que estão no corpo da procedure. Portanto iremos trocar o delimitador para $$.

DELIMITER $$  


 Agora iremos criar a tabela dentro do banco que já criamos e selecionamos. Para uma questão de evitar problemas, se a tabela existir a gente apaga, só por garantia.

 DROP TABLE IF EXISTS tabela $$


 Ai sim criamo-a novamente.

 CREATE TABLE `artigo`.`tabela` (


 A tabela possui um campo id do tipo inteiro, auto-incremental e que é a chave da tabela, um campo nome e um campo numero

  `idtabela` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,

  `nome` VARCHAR(45) NOT NULL,

  `numero` INTEGER UNSIGNED NOT NULL,

  PRIMARY KEY (`idtabela`)


 Dizemos de que tipo é essa tabela

ENGINE = InnoDB 


 E dizemos qual o encode dessa tabela, basicamente é para caracteres acentuados.

CHARACTER SET utf8 COLLATE utf8_unicode_ci;$$ 


Até aqui temos o banco e a tabela que precisamos, e tá tudo lindo. A parti deste ponto será criada a procedure. Tal como para tabela, por questão de segurança se a procedure existir a gente apaga. 

DROP PROCEDURE IF EXISTS pdteste $$ 


 Cria-se a procedure novamente.

CREATE PROCEDURE `pdteste`( 


  Essa procedure só tem um parametro de entrada, que é justamente o complemento do código de inserção, o qual será digitado no campo do formulário.

       IN valoresparam TEXT


 Iniciaremos o corpo da procedure.

BEGIN 


 Criaremos uma variável na qual concatenaremos a parte do código de inserção com o parâmetro passado pela tela.

   SET @novo = CONCAT("insert into tabela(nome, numero) values ", valoresparam);


 Criaremos um statement usando o prepare com o código a ser executado.

   PREPARE stm FROM @novo;


 Executamos o statement.

   EXECUTE stm;


 Desalocamos o statement.

DEALLOCATE PREPARE stm; 


finalizamos a procedure. 

END $$ 


E por último restabelecemos o delimitador padrão 

DELIMITER ; 

E ai temos todo o circo armado para executar a procedure. É só publicar a página teste.php no servidor apache devidamente configurado e acessar.


Ao acessar a página, por exemplo http://localhost:81/teste.php, o conteúdo do campo deve ser algo como:

  • ('teste_web', 12)  

  •  ou ('teste1_web', 13) ,('teste2_web', 14) ,('teste3_web', 15) ,('teste4_web', 15)


“Se tudo deu certo e nada deu errado” no primeiro caso insere um registro, e no segundo insere vários registro de uma só vez no banco. Lembrando que esse tipo de inserte, da forma como foi mostrada só funciona no mysql.

Bem!! Para o momento é só, até um novo artigo




Referências:

http://www.kinghost.com.br/php/mysqli.multi-query.php
http://dev.mysql.com/doc/refman/5.1/en/sql-syntax-prepared-statements.html
http://rpbouman.blogspot.com/2005/11/mysql-5-prepared-statement-syntax-and.html