views:

1317

answers:

6

So I've got a for loop that processes a list of IDs and has some fairly complex things to do. Without going into all the ugly details, basically this:

    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;

How I normally handle this is by selecting into a value, and then wrap the following code in an IF statement checking to make sure that duplicate check variable is NULL. But it's annoying. I just want to be able to say NEXT; or NOOP; or something. Especially since I already have to catch the NO_DATA_FOUND exception. I suppose I could write a letter to Oracle, but I'm curious how others handle this.

I could also wrap this in a function, too, but I was looking for something a little cleaner/simpler.

A: 

This is a case where a GOTO statement might be useful. See the Oracle Documentation in the control structures to see how to do this. Also, you may want to search around here to find out how to query for the existence of a record. Running a query and waiting for an exception isn't optimal.

Adam Hawkes
That's actually a really tight solution, the GOTO statement, thanks! I don't quite get the rest of your answer. I'm more than happy to query duplicate records, it's necessary. Although, I like the idea of selecting a count better, since there's no exception thrown.
Billy Gray
+3  A: 

Instead of catching NO_DATA_FOUND, how about SELECTing the number of matching entries into a variable, say l_count, and proceeding if this count works out to be zero? Something like the following:

    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;
Pourquoi Litytestdata
Was just about to write the same thing
Dave Costa
+3  A: 

Oracle 11g adds a C-style "continue" loop construct to PL/SQL, which syntactically sounds like what you're looking for.

For your purposes, why not just eliminate the duplicates prior to entering the loop? This could be done by querying l_selected using a table function, and then filtering out records you don't want instead of iterating over every value. Something like...

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;

Just substitute the logic for determining a "duplicate" with your own (didn't have enough info from your example to really answer that part)

jimmyorr
+1  A: 

To count the number of rows is also possible (see Pourquoi Litytestdata) but you can also do what you want to do in the when_no_data_found exception block.

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;
tuinstoel
A: 

Another way - turn the check into a local function:

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;

Of course, the local function could be re-written to use the count method if you prefer:

  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;
Tony Andrews
A: 

Another method is to raise and handle a user-defined exception:

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;

I wouldn't normally do this, but if there were half a dozen reasons to jump to the next record, this might be preferable to multiple nested IFs.

Tony Andrews