I need to migrate Firebird database to MySQL, but I am stuck at the stored procedures more precisely I can’t find a solution for firebird’s FOR SELECT
in MySQL when I need to iterate through a result set.
For simplicity lets say I have this code in Firebird:
create or alter procedure get_services(
companyID: integer
returns (
personid integer,
personname varchar(100),
companyname varchar(50),
serviceID integer,
servicetimestamp timestamp,
description varchar(100)
as
for select distinct a.personid, a.personname, a.serviceID, b.companyname,
from persons a
join companies b on (a.companyid = b.companyid)
where b.companyId = :companyid into :personid, :personname, :companyname, :serviceID
do begin
select first 1 servicetimestamp, description
from services
where serviceId = :serviceId
order by servicetimestamp
into :servicetimestamp, :description;
suspend;
end
One solution I have found on internet for MySQL was through use of a cursor fetch with repeat loop and a temporary table in which I have inserted the results but with a cost of performance.
MySQL code:
BEGIN
DECLARE bDone INT;
DECLARE personid INT;
DECLARE personname VARCHAR(100);
DECLARE companyname VARCHAR(50);
DECLARE serviceID INT;
DECLARE servicetimestamp TIMESTAMP,
DECLARE description VARCHAR(100)
DECLARE curs
CURSOR FOR select distinct a.personid, a.personname, a.serviceID, b.companyname,
from persons a
join companies b on (a.companyid = b.companyid)
where b.companyId = companyid;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET bDone = 1;
DROP TEMPORARY TABLE IF EXISTS tblTemp;
CREATE TEMPORARY TABLE IF NOT EXISTS tblTemp (
personid INT,
personname VARCHAR(100),
companyname VARCHAR(50),
serviceID INTEGER,
servicetimestamp TIMESTAMP,
description VARCHAR(100)
);
OPEN curs;
SET bDone = 0;
REPEAT FETCH curs INTO personid,
personname,
companyname,
serviceID;
SELECT servicetimestamp, description
FROM services
WHERE serviceId = serviceId
ORDER by servicetimestamp
LIMIT 1
INTO servicetimestamp, description;
INSERT INTO tblTemp VALUES(personid, personname, companyname, serviceID, servicetimestamp, description);
UNTIL bDone END REPEAT;
CLOSE curs;
SELECT * FROM tblTemp;
END
The procedure in MySQL is 10 times slower compared with the same in Firebird and I have much more complicated procedures with nested loops and inserts that will take forever in MySQL using this method.
This simple procedure and some other selects I can solve with queries and sub queries, but as I have said there is much more complicated code and select query does not help.
Is something similar in MySQL as in Firebird ‘FOR SELECT’ that does not impact performance as using cursor?
2
Answers
This is how I would write the query if it had to be in a MySQL procedure:
No cursor, no temp table (except for implicit temp tables created by the CTE’s or the DISTINCT).
In fact, almost every case I see of developers using cursors in MySQL procedures is unnecessary and hurts performance. I use cursors only in cases where there are more complex conditional things to do in the loop based on the results of the cursor. Never use a cursor simply to prepare the result set.
Remember to name your procedure input parameter something distinct from any column names in the tables. MySQL gets confused between
companyid
andcompanyid
, because it cannot know which one you mean to be the column and which one you mean to be the procedure parameter. Column names are case-independent, socompanyId
andcompanyid
still are seen by MySQL as the same identifier. The ambiguity may be resolved since you used a qualified column name likeb.companyid
, but if you forget to do this, you end up with a tautologycompanyid = companyid
which compares the column to itself.P.S.: I don’t as a rule like to use MySQL stored procedures at all. They have no compiler, no debugger, no support for packages, no support for atomic deployment, and the language hinders productivity.
Or simply
?