PL-SQL: why does a dynamic statement using bind-variable input not work? - oracle

I am trying to create and execute a dynamic SQL statement (using Oracle PL/SQL) and when things didn't work I condensed it to the below two code snippets:
My understanding was that the following two statements would be equivalent (table_name, row_name and row_value are all VARCHAR2 strings):
query_statement VARCHAR2(1000);
-- variant a:
query_statement := 'select * from ' || table_name || ' where ' || row_name || ' = ' || row_value;
dbms_output.put_line('query="' || query_statement || '"');
execute immediate (query_statement);
-- variant b:
query_statement := 'select * from :table where :row = :value';
dbms_output.put_line('query="' || query_statement || '"');
execute immediate (query_statement) using table_name, row_name, row_value;
But not so! The first variant works fine, the second always fails with an error:
** An error was encountered: -903 ORA-00903: invalid table name
From a readability point of view I would prefer variant b but I don't get, why that second variant fails and why the table_name is flagged as wrong. After all that very same name obviously works in variant a. The emitted queries also looks identical to me - of course modulo the fact that the second string still contains placeholders only while the first string contains already concrete values.
Could some kind soul shed some light on this? Why is variant b not working? What am I missing?

As mentioned by #William, usage of bind variable as Tablename is prohibited. On similar lines, you cannot use bind variables as Column Names and Order By clause as well.
Although this is not documented anywhere but its important to note that replacing these ordinal notation affects the execution plan.

You can't use a bind variable as a table_reference. As documented in the link, the table_reference must be static part of the statement.
select * from :table -- ILLEGAL
Contrary to that the usage in the WHERE clause is allowed
where :row = :value -- LEGAL, but questionable
but it doesn't do what you expect. The whereclause returns TRUE, if both bind variables passed are same.
I.e. it is equivalent to
where 'name' = 'xxxx'
and not to
where name = 'xxxx'

Related

Oracle SQL Selecting weekly music rank procedure

I'm trying to make procedure to select weekly music rank based on hits and likes.
create or replace procedure select_rank(
v_title IN music.title%TYPE,
v_release_date IN music.release_date%TYPE,
v_hit IN music.hit%TYPE
) is
v_cnt number := 0;
BEGIN
select music.title, music.release_date, count(melon_user.user_idx) as likes, music.hit
into v_rank, v_title, v_cnt, v_release_date, v_hit
from melon_user, user_like_music, music
where melon_user.user_idx = user_like_music.user_idx
and user_like_music.music_idx = music.music_idx
group by music.title, music.hit, music.release_date
order by count(melon_user.user_idx) desc, music.hit desc;
DBMS_OUTPUT.PUT_LINE('v_title: ' || v_title);
DBMS_OUTPUT.PUT_LINE('v_cnt: ' || v_cnt);
DBMS_OUTPUT.PUT_LINE('v_release_date: ' || v_release_date);
DBMS_OUTPUT.PUT_LINE('v_hit: ' || v_hit);
END select_rank;
/
But I keep getting error says
PLS-00403: expression 'V_RELEASE_DATE' cannot be used as an
INTO-target of a SELECT/FETCH statement
PLS-00403: expression 'V_RELEASE_DATE' cannot be used as an
INTO-target of a SELECT/FETCH statement
PLS-00403: expression 'V_RELEASE_DATE' cannot be used as an
INTO-target of a SELECT/FETCH statement
how do I fix this?
In the procedure's signature you have:
v_release_date IN music.release_date%TYPE,
v_release_date is an IN parameter and will be read only. If you want to assign values to it wthin the procedure and return them then make it an OUT (or IN OUT) parameter.
However, you have also:
Not declared the v_rank variable.
Your query appears likely to return multiple rows - SELECT .. INTO .. will only work for queries that return a single row. If this is the case then you either want to use BULK COLLECT INTO or return a cursor.

oracle produce different result for two same queries when only change field,value

