Tag-Archive for » MySQL «

Tuesday, January 22nd, 2008 | Author: zedmaster

A pedidos segue como fazer uma Stored Procedure recursiva no Mysql.

É aconselhável ler estes tópicos antes:
- Cursores
- Stored Procedure

O nosso exemplo consiste em cadastrar vários elementos e fazer um relacionamento entre pais e filhos, a recursividade é usada para formar uma hierarquia, mostrando a geração de cada indiví­duo relacionado.
Uma função é considerada recursiva quando ela chama a si mesma diversas vezes.

Exemplo:

Geração ID Elemento Pai
1 1 A 0
2 2 AB 1
2 3 AC 1
2 4 AD 1
3 5 ADA 4
1 6 B 0
2 7 BA 6

A tabela tb_recursive precisa ter apenas 3 campos, o campo id_filho irá ser igual a 0 quando o elemento não tiver um pai.

CREATE TABLE  `test`.`tb_recursive` (
  `id` int(11) NOT NULL auto_increment,
  `nome` varchar(255) NOT NULL,
  `id_pai` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=12 DEFAULT CHARSET=latin1;

INSERT INTO `tb_recursive` VALUES (1, 'A', 0);
INSERT INTO `tb_recursive` VALUES (2, 'AB', 1);
INSERT INTO `tb_recursive` VALUES (3, 'AC', 1);
INSERT INTO `tb_recursive` VALUES (4, 'AD', 1);
INSERT INTO `tb_recursive` VALUES (5, 'B', 0);
INSERT INTO `tb_recursive` VALUES (6, 'BA', 5);
INSERT INTO `tb_recursive` VALUES (7, 'BB', 5);
INSERT INTO `tb_recursive` VALUES (8, 'C', 0);
INSERT INTO `tb_recursive` VALUES (9, 'ADA', 4);
INSERT INTO `tb_recursive` VALUES (10, 'ADB', 4);
INSERT INTO `tb_recursive` VALUES (11, 'ADBA', 10);

A pergunta básica que se baseia o algoritmo da SP recursiva é: Sendo esse pai X, quem são os filhos dele?
Esta pergunta é feita para cada filho desse pai, e assim por adiante, até que não tenhamos mais descendentes. É importante identificar a geração desse pai para que se possa organizar a hierarquia.

Para facilitar, iniciamos a criação de uma SP que iniciará a busca dos filhos:

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`sp_recursive_start`$$
CREATE PROCEDURE `test`.`sp_recursive_start` (IN in_id INT)
BEGIN
	DECLARE v_geracao INT;
	SET v_geracao = 1;
	SET @@max_sp_recursion_depth = 10;

	DROP TEMPORARY TABLE IF EXISTS tmp_recursive;
	CREATE TEMPORARY TABLE tmp_recursive (
					geracao INT,
					id int,
					nome VARCHAR(255)
	)ENGINE=MyISAM DEFAULT CHARSET=latin1;

	INSERT INTO tmp_recursive (geracao,id,nome)
	SELECT v_geracao, id, nome FROM tb_recursive WHERE id = in_id;

	CALL sp_recursive(in_id, v_geracao);

	SELECT * FROM tmp_recursive;

END$$

DELIMITER ;

Iremos criar uma tabela temporária para armazenar os resultados da hierarquia dos elementos, e exibi-los depois da pesquisa.

Segue abaixo o código da SP recursiva:

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`sp_recursive`$$
CREATE PROCEDURE `test`.`sp_recursive` (IN in_id INT, IN in_geracao INT )
BEGIN

	DECLARE done BOOLEAN DEFAULT FALSE;
	DECLARE v_nome VARCHAR(255);
	DECLARE v_id_filho INT;
	DECLARE v_geracao INT;

	DECLARE cursor_a CURSOR FOR
		SELECT id,nome FROM tb_recursive WHERE id_pai = in_id;		

	DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;

	OPEN cursor_a;
	REPEAT FETCH cursor_a INTO  v_id_filho,v_nome;

		IF NOT done THEN
			INSERT INTO tmp_recursive (geracao,id,nome) VALUE(in_geracao+1, v_id_filho, v_nome);
			CALL sp_recursive (v_id_filho, in_geracao+1);
		END IF;

		UNTIL done END REPEAT;
	CLOSE cursor_a;

END$$

DELIMITER ;

Caso o elemento não contenha filhos a SP é encerrada e volta para quem a chamou.

Para executar a SP use:

CALL sp_recursive_start(1);

Arquivo contendo todos os comandos recursive.zip

Category: Informática, MySQL  | Tags: ,  | 9 Comments
Friday, August 31st, 2007 | Author: admin

Eu descobri um erro quando estava fazendo o meu script em PHP usando o conector PDO.
(I’ve found a bug when working on a PHP script using a PDO connector.)

Quando fiz uma Stored Procedure usando o recurso PREPARE dentro, ocorreu um problema com o resultado do meu script em PHP.
(When I made a Store Procedure using the PREPARE statement something messed up the result im my PHP script.)

Config:

System: Linux Debian 2.6.21-2-486
Apache: Apache 2.0 Handler
PHP: Version 5.2.3-1+b1
MySQL: 5.0.45

Seguem abaixo os códigos:
(The code is shown below:)

Estrutura da tabela (Table Structure)

CREATE TABLE `tb_user` (
  `cd_user` int(11) NOT NULL auto_increment,
  `login` varchar(32) NOT NULL,
  `password` varchar(32) NOT NULL,
  PRIMARY KEY  (`cd_user`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;

--
-- INSERT tb_user
-- 

INSERT INTO `tb_user` VALUES (1, 'lula', 'lele');
INSERT INTO `tb_user` VALUES (2, 'php', 'cool');

Stored Procedure SELECT

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`sp_test_select`$$
CREATE PROCEDURE `test`.`sp_test_select` ()
BEGIN

	SELECT login,password FROM test.tb_user;

END$$

DELIMITER ;

Stored Procedure PREPARE

DELIMITER $$

DROP PROCEDURE IF EXISTS `test`.`sp_test_prepare`$$
CREATE PROCEDURE `test`.`sp_test_prepare` ()
BEGIN

	PREPARE s1 FROM 'SELECT login,password FROM test.tb_user';
	EXECUTE s1;
	DEALLOCATE PREPARE s1;

END$$

DELIMITER ;

Código PHP (PHP Code)

function result($sql)
{
    $db = new PDO('mysql:dbname=test;host=127.0.0.1', 'test','test');
    $stm = $db->prepare($sql);
    $stm->execute();

    echo "\n######## $sql #######\n";

    while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
        echo $row["login"]." - ".$row["password"]."\n";
    }   

    $db = null;
    $stm = null;
}   

result("SELECT * FROM tb_user");

result("CALL sp_test_select()");

result("CALL sp_test_prepare()");

Resultado (Result)

zed:/www/directdial/tmp# php pdo.php 

######## SELECT * FROM tb_user #######
lula - lele
php - cool

######## CALL sp_test_select() #######
lula - lele
php - cool

######## CALL sp_test_prepare() #######
 -
pcoolb_usertb_usepassworpassword
                                 ý¨ˆ”�½test -

Aqui esta o problema, quando chamo CALL sp_test_prepare() pelo PDO , ele não retorna corretamente os valores.
(Here lies the problem. When I call CALL sp_test_prepare() through PDO, it does not return the values as it should.)

Se você sabe como resolver este problema, por favor poste um comentário.
(If you know how to fix this bug, please comment on this thread.)

Resolução

Encontrei a resposta no seguinte link:
http://pecl.php.net/bugs/bug.php?id=7365

Category: PHP  | Tags: , , ,  | Leave a Comment
Thursday, August 16th, 2007 | Author: admin

Introdução

O conceito básico do relacionamento entre os objetos de dados (objetos extendidos de Zend_Db_Table_Abstract)
é o cascateamento de operações como SELECT, UPDATE e DELETE .

Consulte:

Objetivo

Criar um setor de uma empresa, que possa ter vários funcionários relacionados com ele (um para muitos).

Condições

Quando selecionar o setor, mostrar os seus funcionários, sem usar SELECT em tb_funcionarios, tentar usar o relacionamento para apresenta-los.
Quando um setor for apagado, fazer com que o sistema apague todos os funcionários ralacionados.

Solução

Relacionamento

Primeiro iremos criar as referências entre as objetos de dados chamados DoSetor e DoFuncionario.

Na classe DoSetor inserimos a sua dependencia com o DoFuncionario, usando o parametro $_dependentTables.

class DoSetor extends Zend_Db_Table_Abstract
{
	protected $_name = "tb_setor";

	protected $_primary = "cd_setor";

	protected $_dependentTables = array('DoFuncionario');
}

arquivo: DoSetor.php

Na classe DoFuncionario use o parametro $_referenceMap, passando um array com o nome da referencia as colunas(no caso cd_setor) e as operações permitidas (onDelete e onUpdate como self::CASCADE).

class DoFuncionario extends Zend_Db_Table_Abstract
{
	protected $_name = "tb_funcionario";

	protected $_primary = "cd_funcionario";

	protected $_referenceMap    = array(
			'DoSetor' => array(
				'columns'           => array('cd_setor'),
				'refTableClass'     => 'DoSetor',
				'refColumns'        => array('cd_setor'),
				'onDelete'          => self::CASCADE,
				'onUpdate'          => self::CASCADE
				)
			);
}

arquivo: DoFuncionario.php

Exibição dos Dados

Quando a consulta é pelo o objeto dependente usa-se doObjeto->FindParentRow(“nome_obj_relacao”), no nosso caso seleciona-se o
Funcionário e depois procura-se o seu Setor.
Quando é o objeto que possue dependentes usa-se doObjeto->findDoObjeto_Dependente, no nosso caso coloa-se o Funcionário
como parametro na chamada do método.

echo "Listando por Cliente:";
$funcionario = $doFuncionario->fetchAll();
foreach($funcionario as $currentFuncionario)
{
    $setor = $currentFuncionario->FindParentRow("DoSetor");

    echo "{$currentFuncionario->nm_funcionario} - {$setor->nm_setor}\n";
}

echo "Listando por Setor:";
$setor= $doSetor->fetchAll();
foreach($setor as $currentSetor)
{
    $funcionario = $currentSetor->findDoFuncionario();

    foreach($funcionario as $currentFuncionario)
    {
        echo "{$currentSetor->nm_setor} - {$currentFuncionario->nm_funcionario}\n";
    }
}

arquivo Ex0001Controller.php

Excluindo Registro

Ao apagar um setor, apaga-se todos os funcionários relacionados.

echo "Apagando setor: Financeiro.";
$setor = $doSetor->find($cd_setor);
$setor = $setor->current();
$setor->delete();

arquivo Ex0001Controller.php

Conclusão

A vantagem de usar este tipo de dependência é o fato de não se preocupar com a integridade dos dados, e dexando a responsabilidade para o ZF.
Quando ocorrerem relacionamentos mais complexos, estas referências facilitam a implementação das regras de negócio.