tags:

views:

550

answers:

4

I have a a cursor loop that's building a string by concatenating the contents of a table together, using code along these lines:

OPEN cur_t;
LOOP
    FETCH cur_t INTO v_texttoadd;

    v_string := v_string || v_texttoadd;
EXIT WHEN cur_t%notfound;
END LOOP;

The problem is, of course, that the last item gets added twice because the system runs through it once more before realising that there's nothing more to find.

I tried playing around with something like

OPEN cur_t;
WHILE cur_t%found;
LOOP
    FETCH cur_t INTO v_texttoadd;

    v_string := v_string || v_texttoadd;
END LOOP;

But that didn't seem to return anything at all.

What kind of syntax should I be using so that each row only appears in the resulting string once?

+7  A: 

You can try this:

OPEN cur_t;
LOOP
  FETCH cur_t INTO v_texttoadd;
  EXIT WHEN cur_t%notfound;
  v_string := v_string || v_texttoadd;
END LOOP;

This works because %notfound is set when FETCH is executed and there aren't any more rows to fetch. In your example you checked %notfound after the concatenation and as a result, you had the duplicate in the end.

Petros
A: 

Simple answer, though possibly not the best:

OPEN cur_t;
LOOP
    FETCH cur_t INTO v_texttoadd;
    IF cur_t%found THEN
        v_string := v_string || v_texttoadd;
    END IF;
EXIT WHEN cur_t%notfound;
END LOOP;
Margaret
I think, based on your indenting, you are assuming that the EXIT statement is part of the LOOP syntax and must come at the end of the loop. This isn't true; EXIT is just a statement like any other in the body of the loop. This is why, as others have shown, you can put it immediately after the FETCH to solve your issue.
Dave Costa
+1  A: 

%notfound is set when fetch fails to retrieve a new row.

another possible way (this one avoiding the "if"s and "exit when"s):

OPEN cur_t;
FETCH cur_t INTO v_texttoadd;
WHILE cur_t%found LOOP
    v_string := v_string || v_texttoadd;
    FETCH cur_t INTO v_texttoadd;
END LOOP;
ammoQ
+2  A: 

Right answers have already been given, but just elaborating a bit.

Simulating your current situation:

SQL> declare
  2    cursor cur_t
  3    is
  4    select ename
  5      from emp
  6     where deptno = 10
  7    ;
  8    v_texttoadd emp.ename%type;
  9    v_string    varchar2(100);
 10  begin
 11    open cur_t;
 12    loop
 13      fetch cur_t into v_texttoadd;
 14      v_string := v_string || v_texttoadd;
 15      exit when cur_t%notfound;
 16    end loop
 17    ;
 18    dbms_output.put_line(v_string);
 19  end;
 20  /
CLARKKINGMILLERMILLER

PL/SQL-procedure is geslaagd.

Here MILLER is printed twice. By just switching the EXIT statement and the v_string assignment, you get the desired result:

SQL> declare
  2    cursor cur_t
  3    is
  4    select ename
  5      from emp
  6     where deptno = 10
  7    ;
  8    v_texttoadd emp.ename%type;
  9    v_string    varchar2(100);
 10  begin
 11    open cur_t;
 12    loop
 13      fetch cur_t into v_texttoadd;
 14      exit when cur_t%notfound;
 15      v_string := v_string || v_texttoadd;
 16    end loop
 17    ;
 18    dbms_output.put_line(v_string);
 19  end;
 20  /
CLARKKINGMILLER

PL/SQL-procedure is geslaagd.

However, your PL/SQL code becomes easier when using a cursor-for-loop. You can then skip the v_texttoadd variable and the number of lines in your loop decreases:

SQL> declare
  2    cursor cur_t
  3    is
  4    select ename
  5      from emp
  6     where deptno = 10
  7    ;
  8    v_string    varchar2(100);
  9  begin
 10    for r in cur_t
 11    loop
 12      v_string := v_string || r.ename;
 13    end loop
 14    ;
 15    dbms_output.put_line(v_string);
 16  end;
 17  /
CLARKKINGMILLER

PL/SQL-procedure is geslaagd.

You can also use straight SQL to accomplish the job. An example with the SQL model clause, if you are on version 10g or higher:

SQL> select string
  2    from ( select string
  3                , rn
  4             from emp
  5            where deptno = 10
  6            model
  7                  dimension by (rownum rn)
  8                  measures (ename, cast(null as varchar2(100)) string)
  9                  ( string[any] order by rn desc = ename[cv()] || string[cv()+1]
 10                  )
 11         )
 12   where rn = 1
 13  /

STRING
-----------------------------------------------------------------------------------
CLARKKINGMILLER

1 rij is geselecteerd.

Regards, Rob.

Rob van Wijk
+1 for the FOR .. IN ... LOOP suggestion and examples. Cursor FOR loops are your friends ;-) Added bonus: you can often embed your cursor select statement in the FOR loop: FOR Rec IN (SELECT...) LOOP
DCookie