Em um banco de dados herdado, tenho duas tabelas de interesse atual records
e logins
. login
me diz a localização do usuário e onde e quando seu login ocorreu (horários de entrada e saída incluídos).
A parte divertida é que não há marca de correlação entre a sessão de login e os aplicativos executados. Para resolver o problema, estou criando uma nova tabela login_apps
e tentando usar uma função para combinar as duas tabelas.
Até agora eu tenho:
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 ;
Ele entra no sistema sem problemas e a inserção está funcionando conforme o esperado em um teste. O problema que estou apresentando é que, quando é executado como:
select ltop("265548", "user", "2013-02-21 13:54:27", "2013-02-21 14:32:18");
Eu recebo o erro:
ERROR 1329 (02000): No data - zero rows fetched, selected, or processed
Os dados a serem retornados não deveriam estar "concluídos"?
Em segundo lugar, não tenho muita certeza de qual seria a melhor maneira de executar isso em todos os valores de logins
quaisquer ponteiros para seguir essa rota.
Esse erro não é sobre o seu
RETURN
; é isso:Quando o cursor fica sem dados, esse erro é lançado. Isso pode ocorrer porque a
SELECT
consulta declarada no cursor não encontrou nenhuma linha ou porque encontrou linhas, mas conforme você iterava por ela, você leu além da última linha. Se nada acontecesse quando o cursor ficasse sem dados, como você escapariaread_loop
?Você precisa interceptar o erro com um manipulador , o que impedirá que a execução do procedimento seja encerrada e defina uma variável que possa ser usada para interromper o loop.
No topo, com as outras variáveis:
Então, depois de declarar, mas antes de abrir o cursor:
Dentro do seu loop:
Isso deve resolver seu problema imediato.
Outras observações:
Sua função modifica o banco de dados, portanto, no topo, você deve ter isto:
...embora, realmente, isso seria mais apropriadamente escrito como um procedimento armazenado em vez de uma função. O MySQL suporta funções armazenadas não determinísticas que modificam o banco de dados, você pode ter problemas potenciais com o log binário usado para recuperação pontual ou replicação.
Para processar todos os dados em vez de um valor de cada vez, você pode usar cursores aninhados - o cursor externo buscando quaisquer dados que você está alimentando nesta função e o cursor interno - dentro de um bloco
BEGIN
/ adicional fazendo a lógica que vocêEND
obtivemos acima com valores buscados no cursor externo. Isso requer a redefinição dodone
valor de volta para 0 após cada iteração do bloco interno para que o outro bloco não pare prematuramente.A subconsulta interna é desnecessária, pois o cursor pode ser declarado com...
...mas, na verdade, toda essa função parece ser desnecessária .
Em SQL, normalmente é melhor dizer ao banco de dados o que precisa ser feito ("declarativo"), em vez de como fazê-lo ("procedural").
Pensar de maneira muito processual é um antipadrão SQL .
Não sei os nomes das colunas na tabela 'login', então você precisará corrigi-los, mas considere a seguinte consulta. Tanto quanto eu posso dizer, ele faz exatamente o que a função faz, para todos os logins e todos os registros correspondentes.
Feito.
Para manutenção futura da tabela "login_apps", um gatilho na tabela "records" pode procurar o login relacionado e gravar as novas entradas em "login_apps" toda vez que "records" tiver uma inserção ou atualização... ou quando o o tempo de parada é registrado na tabela "login", um gatilho de atualização pode ler "registros" e inserir em "login_apps".
Pensamento final, provavelmente seria um design melhor se você armazenasse o ID do programa em vez do nome do programa na tabela login_apps ... a menos que a tabela "records" realmente tenha uma chave primária auto_increment, caso em que esse seria o valor que você deveria realmente armazene em login_apps.