在一个继承的数据库中,我有两个当前感兴趣的表,records
和logins
. login
告诉我用户的位置以及他登录的地点和时间(包括进出时间)。
有趣的是登录会话和运行的应用程序之间没有关联标记。为了解决这个问题,我正在创建一个新表login_apps
并尝试使用一个函数来合并这两个表。
到目前为止我有:
drop function if exists ltop;
delimiter $$
create function ltop(id int, u varchar(10), s datetime, e datetime)
returns text
language sql
begin
declare pid varchar(40);
declare pname varchar(63);
declare my_return varchar(100);
declare cur1 cursor for (select usageProgramID
from records
where usageUser=u
and usageWhen>=s
and usageWhen<=e
and usageProgramID!="");
open cur1;
read_loop: loop
fetch cur1 into pid;
set pname = (select programName from Programs where programID=pid);
insert into login_apps(`sid`, `programName`) values(id, pname);
end loop read_loop;
close cur1;
return "finished";
end;$$
delimiter ;
它毫无问题地进入系统,并且插件在测试中按预期工作。我提出的问题是当它运行时:
select ltop("265548", "user", "2013-02-21 13:54:27", "2013-02-21 14:32:18");
我收到错误:
ERROR 1329 (02000): No data - zero rows fetched, selected, or processed
返回的数据不应该是“完成”的吗?
其次,我不太确定在logins
任何指针的每个值上运行这条路线的最佳方式是什么?
该错误与您无关
RETURN
;是这样的:当游标用完数据时,将抛出此错误。这可能是因为
SELECT
在游标中声明的查询没有找到任何行,或者因为它确实找到了行但是当您遍历它时,您读取了最后一行。如果游标用完数据时什么也没发生,您将如何退出read_loop
?您需要使用handler捕获错误,这将阻止过程的执行终止,并设置一个可用于跳出循环的变量。
在顶部,与其他变量:
然后,在声明之后但在打开游标之前:
在你的循环中:
这应该可以解决您眼前的问题。
其他观察:
你的函数修改数据库,所以在顶部,你应该有这个:
...虽然,实际上,将其编写为存储过程而不是函数更合适。MySQL 支持修改数据库的非确定性存储函数,用于时间点恢复或复制的二进制日志可能会出现潜在问题。
要一次处理所有数据而不是一个值,您可以使用嵌套游标——外部游标获取您输入该函数的任何数据,内部游标——在一个附加的
BEGIN
/END
块中执行您的逻辑在上面,我们得到了在外部游标中获取的值。这需要在内部块的每次迭代后将值重置done
回 0,以便另一个块不会过早停止。内部子查询是不必要的,因为游标可以声明为...
...但是,事实上,这整个功能似乎是不必要的。
在 SQL 中,通常最好告诉数据库需要做什么(“声明式”),而不是如何做(“过程式”)。
过于程序化的思考是一种 SQL 反模式。
我不知道“登录”表中的列名,因此您需要更正这些列名,但请考虑以下查询。据我所知,对于所有登录和所有匹配记录,它的作用与函数完全相同。
完毕。
为了将来维护“login_apps”表,“records”表上的触发器可以查找相关登录,并在每次“records”有插入或更新时将新条目写入“login_apps”......停止时间记录在“登录”表中,更新触发器可以读取“记录”并插入到“登录应用程序”中。
最后的想法是,如果您将程序 ID 而不是程序名称存储在 login_apps 表中,这可能是更好的设计……除非“records”表实际上有一个 auto_increment 主键,在这种情况下,这将是您应该的值真正存储在 login_apps 中。