MySQL procedure to PostgreSQL function - mysql

Migrating from MySQL to PostgreSQL.
DROP PROCEDURE IF EXISTS prepend;
DELIMITER $$
CREATE PROCEDURE prepend
(
IN inParam VARCHAR(255),
INOUT inOutParam INT
)
BEGIN
DECLARE z INT;
SET z = inOutParam + 1;
SET inOutParam = z;
SELECT inParam;
SELECT CONCAT('zyxw', inParam);
END;$$
DELIMITER ;
CALL prepend('abcdefg', #inOutParam);
The MySQL procedure call output is:
abcdefg
zyxwabcdefg
Verify the output here.
Here's the original MySQL code snippet.
The corresponding PostgreSQL function is not working. Please help.
DROP FUNCTION prepend;
CREATE OR REPLACE FUNCTION prepend
(
inParam VARCHAR,
INOUT inOutParam INT
)
AS $$
DECLARE z INT;
BEGIN
z := inOutParam + 1;
inOutParam := z;
SELECT inParam;
SELECT CONCAT('zyxw', inParam);
END; $$
LANGUAGE plpgsql;
SELECT prepend('abcdefg', 0);

PostgreSQL has not unbound queries - this technique is available on Sybase like databases (Sybase, MSSQL) and MySQL (MariaDB). Currently you can write function that can returns set of some values (tabular result) or returns scalar, composite or array (usual functions).
So most near design to your procedure is:
CREATE OR REPLACE FUNCTION prepend(inparam text)
RETURNS SETOF text AS $$
BEGIN
RETURN NEXT inparam;
RETURN NEXT 'zyxw' || inparam;
END;
$$ LANGUAGE plpgsql;
you can call this function with SELECT
SELECT * FROM prepend('abcdefg');
That is all what is possible. You cannot to set other out variables in this case.
This is common problem when you porting stored procedures from Sybase like systems that uses this technique to any other database (Postgres, Oracle, DB2, ...). The functionality of these systems cannot be mapped simply 1:1.
Because Postgres (plpgsql) has not unbounded queries support, then the syntax is prohibited.
BEGIN
SELECT 1;
END;
Has not sense there. Any result of plpgsql functions can be realized by using OUT variables or by using RETURN statement.

Related

Stored procedure to create a MaillingListCount

I am following sql in 10 minutes to learn "stored procedure"
#+BEGIN_SRC sql :engine mysql :dbuser org :database grocer
CREATE PROCEDURE MailingListCount (
ListCount OUT INTEGER )
IS
v_rows INTEGER;
BEGIN
SELECT COUNT(*) INTO v_rows
FROM Customers
WHERE NOT cust_email IS NULL;
ListCount := v_rows;
END;
#+END_SRC
#+RESULTS:
| |
it report error:
ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'OUT INTEGER )
IS
v_rows INTEGER' at line 2
Could you please provide any hints?
Couple of fixes:
The OUT comes before the parameter name
Remove the unnecessary IS
Declare the variables inside the BEGIN END block
Use SET when you assign variables
So:
CREATE PROCEDURE MailingListCount(OUT ListCount INTEGER )
BEGIN
declare v_rows INTEGER;
SELECT COUNT(*) INTO v_rows
FROM Customers
WHERE NOT cust_email IS NULL;
SET ListCount := v_rows;
END;
Usually it's easier to handle the procedure output from result set rather than OUT variables. The OUT variables are useful primarily on calls between procedures.
So if you plan to call the routine from application, use:
CREATE PROCEDURE MailingListCount()
BEGIN
SELECT COUNT(*) as 'Count'
FROM Customers
WHERE NOT cust_email IS NULL;
END;
First thing is the position of the OUT keyword. It should be before the Parameter name.
Then second one no need to create the variable v_rows to store the output and then finally assigning it back to the OUT parameter listCount.
If you want to check condition like email should not null then you should do something like WHERE cust_email IS NOT NULL instead of WHERE NOT cust_email IS NULL
Please refer below code for the reference :
DELIMITER $$
CREATE PROCEDURE `MailingListCount` (OUT listCount INTEGER)
BEGIN
SELECT COUNT(*) INTO listCount
FROM Customers
WHERE cust_email IS NOT NULL;
END$$
DELIMITER ;
You can use the different delimiter for the MySql stored procedures and functions.
MySql use ; as default delimiter so delimiters other than the default ; are typically used when defining functions, stored procedures, and triggers wherein you must define multiple statements. You define a different delimiter like $$ which is used to define the end of the entire procedure, but inside it, individual statements are each terminated by ;. That way, when the code is run in the mysql client, the client can tell where the entire procedure ends and execute it as a unit rather than executing the individual statements inside.
You can refer the https://dev.mysql.com/doc/refman/8.0/en/create-procedure.html to learn MySQL Stored procedure.

How to make use of variable on a function

I need to make a stored function:
This is my code
SELECT count(Dominio) FROM Thogar WHERE DOMINIO='%'
I need to make a stored function where I will write a letter between (U,C,R) and the function will replace the % in the previous code with the selected letter.
How can I do it? Thanks!
Got it working
CREATE FUNCTION `Buscar`(`param` CHAR(1))
RETURNS INT
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE res INT;
SELECT count(Dominio) INTO res FROM Thogar WHERE DOMINIO=param;
RETURN res;
END
Call buscar('C')
This should work:
DROP FUNCTION IF EXISTS myFunc;
DELIMITER $$
CREATE FUNCTION myFunc(
param CHAR(1)
) RETURNS INT;
BEGIN
DECLARE res INT;
SELECT count(Dominio) INTO res FROM Thogar WHERE DOMINIO=param;
RETURN res;
END;
$$
DELIMITER ;
If You want to make stored function with only one sql query,
I don't see any normal reason for it.
It will not give You performance gain.
How about simplification?
You can create view:
CREATE VIEW v_dominio_counters AS
SELECT Dominio, count(Dominio) AS counter FROM Thogar GROUP BY Dominio
And then use it:
SELECT counter FROM v_dominio_counters WHERE Dominio = 'U' LIMIT 1;
It will always keep for You ready to use counters that is handy when You have huge table.

MySQL: call procedure from function

Is it possible to call a procedure from a function in MySQL? I get the error "not allowed to return a result set from a function." I want the results of the procedure call to be inserted into the function variables the same as if I had done a SELECT INTO directly in the function.
The function is (simplified) defined as
create function my_function()
returns int deterministic
begin
declare some_parameter int;
declare the_result int;
call my_procedure(some_parameter, the_result)
return the_result;
end;
The procedure is (simplified) defined as:
create procedure my_procedure(in my_parameter int, out my_result int)
begin
select 1
from dual;
end;
In essence, no. Functions are looking for a datatype, not a record (which is what is returned from the procedure).

Getting errors when trying to create a PROCEDURE in mysql

I am trying to create a mysql stored procedure, but I get this error:
Script line: 2 Failed to CREATE PROCEDURE proc_test_bideep
The stored procedure code is:
DELIMITER $$
DROP PROCEDURE IF EXISTS `commun`.`insert_categorie` $$
CREATE PROCEDURE `commun`.`insert_categorie` (id_mere INT,
lib_categ VARCHAR(50),
id_categ_sup INT ,
categ_authInstantBuy INT)
BEGIN
SET #bg_mere := (SELECT categ_bg FROM categ_basic WHERE categ_id = id_mere);
#bg_mere+2,categ_level_bideep,categ_statut,categ_adult,categ_authSmallBid,categ_authBid,categ_authInstantBuy);
SELECT '1' AS code_retour; END IF;
ecetera.........
END $$
DELIMITER ;
a) You need to DECLARE any variables on the first lines of the procedure, including their datatype:
DECLARE bg_mere INT;
b) To fetch a value from the database into a variable, you use SELECT ... INTO syntax:
SELECT categ_bg INTO bg_mere FROM categ_basic WHERE categ_basic.categ_id = id_mere;
c) You have an END IF without the corresponding IF.
d) The closing END needs a semicolon (not BEGIN though), only then do you need a delimiter to finish the entire statement, and finally you should reset the delimiter back to normal:
BEGIN
# body of the stored procedure goes here
END;
$$
DELIMITER ;
Your parameters are missing the keyword IN such as: ...(IN id_mere INT, IN lib_categ ...). Also, you need to configure your OUT variable for #bg_mere in the initial parameter list such as (IN xxx, ..., OUT bg_mere VARCHAR/INT/WHATEVER).

