Converta procedimento armazenado Oracle usando ref_cursor e empacotá variável global para PostgreSQL ou MySQL

StackOverflow https://stackoverflow.com/questions/489441

Pergunta

Este pacote utiliza duas características únicas da Oracle, ref_cursor e uma variável global pacote. Gostaria de porta a funcionalidade do Oracle para o PostgreSQL ou MySQL.

PACKAGE tox IS
    /*=======================*/
        g_spool_key spool.key%TYPE := NULL;
        TYPE t_spool IS REF CURSOR RETURN spool%ROWTYPE;
    /*=======================*/
        PROCEDURE begin_spool;
    /*=======================*/
        PROCEDURE into_spool
            (
            in_txt IN spool.txt%TYPE
            );
    /*=======================*/
        PROCEDURE reset_spool;
    /*=======================*/
        FUNCTION end_spool
            RETURN t_spool;
    /*=======================*/
        FUNCTION timestamp
            RETURN VARCHAR2;
    /*=======================*/
    END tox;

PACKAGE BODY tox
    IS
    /*========================================================================*/
    PROCEDURE begin_spool
        AS
        /*=======================*/
        BEGIN
        /*=======================*/
            SELECT
                key.NEXTVAL
            INTO
                g_spool_key
            FROM
                DUAL;
        /*=======================*/
        END begin_spool;
    /*========================================================================*/
    PROCEDURE into_spool
        (
        in_txt IN spool.txt%TYPE
        )
        AS
        /*=======================*/
        BEGIN
        /*=======================*/
            INSERT INTO
                spool
            VALUES
                (
                g_spool_key,
                in_txt,
                seq.NEXTVAL
                );
        /*=======================*/
        END into_spool;
    /*========================================================================*/
    PROCEDURE reset_spool
        AS
        /*=======================*/
        BEGIN
        /*=======================*/
            DELETE
                spool
            WHERE
                key = g_spool_key;
            COMMIT;
            begin_spool;
        /*=======================*/
        END reset_spool;
    /*========================================================================*/
    FUNCTION end_spool
        RETURN t_spool
        AS
        v_spool t_spool;
        /*=======================*/
        BEGIN
        /*=======================*/
            COMMIT;
            OPEN v_spool FOR
                SELECT
                    *
                FROM
                    spool
                WHERE
                    key = g_spool_key
                ORDER BY
                    seq;
            RETURN v_spool;
        /*=======================*/
        END end_spool;
    /*========================================================================*/
    FUNCTION timestamp
        RETURN VARCHAR2
        AS
        /*-----------------------*/
        v_result VARCHAR2(14);
        /*=======================*/
        BEGIN
        /*=======================*/
            SELECT
                TO_CHAR(SYSDATE,'YYYYMMDDHH24MISS')
            INTO
                v_result
            FROM
                DUAL;
            RETURN v_result;
        /*=======================*/
        END timestamp;
    /*========================================================================*/
    END tox;

Você pode produzir o código equivalente? para o PostgreSQL? para o MySQL?

Nota: O código Oracle é thread-safe. Esta é uma característica fundamental.

Foi útil?

Solução

PostgreSQL 8.3

O problema no PostgreSQL é a falta de variáveis ??globais (ou pacote), de modo que parte tem que ser resolvido com uma temp-tabela que é criada pela primeira vez. O resto era bastante fácil.

Se você é sério sobre a portabilidade do aplicativo até PostgreSQL ou MySQL, eu recomendo que você não usar variáveis ??globais em tudo desde que eles são uma prática ruim quando codificação (de acordo comigo, pelo menos:))

Mas de qualquer maneira, aqui está o código:

Isto tem de existir antes de executar as funções:

create table spool (key integer, txt varchar(2048), seq integer);
create sequence s_key;
create sequence s_seq;
create schema tox;
create temp table globals (name varchar(10), value varchar(100), primary key(name));

As funções estão sendo colocados no tox esquema para simular um pacote.

create or replace function tox.get_variable(var_name varchar) returns varchar as $$
declare 
    ret_val varchar(100);
begin
    select value into ret_val from globals where name = var_name;
    return ret_val;
end
$$ language plpgsql;

create or replace function tox.set_variable(var_name varchar, value anyelement) returns void as $$
begin
    delete from globals where name = var_name;
    insert into globals values(var_name, value);
end;
$$ language plpgsql;


create or replace function tox.begin_spool() returns integer as $$
begin
    perform tox.set_variable('key', nextval('s_key')::varchar);
    return tox.get_variable('key'); 
end;
$$ language plpgsql;

create or replace function tox.reset_spool() returns integer as $$
begin
    delete from spool where key = tox.get_variable('key')::integer;
    return tox.begin_spool();
