Ci sono metodi alternativi per dire 'next' in un ciclo pl / sql per?
Domanda
Quindi ho un ciclo for che elabora un elenco di ID e ha alcune cose abbastanza complesse da fare. Senza entrare in tutti i dettagli brutti, sostanzialmente questo:
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;
Il modo in cui lo gestisco normalmente è selezionando un valore e quindi racchiudendo il seguente codice in un'istruzione IF verificando che la variabile di controllo duplicata sia NULL. Ma è fastidioso. Voglio solo poter dire NEXT; o NOOP; o qualcosa. Soprattutto da quando devo già prendere l'eccezione NO_DATA_FOUND. Suppongo di poter scrivere una lettera a Oracle, ma sono curioso di sapere come gli altri lo gestiranno.
Potrei anche racchiuderlo in una funzione, ma stavo cercando qualcosa di un po 'più pulito / semplice.
Soluzione
È anche possibile contare il numero di righe (vedi Pourquoi Litytestdata) ma puoi anche fare quello che vuoi fare nel blocco 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;
Altri suggerimenti
Oracle 11g aggiunge uno stile C "continua" costrutto loop su PL / SQL, che suona sintatticamente come quello che stai cercando.
Per i tuoi scopi, perché non eliminare semplicemente i duplicati prima di entrare nel ciclo? Questo potrebbe essere fatto interrogando l_selected usando una funzione table, e quindi filtrando i record che non vuoi invece di iterare su ogni valore. Qualcosa come ...
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;
Basta sostituire la logica per determinare un "duplicato" con il tuo (non hai avuto abbastanza informazioni dal tuo esempio per rispondere davvero a quella parte)
Invece di prendere NO_DATA_FOUND
, che ne dici di SELEZIONARE il numero di voci corrispondenti in una variabile, dire l_count
, e procedere se questo conteggio risulta essere zero? Qualcosa di simile al seguente:
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>
Questo è un caso in cui un'istruzione GOTO potrebbe essere utile. Vedi la Documentazione Oracle nella strutture di controllo per vedere come farlo. Inoltre, potresti voler cercare qui intorno per scoprire come interrogare l'esistenza di un record. Eseguire una query e attendere un'eccezione non è ottimale.
Un altro modo: trasforma il segno di spunta in una funzione locale:
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;
Naturalmente, la funzione locale potrebbe essere riscritta per utilizzare il metodo count se si preferisce:
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;
Un altro metodo è sollevare e gestire un'eccezione definita dall'utente:
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 non lo farei, ma se ci fossero mezza dozzina di motivi per passare al record successivo, questo potrebbe essere preferibile a più IF nidificati.
So che si tratta di un vecchio ma non ho potuto fare a meno di notare che nessuna delle risposte precedenti tiene conto del cursore attributi :
Esistono quattro attributi associati ai cursori: ISOPEN, FOUND, NOTFOUND e ROWCOUNT. È possibile accedere a questi attributi con il delimitatore% per ottenere informazioni sullo stato del cursore.
La sintassi per un attributo cursore è:
cursor_name%attribute
dove cursore_name è il nome del cursore esplicito.
Quindi in questo caso potresti usare ROWCOUNT (che indica il numero di righe recuperate finora) per i tuoi scopi, in questo modo:
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;