I have SQL query . When I declare variable and run the query it produce 39 rows but when I use a literal value instead of variable and run query it produce three rows. How can it be ?
Here is query and results with variables
declare
fromnode number :=1;
CURRENTESTIMATE number :=0;
begin
for ts in (SELECT e.fromnode,e.tonode,e.weight
FROM TS_DIJNODEESTIMATE N
INNER JOIN TS_EDGE E ON N.ID=E.TONODE
WHERE N.DONE=0 AND E.FROMNODE=fromnode AND (CURRENTESTIMATE+E.WEIGHT)<N.ESTIMATE)
loop
dbms_output.put_line(ts.fromnode || ',' || ts.tonode || ',' || ts.weight);
end loop;
end;
The result is
1,2,1306
1,5,2161
1,6,2661
2,3,919
2,4,629
3,2,919
3,4,435
3,5,1225
3,7,1983
4,2,629
4,3,435
5,3,1225
5,6,1483
5,7,1258
6,5,1483
6,7,1532
6,8,661
7,3,1983
7,5,1258
7,6,1532
7,9,2113
7,12,2161
8,6,661
8,9,1145
8,10,1613
9,7,2113
9,8,1145
9,10,725
9,11,383
9,12,1709
10,8,1613
10,9,725
10,11,338
11,9,383
11,10,338
11,12,2145
12,7,2161
12,9,1709
12,11,2145
With literal instead of variable:
declare
fromnode number :=1;
CURRENTESTIMATE number :=0;
begin
for ts in (SELECT e.fromnode,e.tonode,e.weight
FROM TS_DIJNODEESTIMATE N
INNER JOIN TS_EDGE E ON N.ID=E.TONODE
WHERE N.DONE=0 AND E.FROMNODE=1 AND (0+E.WEIGHT)<N.ESTIMATE)
loop
dbms_output.put_line(ts.fromnode || ',' || ts.tonode || ',' || ts.weight);
end loop;
end;
the result is
1,2,1306
1,5,2161
1,6,2661
The desired result is the second result.
You should change the name of your variable fromnode to my_fromnode or something like that.
In your firt query inside where the E.FROMNODE=fromnode is actually comparing the column with itself and returns true.
The problem is naming: you gave your variable the same name as the table column:
E.FROMNODE=fromnode
Oracle's scoping rules resolve names using the nearest match, starting from the innermost point, working outwards and upwards. The nearest namespace in a query is the table so it first attempts to resolve fromnode as a table column. The name fits, so the compiler doesn't look any further.
Effectively your WHERE clause filter is logically the same as 1 = 1, and that's why you get more rows. This should not be a surprise as it is the documented behaviour. The PL/SQL documentation covers scope of variable identifier names here and the interaction with variable names here. The key point is the one Alex Poole highlights:
"If a SQL statement references a name that belongs to both a column and either a local variable or formal parameter, then the column name takes precedence."
You thought you had avoided this by putting fromnode in lower case but PL/SQL is not case sensitive. The correct approach is to use a naming convention such as identifying variables with a prefix: V for variable - v_fromnode - or L for Local - l_fromnode - are both common conventions.

Oracle Cursor Scripts errors

