2

This is a stupid problem, but I can't seem to get around it. I have a query that's causing trouble in an OCI program, so I want to run it manually in SQL*Plus to check if there is any difference there. This is the query:

select e.label as doc_name,
                       e.url,
                       i.item_id,
                       'multi' as form_type
                from cr_items i, cr_extlinks e
                where i.parent_id = :comment_id
                and e.extlink_id = i.item_id
               UNION
                select null as doc_name,
                       utl_raw.cast_to_varchar2(DBMS_LOB.SUBSTR(r.content, 2000, 1))  as url,
                       r.item_id,
                       'single' as form_type
                from cr_revisions r
                where r.revision_id = ( select content_item.get_latest_revision(:comment_id) from dual);
end;

I wanted to bind the comment_id to the value 3052753, so I did the following:

    DECLARE
     comment_id number := 3052753;
    BEGIN
    select e.label  ,
                           e.url,
                           i.item_id,
                           'multi'  
                    from cr_items i, cr_extlinks e
                    where i.parent_id = :comment_id
                    and e.extlink_id = i.item_id
                   UNION
                    select null  ,
                           utl_raw.cast_to_varchar2(DBMS_LOB.SUBSTR(r.content, 2000, 1))  as url,
                           r.item_id,
                           'single'  
                    from cr_revisions r
                    where r.revision_id = ( select content_item.get_latest_revision(:comment_id) from dual);
    END;
/

which gives this error:

ORA-06550: line 4, column 1:
PLS-00428: an INTO clause is expected in this SELECT statement