Convert Oracle stored procedure using REF_CURSOR and package global variable to Postgresql or MySQL

This package uses two unique features of Oracle, REF_CURSOR and a package global variable. I would like to port the functionality from Oracle to Postgresql or 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;
Can you produce the equivalent code? for Postgresql? for MySQL?
Note: The Oracle code is thread safe. This is a key feature.
PostgreSQL 8.3
The problem in PostgreSQL is the lack of global (or package) variables, so that part has to be solved with a temp-table that is created first. The rest of it was quite easy.
If you are serious about porting the application over to PostgreSQL or MySQL, I would recommend you to not use global variables at all since they are bad practice when coding (according to me at least :))
But anyway, here is the code:
This has to exist before running the functions:
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));
The functions are being put in the schema tox to simulate a 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;
To test, just run this after everything have been created.
select * from tox.test('Test');
For mysql:
For ref_cursor you can just use a regular select in a procedure. Mysql has an implicit result set that is returned from stored procedure if you issue a select statement. See my answer here.
For the package global variable, you can put it in a table, but it appears from your code that it is a sequence, so it can be replaced with an auto_increment field. That should be pretty simple.
It would help if you can post the definition of your spool table in the question. Then I could probably provide you with exact code for mysql.
I have a hard time understanding several things in your code. It looks like you have a table with two sequences, but only one of them is truly an auto_increment column.
In mysql auto_increment is allowed only on one column in a table. have you considered making the other column a foreign key to an auto incremented column of another table?
The global variable is tricky, because mysql doesn't have them. I think the only resolution is to store it as a scalar in a table, and then tie your data to it with a foreign key.
Finally, returning a ref cursor is easy, as I pointed out in my previous answer. In the link provide (to a different answer) you can see a code sample.
Here's a solution tested with MySQL 5.1.30.
Regarding your requirement for thread-safety, the MySQL User Variable mechanism should help. This allows you to SET a variable whose state is limited to the current session. Other sessions can also create a variable by the same name, and keep a different value in it.
I assume by thread-safety you mean something like this -- session-scoped state. Because you can't really have more fine-grained thread-safe state in a database. Each thread of your application must have its own session to the database.
There are no packages in MySQL, so the user variable is global to the session. Another stored procedure that happens to use a variable of the same name will conflict.
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();