I have to construct a block to pull information from two tables using a cursor. As a further challenge, I have to identify the first item on the pull. I tried to use an IF statement to work through this. It errors out in several areas and I have no idea what I am doing wrong. Not asking for the answer per say, just enough of a push to get me moving again. Thanks. Here is the code I've put together so far:
DECLARE
CURSOR cur_pled IS
SELECT dd_pledge.idpledge, dd_pledge.pledgeamt, dd_pledge.paymonths, dd_payment.paydate, dd_payment.payamt
FROM dd_pledge, dd_payment
WHERE dd_payment.idpledge = dd_pledge.idpledge AND
dd_pledge.idpledge = 104
ORDER BY dd_pledge.idpledge, dd_payment.paydate;
TYPE type_pled IS RECORD
(pledID dd_pledge.idpledge%TYPE,
pledAmt dd_pledge.pledgeamt%TYPE,
payMonths dd_pledge.paymonths%TYPE,
payDate dd_payment.paydate%TYPE,
payAmt dd_payment.payamt%TYPE);
rec_pled type_pled;
lv_id_num dd_pledge.idpledge%TYPE := 0;
BEGIN
OPEN cur_pled;
LOOP
FETCH cur_pled INTO rec_pled;
EXIT WHEN cur_pled%NOTFOUND;
IF rec_pled.type <> lv_id_num THEN
DBMS_OUTPUT.PUT_LINE('First Payment');
ELSE DBMS_OUTPUT.PUT_LINE('Other Payment');
END IF;
END LOOP;
CLOSE cur_pled;
DBMS_OUTPUT.PUT_LINE(pledID || ' ' || dd_pledge.pledgeamt || ' ' ||
dd_pledge,payMonths || ' ' || dd_payment.payDate || ' ' ||
dd_payment.payAmt);
END;
There are loads of errors in your code. If you had formatted it correctly, you would have spotted some of them yourself!
Things that leapt out at me:
You're referring to dd_pledge in the final dbms_output.put_line, but dd_pledge isn't a variable. I think you meant to use rec_pled instead.
You refer to pledID in your final dbms_output.put_line statement - but this is a field defined in the record type, NOT a defined variable. I think you probably meant to use rec_pled.pledid
You're selecting the results of the cursor into rec_pled.type - however, "type" is not a field declared in the type_pled's definition! Did you mean rec_pled.idpledge instead?
You have dd_pledge,payMonths in your final dbms_output.put_line statement - the comma should be a full stop: rec_pled.payMonths
You're outputting the results after you've closed the results. Because this is just a record variable, you're only going to be outputting the results from the last row in the query.
Why aren't you doing a cursor for loop? That takes care of the exiting and declaring a record for you.
Anyway, I think you can achieve your results by using an analytic function in your query, rather than needing to use PL/SQL to do the work:
SELECT plg.idpledge,
plg.pledgeamt,
plg.paymonths,
pay.paydate,
pay.payamt,
case when row_number() over (partition by plg.idpledge, pay.paydate) = 1 then 'First Payment'
else 'Other Payment'
end type_of_payment
FROM dd_pledge plg
inner join dd_payment pay on (pay.idpledge = plg.idpledge)
--WHERE plg.idpledge = 104 -- add back in if you really need to do it for a single id
ORDER BY plg.idpledge, pay.paydate;

Oracle pipelined function cannot access remote table (ORA-12840) when used in a union

