Como outros já sugeriram, normalmente evitamos percorrendo um conjunto de resultados RBAR (linha por linha agonizante) principalmente por motivos de desempenho. Nós apenas não queremos adquirir o hábito de fazer um loop através de um conjunto de resultados. Mas isso não responde a pergunta que você fez.
Para responder à pergunta que você fez, aqui está um exemplo rudimentar de um programa armazenado no MySQL que usa um CURSOR para processar individualmente as linhas retornadas por uma consulta. O MySQL não suporta blocos anônimos, então a única maneira de fazer isso é em um programa armazenado no MySQL, como um PROCEDURE
DELIMITER $$
CREATE PROCEDURE loop_through_var_list
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE v_id INT DEFAULT NULL;
DECLARE csr_var_list CURSOR FOR SELECT id FROM var_list ORDER BY id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN csr_var_list;
get_id: LOOP
FETCH csr_var_list INTO v_id;
IF done = 1 THEN
LEAVE get_id;
END IF;
-- at this point, we have an id value in v_id, so we can do whatever
SET @s1 = CONCAT('SELECT ... WHERE id =''', v_id, ''' ...');
END LOOP get_id;
CLOSE csr_var_list;
END$$
DELIMITER ;
Para executar o procedimento:
CALL loop_through_var_list();
NOTAS:A sintaxe para processar um CURSOR no MySQL é um pouco diferente de outros bancos de dados.
Para obter o "looping", precisamos usar um
LOOP ... END LOOP
construir. Mas para evitar que esse loop seja executado para sempre, precisamos de uma instrução LEAVE que nos permita sair do loop.
Usamos um teste condicional para determinar quando sair. Neste exemplo, queremos sair depois de terminar de processar a última linha.
O
FETCH
vai lançar uma exceção quando não houver mais linhas a serem buscadas. Nós "capturamos" essa exceção em um CONTINUE HANDLER (por alguma razão misteriosa, os "handlers" têm as últimas coisas declaradas; o MySQL lança um erro se tentarmos declarar algo depois de um HANDLER (diferente de outro HANDLER).
Quando o MySQL lança a exceção "no more rows", isso aciona o código do manipulador. Neste exemplo, estamos apenas configurando uma variável (chamada
done
) para um valor. Como é um manipulador "continue", o processamento é iniciado novamente na instrução em que a exceção foi lançada, neste caso, será a instrução após o FETCH. Então, a primeira coisa que fazemos é verificar se estamos "feitos" ou não. Se estivermos "concluídos", saímos do loop e fechamos o cursor.
Caso contrário, sabemos que temos um
id
valor de var_list
armazenado em uma variável de procedimento chamada v_id
. Então agora podemos fazer o que quisermos. Parece que você deseja colocar algum texto SQL em uma variável definida pelo usuário (incluindo o valor de v_id no texto SQL, depois PREPARE, EXECUTE e DEALLOCATE PREPARE. Certifique-se de declarar o
v_id
variável com o tipo de dados apropriado, que corresponde ao tipo de dados do id
coluna em var_list
, acabei de assumir que é um INT. Quando chegamos ao final do loop, o MySQL "loops" de volta ao início do loop, e lá vamos nós novamente.
No corpo do loop, provavelmente você desejará CONCAT v_id no texto SQL que deseja executar. Parece que você já tem um controle sobre o PREPARE, DEALLOCATE prepare. Para teste, você pode querer adicionar uma cláusula LIMIT no SELECT na declaração do cursor e então fazer um simples SELECT v_id; no corpo, apenas para verificar se o loop está funcionando, antes de adicionar mais código.
ACOMPANHAMENTO
Eu queria mencionar outra abordagem alternativa para a tarefa, ou seja, executar uma série de instruções com base em um modelo, substituindo os valores fornecidos por uma única instrução SQL select...
Por exemplo, se eu tivesse este modelo:
SELECT *
INTO OUTFILE '/tmp/[email protected]'
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
FROM data
WHERE id = @ID
ORDER BY 1
e eu precisava substituir as ocorrências de @ID por um valor de id específico de uma lista retornada de uma instrução SELECT, por exemplo.
SELECT id
FROM var_list
WHERE id IS NOT NULL
GROUP BY id
Eu provavelmente não usaria um programa armazenado MySQL com um loop CURSOR, eu usaria uma abordagem diferente.
Eu faria uso de uma instrução SELECT para gerar um conjunto de instruções SQL que poderiam ser executadas. Assumindo
id
é do tipo inteiro, eu provavelmente faria algo assim:SELECT CONCAT(' SELECT *
INTO OUTFILE ''/tmp/orders_',s.id,'.csv''
FIELDS TERMINATED BY '','' ENCLOSED BY ''"''
LINES TERMINATED BY ''\n''
FROM data
WHERE id = ',s.id,'
ORDER BY 1;') AS `stmt`
FROM ( SELECT v.id
FROM var_list v
WHERE v.id IS NOT NULL
GROUP BY v.id
) s
ORDER BY s.id
Para cada valor de
id
retornado de s
, a instrução retorna o texto de uma instrução SQL SELECT que pode (e precisa) ser executada. Capturar isso em um arquivo de texto me daria um script SQL que eu poderia executar.