¿Existen métodos alternativos para decir 'siguiente' en un bucle pl / sql for?
Pregunta
Así que tengo un ciclo for que procesa una lista de ID y tiene algunas cosas bastante complejas que hacer. Sin entrar en todos los detalles feos, básicamente esto:
DECLARE l_selected APEX_APPLICATION_GLOBAL.VC_ARR2; ...snip... BEGIN -- get the list ids l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST); -- process each in a nice loop FOR i IN 1..l_selected.count LOOP -- do some data checking stuff... -- here we will look for duplicate entries, so we can noop if duplicate is found BEGIN SELECT county_id INTO v_dup_check FROM org_county_accountable WHERE organization_id = :P4_ID AND county_id = v_county_id; -- NEXT;! NOOP;! but there is no next! EXCEPTION WHEN NO_DATA_FOUND THEN dbms_output.put_line('no dups found, proceeding'); END; -- here we have code we only want to execute if there are no dupes already IF v_dup_check IS NULL THEN -- if not a duplicate record, proceed... ELSE -- reset duplicate check variable v_dup_check := NULL; END; END LOOP; END;
La forma en que normalmente manejo esto es seleccionando un valor y luego envolviendo el siguiente código en una comprobación de instrucción IF para asegurarme de que la variable de verificación duplicada sea NULL. Pero es molesto. Solo quiero poder decir SIGUIENTE; o NOOP; o algo. Especialmente porque ya tengo que atrapar la excepción NO_DATA_FOUND. Supongo que podría escribir una carta a Oracle, pero tengo curiosidad de cómo otros manejan esto.
También podría incluir esto en una función, pero estaba buscando algo un poco más limpio / simple.
Solución
También es posible contar el número de filas (ver Pourquoi Litytestdata) pero también puede hacer lo que quiera hacer en el bloque when_no_data_found excepcion
.
declare
l_selected apex_application_global.vc_arr2;
l_county_id org_county_accountable.count_id%type;
begin
l_selected := apex_util.string_to_table(:p4_select_lst);
for i in l_selected.first..l_selected.last loop
begin
select count_id
into l_county_id
from org_county_accountable
where organization_id = :p4_id
and county_id = v_county_id;
exception
when no_data_found then
-- here we have code we only want to execute if there are no dupes already
-- if not a duplicate record, proceed...
end;
end loop;
end;
Otros consejos
Oracle 11g agrega un estilo C " continuar " construcción de bucle a PL / SQL, que suena sintácticamente como lo que estás buscando.
Para sus propósitos, ¿por qué no simplemente eliminar los duplicados antes de ingresar al ciclo? Esto se puede hacer consultando l_selected usando una función de tabla y luego filtrando los registros que no desea en lugar de iterar sobre cada valor. Algo así como ...
declare
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
cursor no_dups_cur (p_selected APEX_APPLICATION_GLOBAL.VC_ARR2) is
select * from (
select selected.*,
count(*) over (partition by county_id) cnt -- analytic to find counts grouped by county_id
from table(p_selected) selected -- use table function to treat VC_ARR2 like a table
) where cnt = 1 -- remove records that have duplicate county_ids
;
begin
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
for i in no_dups_cur(l_selected) loop
null; -- do whatever to non-duplicates
end loop;
end;
Simplemente sustituya la lógica para determinar un "duplicado" con la suya (no tenía suficiente información de su ejemplo para realmente responder esa parte)
En lugar de atrapar NO_DATA_FOUND
, ¿qué tal si SELECCIONA el número de entradas coincidentes en una variable, diga l_count
y continúe si este recuento resulta ser cero? Algo así como lo siguiente:
DECLARE l_selected APEX_APPLICATION_GLOBAL.VC_ARR2; l_count INTEGER; ...snip... BEGIN -- get the list ids l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST); -- process each in a nice loop FOR i IN 1..l_selected.count LOOP -- do some data checking stuff... -- here we will count duplicate entries, so we can noop if duplicate is found SELECT COUNT(*) INTO l_count FROM org_county_accountable WHERE organization_id = :P4_ID AND county_id = v_county_id; IF l_count = 0 THEN -- here we have code we only want to execute if there are no dupes already -- if not a duplicate record, proceed... END IF; END LOOP; END;
<xmp>
<<next_loop>>
loop
...
...
if ....
then
goto next_loop;
</xmp>
Este es un caso en el que una declaración GOTO podría ser útil. Consulte Documentación de Oracle en el estructuras de control para ver cómo hacer esto. Además, es posible que desee buscar por aquí para averiguar cómo consultar la existencia de un registro. Ejecutar una consulta y esperar una excepción no es óptimo.
Otra forma: convertir el cheque en una función local:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
...snip...
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
v_dup_check org_county_accountable.county_id%TYPE;
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN TRUE;
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN FALSE;
END;
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
-- do some data checking stuff...
-- here we have code we only want to execute if there are no dupes already
IF NOT dup_exists (:P4_ID, v_county_id) THEN
-- if not a duplicate record, proceed...
END;
END LOOP;
END;
Por supuesto, la función local podría reescribirse para usar el método de conteo si lo prefiere:
FUNCTION dup_exists
( p_org_id org_county_accountable.organization_id%TYPE
, p_county_id org_county_accountable.county_id%TYPE
) RETURN BOOLEAN
IS
l_count INTEGER;
BEGIN
SELECT COUNT(*) INTO l_count
FROM org_county_accountable
WHERE organization_id = p_org_id AND county_id = p_county_id;
RETURN (l_count > 0);
END;
Otro método es generar y manejar una excepción definida por el usuario:
DECLARE
l_selected APEX_APPLICATION_GLOBAL.VC_ARR2;
duplicate_org_county EXCEPTION;
...snip...
BEGIN
-- get the list ids
l_selected := APEX_UTIL.STRING_TO_TABLE(:P4_SELECT_LIST);
-- process each in a nice loop
FOR i IN 1..l_selected.count
LOOP
BEGIN
-- do some data checking stuff...
-- here we will look for duplicate entries, so we can noop if duplicate is found
BEGIN
SELECT county_id INTO v_dup_check FROM org_county_accountable
WHERE organization_id = :P4_ID AND county_id = v_county_id;
RAISE duplicate_org_county;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line('no dups found, proceeding');
END;
-- here we have code we only want to execute if there are no dupes already
EXCEPTION
WHEN duplicate_org_county THEN NULL;
END;
END LOOP;
END;
Normalmente no haría esto, pero si hubiera media docena de razones para saltar al siguiente registro, esto podría ser preferible a varios IF anidados.
Sé que esto es antiguo, pero no pude evitar notar que ninguna de las respuestas anteriores tiene en cuenta el cursor atributos :
Hay cuatro atributos asociados con los cursores: ISOPEN, FOUND, NOTFOUND y ROWCOUNT. Se puede acceder a estos atributos con el delimitador% para obtener información sobre el estado del cursor.
La sintaxis para un atributo de cursor es:
cursor_name%attribute
donde cursor_name es el nombre del cursor explícito.
Entonces, en este caso, podría usar ROWCOUNT (que indica el número de filas recuperadas hasta el momento) para sus propósitos, así:
declare
aux number(10) := 0;
CURSOR cursor_name is select * from table where something;
begin
select count(*) into aux from table where something;
FOR row IN cursor_name LOOP
IF(aux > cursor_name%ROWCOUNT) THEN 'do something is not over';
ELSE 'do something else';
END IF;
END LOOP;
end;