我需要将 Firebird 数据库迁移到 MySQL,但我被困在存储过程中,更准确地说,当我需要迭代结果集时,我无法在 MySQL 中找到 Firebird
FOR SELECT
的解决方案。
为了简单起见,假设我在 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
我在互联网上找到的 MySQL 解决方案是使用带有重复循环的游标获取和一个临时表,我在其中插入了结果,但会降低性能。
MySQL代码:
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
MySQL 中的过程比 Firebird 中的过程慢 10 倍,而且我有更复杂的过程,带有嵌套循环和插入,使用这种方法在 MySQL 中将永远花费时间。
这个简单的过程和其他一些选择我可以通过查询和子查询来解决,但正如我所说,有更复杂的代码,选择查询没有帮助。
MySQL 中是否有与 Firebird 'FOR SELECT' 类似的东西,但不会像使用游标那样影响性能?
如果必须在 MySQL 过程中,这就是我编写查询的方式:
CREATE PROCEDURE get_services(IN in_companyid INT)
READS SQL DATA
BEGIN
WITH cte1 AS (
SELECT DISTINCT a.personid, a.personname, a.serviceID, b.companyname
FROM persons AS a
JOIN companies AS b USING (companyid)
WHERE b.companyid = in_companyid
),
cte2 AS (
SELECT cte1.*, s.servicetimestamp, s.description,
ROW_NUMBER() OVER (ORDER BY s.servicetimestamp) AS rownum
FROM cte1
JOIN services AS s USING (serviceID)
)
SELECT * FROM cte2 WHERE rownum = 1;
END
没有游标,没有临时表(由 CTE 或 DISTINCT 创建的隐式临时表除外)。
事实上,我看到的几乎所有开发人员在 MySQL 过程中使用游标的案例都是不必要的,并且会损害性能。我仅在基于游标结果的循环中需要执行更复杂的条件操作的情况下才使用游标。切勿仅使用游标来准备结果集。 请记住将过程输入参数命名为与表中任何列名称不同的名称。 MySQL 会混淆
companyid
和
companyid
,因为它无法知道哪一个是列,哪一个是过程参数。列名与大小写无关,因此 companyId
和 companyid
仍然被 MySQL 视为相同的标识符。由于您使用了像 b.companyid
这样的限定列名称,歧义可能会得到解决,但如果您忘记这样做,您最终会得到一个同义反复 companyid = companyid
,它将列与其自身进行比较。P.S.:我通常根本不喜欢使用 MySQL 存储过程。它们没有编译器,没有调试器,不支持包,不支持原子部署,并且语言阻碍了生产力。
INSERT INTO tbl VALUES(personid, personname, companyname,
serviceID, servicetimestamp, description)
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;
?