Conversion de la procédure stockée Oracle à l'aide de REF_CURSOR et de la variable globale du package en Postgresql ou MySQL
-
20-08-2019 - |
Question
Ce package utilise deux fonctionnalités uniques d'Oracle, REF_CURSOR et une variable globale de package. Je souhaite transférer les fonctionnalités d'Oracle vers 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;
Pouvez-vous produire le code équivalent? pour Postgresql? pour MySQL?
Remarque: le code Oracle est thread-safe. C’est une caractéristique clé.
La solution
PostgreSQL 8.3
Le problème dans PostgreSQL est le manque de variables globales (ou de paquet), de sorte que cette partie doit être résolue avec une table temporaire créée en premier. Le reste était assez facile.
Si vous souhaitez sérieusement porter l'application sur PostgreSQL ou MySQL, je vous recommanderais de ne pas utiliser de variables globales, car elles sont une mauvaise pratique lors du codage (du moins selon moi:))
Mais enfin, voici le code:
Ceci doit exister avant d'exécuter les fonctions:
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));
Les fonctions sont mises dans le schéma tox pour simuler un package.
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;
Pour tester, exécutez cette opération après que tout ait été créé.
select * from tox.test('Test');
Autres conseils
Pour mysql:
- Pour ref_cursor, vous pouvez simplement utiliser une sélection régulière dans une procédure. Mysql a un jeu de résultats implicite qui est renvoyé à partir d'une procédure stockée si vous émettez une instruction select. Voir ma réponse ici .
- Pour la variable globale du package, vous pouvez la placer dans une table, mais il ressort de votre code qu'il s'agit d'une séquence et qu'elle peut donc être remplacée par un champ auto_increment. Cela devrait être assez simple.
Cela vous aiderait si vous pouviez publier la définition de votre table de spool dans la question. Ensuite, je pourrais probablement vous fournir le code exact pour mysql.
J'ai du mal à comprendre plusieurs choses dans votre code. Il semble que vous ayez une table avec deux séquences, mais une seule d’entre elles est vraiment une colonne auto_increment.
Dans mysql, auto_increment n’est autorisé que sur une colonne de la table. avez-vous envisagé de transformer l'autre colonne en clé étrangère en une colonne incrémentée automatiquement d'une autre table?
La variable globale est délicate, car mysql ne les a pas. Je pense que la seule solution est de le stocker en tant que scalaire dans une table, puis d'y attacher vos données avec une clé étrangère.
Enfin, il est facile de renvoyer un curseur de référence, comme je l'ai indiqué dans ma réponse précédente. Dans le lien fournir (à une réponse différente), vous pouvez voir un exemple de code.
Voici une solution testée avec MySQL 5.1.30.
En ce qui concerne vos exigences en matière de sécurité des threads, utilisateur de MySQL Un mécanisme variable devrait aider. Cela vous permet de SET
une variable dont l'état est limité à la session en cours. D'autres sessions peuvent également créer une variable du même nom et y conserver une valeur différente.
Je suppose que par thread-safety, vous entendez quelque chose comme ceci: état de session. Parce que vous ne pouvez pas vraiment avoir un état thread-safe plus précis dans une base de données. Chaque thread de votre application doit avoir sa propre session dans la base de données.
Il n'y a pas de paquet dans MySQL, donc la variable utilisateur est globale à la session. Une autre procédure stockée utilisant une variable du même nom sera en conflit.
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();