Существуют ли альтернативные способы сказать «следующий» в цикле pl / sql for?
Вопрос
Итак, у меня есть цикл for, который обрабатывает список идентификаторов и выполняет довольно сложные вещи. Не вдаваясь во все уродливые подробности, в основном это:
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;
Как я обычно это делаю, выбирая значение, а затем оборачивая следующий код в проверку оператора IF, чтобы убедиться, что дублирующая переменная проверки имеет значение NULL. Но это раздражает. Я просто хочу иметь возможность сказать СЛЕДУЮЩИЙ; или NOOP; или что-то. Тем более, что я уже должен поймать исключение NO_DATA_FOUND. Полагаю, я мог бы написать письмо в Oracle, но мне любопытно, как другие справятся с этим.
Я тоже мог бы обернуть это в функцию, но я искал кое-что немного чище / проще.
Решение
Подсчет количества строк также возможен (см. Pourquoi Litytestdata), но вы также можете делать то, что хотите, в блоке when_no_data_found exception
.
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;
Другие советы
Oracle 11g добавляет стиль C " продолжить " конструкция цикла в PL / SQL, которая синтаксически звучит так, как вы ищете.
Почему бы вам просто не удалить дубликаты до входа в цикл? Это можно сделать, запросив l_selected с помощью табличной функции, а затем отфильтровав ненужные записи, вместо того, чтобы перебирать каждое значение. Что-то вроде ...
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;
Просто замените логику для определения " дубликата " с вашим собственным (недостаточно информации из вашего примера, чтобы действительно ответить на эту часть)
Вместо того, чтобы перехватывать NO_DATA_FOUND
, как насчет ВЫБОРА количества совпадающих записей в переменной, скажем, l_count
, и продолжения, если это число работает равным нулю? Примерно так:
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>
Это тот случай, когда оператор GOTO может быть полезным. См. документацию Oracle в разделе структуры управления, чтобы увидеть, как это сделать. Кроме того, вы можете поискать здесь, чтобы узнать, как запросить наличие записи. Выполнение запроса и ожидание исключения не является оптимальным.
Другой способ - превратить чек в локальную функцию:
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;
Конечно, локальная функция может быть переписана для использования метода count, если вы предпочитаете:
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;
Другой метод - вызвать и обработать пользовательское исключение:
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;
Обычно я бы так не делал, но если бы было полдюжины причин для перехода к следующей записи, это может быть предпочтительнее, чем несколько вложенных IF.
Я знаю, что это старенький, но я не мог не заметить, что ни один из ответов выше не учитывает курсор атрибуты :
С курсорами связаны четыре атрибута: ISOPEN, FOUND, NOTFOUND и ROWCOUNT. Эти атрибуты могут быть доступны с помощью разделителя%, чтобы получить информацию о состоянии курсора. Р>
Синтаксис для атрибута курсора:
cursor_name%attribute
где имя_курсора - это имя явного курсора. Р>
Так что в этом случае вы можете использовать ROWCOUNT (который указывает количество выбранных строк) для ваших целей, например так:
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;