Estou escrevendo uma função PL/pgSQL que cria um cursor para uma consulta que preciso verificar se ela retorna algo.
O que estou fazendo é isso:
- Execute a consulta
- Verifique se ele retorna algo.
- Caso contrário, duplique um parâmetro e execute a consulta novamente.
- Caso contrário, retorne todas as linhas da consulta.
Achei que verificar se a consulta retornava algo com um cursor era a melhor escolha, pois é uma consulta muito longa (juntando 5 tabelas e muitas colunas) e SELECT ... INTO
não parecia certo, porque eu teria que criar uma TYPE
vez que o consulta tem colunas de uma tabela e uma coluna para um cálculo de distância.
O problema é que atualmente estou usando o cursor apenas para verificar se a consulta retornou algo dentro de um loop onde eu abro e fecho. Depois que a consulta retorna algo, saio do loop e retorno a consulta. Eu posso dizer imediatamente que esta é uma solução feia para o que eu preciso. Talvez alguém possa me ajudar com esta questão.
Aqui está um código que mostra o que estou fazendo atualmente.
CREATE FUNCTION store_distance(
latitude double precision,
longitude double precision,
radius double precision,
tries integer
)
RETURNS TABLE(
store_id store.id%type,
store_name store.name%type,
distance double precision
)
AS
$$
DECLARE
cur_stores CURSOR FOR
SELECT
store.id,
store.name,
get_distance(latitude, longitude, store.latitude, store.longitude) distance
FROM
store
WHERE
store.latitude BETWEEN (latitude - radius) AND (latitude + radius)
AND store.longitude BETWEEN (longitude - radius) AND (longitude + radius)
ORDER BY
distance ASC;
count int := 0;
storerow RECORD;
BEGIN
LOOP
IF count = tries THEN
EXIT;
END IF;
OPEN cur_stores;
FETCH cur_stores INTO storerow;
IF FOUND THEN
EXIT;
END IF;
radius := radius * 2;
count := count + 1;
CLOSE cur_stores;
END LOOP;
RETURN QUERY
SELECT
store.id,
store.name,
get_distance(latitude, longitude, store.latitude, store.longitude) distance
FROM
store
WHERE
store.latitude BETWEEN (latitude - radius) AND (latitude + radius)
AND store.longitude BETWEEN (longitude - radius) AND (longitude + radius)
ORDER BY
distance ASC;
END;
$$ LANGUAGE PLPGSQL;
Portanto, meu objetivo é fornecer coordenadas, raio e número de tentativas e tentar encontrar lojas nessa caixa de pesquisa. Se nenhuma loja for encontrada, dobro o raio e tento novamente até que algo seja retornado pela consulta ou até que o número de tentativas seja atingido.
A RETURN TABLE
parte é basicamente porque quero voltar a distância, então RETURN SETOF store
não adiantou.
Eu não acho que você precisa de um cursor aqui. Para encurtar seu código, você pode usar apenas uma visualização. Para melhorar o desempenho, uma visualização materializada deve levá-lo mais longe. O Postgres 9.3 possui recursos integrados, mas você mesmo pode implementá-lo facilmente em versões mais antigas.
Considere esta forma simplificada:
Fiz a função
STRICT
para desabilitar a entrada NULL, o que poderia resultar em um loop infinito.Observe como eu uso círculos com o operador "Contained" em
<@
vez de caixas . Pode-se supor que os cálculos sejam um pouco mais caros do que com caixas, mas isso dificilmente importa quando você suporta sua consulta com um índice GiST como:Você pode considerar armazenar lat / lon
point
para começar e substituir o índice em uma expressão por um mais simples na coluna. Funciona de qualquer maneira, apenas certifique-se de que a consulta corresponda ao índice para que seja usado. Grande diferença para grandes mesas.Você pode estar interessado nesta resposta intimamente relacionada ao SO que postei no ano passado - com muito mais explicações e links.