I have created a pipelined function which returns a table. I use this function like a dynamic view in another function, in a with clause, to mark certain records. I then use the results from this query in an aggregate query, based on various criteria. What I want to do is union all these aggregations together (as they all use the same source data, but show aggregations at different heirarchical levels).
When I produce the data for individual levels, it works fine. However, when I try to combine them, I get an ORA-12840 error: cannot access a remote table after parallel/insert direct load txn.
(I should note that my function and queries are looking at tables on a remote server, via a DB link).
Any ideas what's going on here?
Here's an idea of the code:
function getMatches(criteria in varchar2) return myTableType pipelined;
...where this function basically executes some dynamic SQL, which references remote tables, as a reference cursor and spits out the results.
Then the factored queries go something like:
with marked as (
select id from table(getMatches('OK'))
),
fullStats as (
select mainTable.id,
avg(nvl2(marked.id, 1, 0)) isMarked,
sum(mainTable.val) total
from mainTable
left join marked
on marked.id = mainTable.id
group by mainTable.id
)
The reason for the first factor is speed -- if I inline it, in the join, the query goes really slowly -- but either way, it doesn't alter the status of whatever's causing the exception.
Then, say for a complete overview, I would do:
select sum(total) grandTotal
from fullStats
...or for an overview by isMarked:
select sum(total) grandTotal
from fullStats
where isMarked = 1
These work fine individually (my pseudocode maybe wrong or overly simplistic, but you get the idea), but as soon as I union all them together, I get the ORA-12840 error :(
EDIT By request, here is an obfuscated version of my function:
function getMatches(
search in varchar2)
return idTable pipelined
as
idRegex varchar2(20) := '(05|10|20|32)\d{3}';
searchSQL varchar2(32767);
type rc is ref cursor;
cCluster rc;
rCluster idTrinity;
BAD_CLUSTER exception;
begin
if regexp_like(search, '^L\d{3}$') then
searchSQL := 'select distinct null id1, id2_link id2, id3_link id3 from anotherSchema.linkTable#my.remote.link where id2 = ''' || search || '''';
elsif regexp_like(search, '^' || idRegex || '(,' || idRegex || || ')*$') then
searchSQL := 'select distinct null id1, id2, id3 from anotherSchema.idTable#my.remote.link where id2 in (' || regexp_replace(search, '(\d{5})', '''\1''') || ')';
else
raise BAD_CLUSTER;
end if;
open cCluster for searchSQL;
loop
fetch cCluster into rCluster;
exit when cCluster%NOTFOUND;
pipe row(rCluster);
end loop;
close cCluster;
return;
exception
when BAD_CLUSTER then
raise_application_error(-20000, 'Invalid Cluster Search');
return;
when others then
raise_application_error(-20999, 'API' || sqlcode || chr(10) || sqlerrm);
return;
end getMatches;
It's very simple, designed for an API with limited access to the database, in terms of sophistication (hence passing a comma delimited string as a possible valid argument): If you supply a grouping code, it returns linked IDs (it's a composite, 3-field key); however, if you supply a custom list of codes, it just returns those instead.
I'm on Oracle 10gR2; not sure which version exactly, but I can look it up when I'm back in the office :P
To be honest no idea where the issue came from but the simplest way to solve it - create a temporary table and populate it by values from your pipelined function and use the table inside WITH clause. Surely the temp table should be created but I'm pretty sure you get serious performance shift because dynamic sampling isn't applied to pipelined functions without tricks.
p.s. the issue could be fixed by with marked as ( select /*+ INLINE / id from table(getMatches('OK'))) but surely it isn't the stuff you're looking for so my suggestion is confirmed WITH does something like 'insert /+ APPEND*/' inside it'.

How to use SELECT result record as DECODE parameters?

Is it possible to use SELECT result as DECODE parameters when this SELECT returns only one record with prepared string?
For example:
SELECT replace(replace(serialized_data)..)..) as result FROM table
Returns following result in ONE ROW:
0,'label0',1,'label1',2,'label2'
But when I put this to DECODE it's being interpreted as ONE PARAMETER.
Is there possiblity to convert this result "string" to "pure" sql code? ;)
Thanks for any help.
You could kind of replicate decode by use of instr and substr. An example below (which could probably be tidied up considerably but works):
select DTXT
,Nvl(
Substr(
DTXT
,Instr(DTXT, SEARCHTXT || VALMTCH)
+ Length(SEARCHTXT || VALMTCH)
, Instr(DTXT, VALSEP, Instr(DTXT, SEARCHTXT || VALMTCH))
- Instr(DTXT, SEARCHTXT || VALMTCH)
- Length(SEARCHTXT || VALMTCH))
,CASEOTHER)
as TXTMATCH
from (select '0=BLACK;1=GREEN;2=YELLOW;' as DTXT
,'1' as SEARCHTXT
,';' as VALSEP
,'=' as VALMTCH
,'OTHER' as CASEOTHER
from Dual)
You do need to have a semi-colon (VALSEP) at the end of the text though to ensure you can find the last value (though it's possible to work around that if I looked into it further).
list_agg or wm_concat depending on version of oracle.
http://docs.oracle.com/cd/E14072_01/server.112/e10592/functions087.htm
You can use dynamic SQL:
declare
DECTXT varchar2(4000);
begin
select replace(replace(serialized_data)..)..) as result into dectxt from table;
execute immediate 'select decode(col1,' || DECTXT || ') from tab1';
end;
This is just a quick example, not showing any output, etc.

Resources