Now, I'm already unhappy because I don't want to be radically changing this query, but anyway I forge ahead and come up with this (INTO and UNIONs don't go together so smoothly):

DECLARE
 comment_id number := 3052753;
 x_label VARCHAR2(50);
 x_url VARCHAR2(500);
 x_item number;
 x_thing VARCHAR2(50);
BEGIN
select label, url, item_id, thing into x_label, x_url, x_item, x_thing from (
select e.label  ,
                       e.url,
                       i.item_id,
                       'multi' as thing  
                from cr_items i, cr_extlinks e
                where i.parent_id = :comment_id
                and e.extlink_id = i.item_id
               UNION
                select null  ,
                       utl_raw.cast_to_varchar2(DBMS_LOB.SUBSTR(r.content, 2000, 1))  as url,
                       r.item_id,
                       'single' as thing 
                from cr_revisions r
                where r.revision_id = ( select content_item.get_latest_revision(:comment_id) from dual)) ;
END;
/

but now, of course because I'm returning more than 1 row, I get the utterly predictable

ORA-01422: exact fetch returns more than requested number of rows

Now, I can go ahead and start using cursors etc, but my little query is getting more and more distorted from its original self. All I wanted to do was to check if the query ran ok with that value of comment_id. Of course, I can just hardcode the comment_id into the query, and that works fine. But it also works fine in the OCI so I'm to reproduce in SQL*PLus the issue with bind variables that I'm seeing in the OCI code. But why is it such a struggle to do this in SQL*Plus? Have I missed something really obvious?

Database is Oracle 10.2.0.1.0 - 64bit running on Red Hat Enterprise Linux ES release 4 (Nahant Update 8)

TrojanName
  • 4,853
  • 5
  • 29
  • 41

2 Answers2

3

Similar to @Glenn's approach, but you can declare a bind variable in SQL*Plus and use it in a plain SQL query. First declare it with the var[iable] command:

variable comment_id number;

Then set it with the exec[ute] command, which is essentially an anonymous block:

execute :comment_id := 3052753;

Then run your original query with the :comment_id references, and no BEGIN or END:

select e.label as doc_name,
                       e.url,
                       i.item_id,
                       'multi' as form_type
                from cr_items i, cr_extlinks e
                where i.parent_id = :comment_id
                and e.extlink_id = i.item_id
               UNION
                select null as doc_name,
                       utl_raw.cast_to_varchar2(DBMS_LOB.SUBSTR(r.content, 2000, 1))  as url,
                       r.item_id,
                       'single' as form_type
                from cr_revisions r
                where r.revision_id = ( select content_item.get_latest_revision(:comment_id) from dual);

I don't think there's much functional difference between the two approaches beyond personal preference, and both also work in SQL Developer (when run as a script). I find this easier when running SQL copied from a Pro*C file which already uses the : bind form, purely because you don't have to modify the code at all.


Incidentally, you can write:

where r.revision_id = ( select content_item.get_latest_revision(:comment_id) from dual)

without the extra select, as:

where r.revision_id = content_item.get_latest_revision(:comment_id)
Alex Poole
  • 183,384
  • 11
  • 179
  • 318
  • Thanks Alex, I had already tried this, but it gave the same error as my own approach: PLS-00428: an INTO clause is expected in this SELECT statement – TrojanName Jun 22 '12 at 13:56
  • 1
    You can't get a PL/SQL error from a plain SQL block; you're running your query *without* the `BEGIN`/`END` around it, right? – Alex Poole Jun 22 '12 at 13:59
  • AH! Now that is interesting. You are right of course, I did have a BEGIN and END around it because I was getting a ORA-00904: : invalid identifier. What's even more interesting is that if I change the function call as you suggest, it works! Impressive :-) – TrojanName Jun 22 '12 at 14:08
  • Now, what's going to drive me crazy until I figure it out is why did that function call using "from dual" work for 10 years, and suddenly today stop working? Do you know if that syntax is a recent addition to Oracle?? – TrojanName Jun 22 '12 at 14:11
  • No, both version should work the same, and both have always been valid as far as I know. Presumably nothing has changed recently - can't imagine you've just upgraded to 10g - so bit odd. Maybe the optimizer is confused by something, but if so I'd have thought it would be a bug. – Alex Poole Jun 22 '12 at 14:26
  • Thanks. I'll put up a new question for that. I'm going to give you the correct answer, because with your approach I didn't need to change my query to work with the binds. Thanks again! – TrojanName Jun 22 '12 at 14:28
  • Looks like it might be bug 6038461, 'Wrong results from SQL with UNION and a fast DUAL subquery', which was fixed in 10.2.0.4. Depends what problem you were actually seeing though, and doesn't help explain why it was OK until now. – Alex Poole Jun 22 '12 at 14:28
  • Thanks again for the bug reference. It's so peculiar - it seems to be that exact combination of columns and AND clauses that breaks it. If I change ANYTHING in the query, it works fine. It's just that exact combination of things - how odd.... – TrojanName Jun 22 '12 at 14:59
  • 1
    Changing anything will cause a different execution plan, and a chance for the optimizer to make a different choice and hit/avoid any of its bugs. I guess your query has previously been OK, but the most recent hard parse happened to trip over, hence your problem appearing. Mere mortals aren't supposed to understand the machinations of the optimizer... – Alex Poole Jun 22 '12 at 15:03
  • I created a new question - nobody biting so far. http://stackoverflow.com/questions/11160230/is-this-a-possible-oracle-bug-or-am-i-missing-something – TrojanName Jun 22 '12 at 16:57
2

Rather than creating an anonymous block, you probably want to define an environment variable in sqlplus:

DEFINE comment_id = 3052753

Then refer to &comment_id in your query.

select e.label as doc_name,
                       e.url,
                       i.item_id,
                       'multi' as form_type
                from cr_items i, cr_extlinks e
                where i.parent_id = &comment_id
                and e.extlink_id = i.item_id
               UNION
                select null as doc_name,
                       utl_raw.cast_to_varchar2(DBMS_LOB.SUBSTR(r.content, 2000, 1))  as url,
                       r.item_id,
                       'single' as form_type
                from cr_revisions r
                where r.revision_id = ( select content_item.get_latest_revision(&comment_id) from dual);
Glenn
  • 8,932
  • 2
  • 41
  • 54
  • Doh, I'm such a tool, I knew that 15 years ago! So simple, and I was just digging deeper with each step. Thanks! :-) – TrojanName Jun 22 '12 at 13:54
  • Thanks, I corrected that now (I cut and paste from the anonymous block above). – Glenn Jun 22 '12 at 13:57
  • Glenn, I'm going to give Alex the points as with his approach I didn't need to make any changes to the query. But I do appreciate you taking the time to reply, and I've given you an upvote. Cheers! – TrojanName Jun 22 '12 at 14:29
  • Thanks. I prefer the answer by Alex as well :) – Glenn Jun 22 '12 at 14:40