end;
$$ language plpgsql;

create or replace function tox.into_spool(in_txt spool.txt%TYPE) returns void as $$
begin
    insert into spool values(tox.get_variable('key')::integer, in_txt, nextval('s_seq'));
end;
$$ language plpgsql;



create or replace function tox.end_spool(refcursor) returns refcursor as $$
declare
begin
    open $1 for select * from spool where key = tox.get_variable('key')::integer order by seq;
    return $1;
end;
$$ language plpgsql;



create or replace function tox.test(txt varchar(100)) returns setof spool as $$
declare 
    v_spool_key integer;
    cnt integer;
begin
    v_spool_key = tox.begin_spool();

    for cnt in 1..10 loop
    perform tox.into_spool(txt || cnt); 
    end loop;

    perform tox.end_spool('spool_cursor');
    return query fetch all from spool_cursor;
end;
$$ language plpgsql;

Para testar, basta executar este depois de tudo ter sido criado.

select * from tox.test('Test');

Outras dicas

Para mysql:

  1. Para ref_cursor você pode apenas usar um seleto regular em um procedimento. Mysql tem um conjunto de resultados implícito que é retornado do procedimento armazenado se você emitir uma instrução SELECT. Veja a minha resposta aqui .
  2. Para a variável global pacote, você pode colocá-lo em uma mesa, mas parece do seu código que é uma seqüência, assim que pode ser substituído com um campo auto_increment. Isso deve ser bastante simples.

Seria bom se você pode enviar a definição de sua tabela de spool na pergunta. Então eu provavelmente poderia fornecê-lo com código exato para mysql.

Eu tenho dificuldade em entender várias coisas no seu código. Parece que você tem uma tabela com duas seqüências, mas apenas um deles é verdadeiramente uma coluna auto_increment.

Em auto_increment mysql é permitido apenas em uma coluna em uma tabela. você já pensou em fazer outra coluna uma chave estrangeira para uma auto coluna incrementado de outra tabela?

A variável global é complicado, porque o MySQL não tê-los. Eu acho que a única solução é armazená-lo como um escalar em uma tabela, e em seguida, amarre os seus dados a ele com uma chave estrangeira.

Finalmente, retornando um cursor ref é fácil, como indiquei na minha resposta anterior. No link fornecer (a uma resposta diferente), você pode ver um exemplo de código.

Aqui está uma solução testada com o MySQL 5.1.30.

Quanto à sua exigência de thread-segurança, o MySQL Usuário mecanismo variável deve ajudar. Isso permite que você SET uma variável cujo estado é limitado à sessão actual. Outras sessões também pode criar uma variável com o mesmo nome, e manter um valor diferente nele.

Eu assumo por thread-safety você quer dizer algo como isto - Estado com escopo de sessão. Porque você não pode realmente ter mais Estado thread-safe de grão fino em um banco de dados. Cada segmento da sua aplicação deve ter a sua própria sessão no banco de dados.

Não há pacotes no MySQL, então a variável de usuário é global para a sessão. Outro procedimento armazenado que acontece de usar uma variável com o mesmo nome entrem em conflito.

CREATE TABLE spool (
  `key` INT,
  txt   VARCHAR(2048),
  seq   INT AUTO_INCREMENT PRIMARY KEY
);

CREATE TABLE spool_key (
  `key` INT AUTO_INCREMENT PRIMARY KEY
);

DELIMITER $$
CREATE PROCEDURE begin_spool ()
BEGIN
  DELETE FROM spool_key;
  INSERT INTO spool_key (`key`) VALUES (DEFAULT);
  SET @sp_key = LAST_INSERT_ID();
END $$

CREATE PROCEDURE into_spool(IN in_txt VARCHAR(2048))
BEGIN
  INSERT INTO spool (`key`, txt, seq) VALUES
    (@sp_key, in_txt, DEFAULT);
END $$

CREATE PROCEDURE reset_spool()
BEGIN
  DELETE spool FROM spool JOIN spool_key USING (`key`);
  CALL begin_spool();
END $$

CREATE PROCEDURE end_spool()
BEGIN
  SELECT *
  FROM spool JOIN spool_key USING (`key`)
  ORDER BY seq;
END $$
DELIMITER ;

CALL begin_spool();
CALL into_spool('now is the time');
CALL into_spool('for all good men');
CALL end_spool();
CALL reset_spool();
CALL into_spool('to come to the aid');
CALL into_spool('of their country');
CALL end_spool();

DROP FUNCTION IF EXISTS fmt_timestamp;
CREATE FUNCTION fmt_timestamp() RETURNS CHAR(14)
RETURN DATE_FORMAT(SYSDATE(), '%Y%m%d%H%i%s');

SELECT fmt_timestamp();
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top