Native Dynamic SQL, creating string - sql

I've just started with PL/SQL. My concern is as follows:
I want to create a string dynamically.
I tried the following, but it always results in ORA-00900 & ORA-06512 at the line of "Execute Immediate...".
Here we go:
Declare
l_pre_sql varchar2(4000) := 'schema.';
l_sql varchar2(4000) := 'concat(concat(:a, :b), :c)';
l_after_sql := '.procedure(a,b,c)';
begin
execute immediate l_sql using l_pre_sql, l_sql, l_after_sql;
end;
Is the syntax of execute immediate wrong? Are there any other working possibilities? As you may see, i am working around the problem that one cannot use schema name as a dynamic variable.
For clarification I basically want to do this:
execute immediate ':a'||'.'||':b'||'.procedure(a,b,c)' using schema, name;
Thanks in advance!

In prepared statements (in Oracle and other languages), you can replace constant values in the query string using parameters. However, you cannot replace column names, table names, users (schemas), procedure names, and so on.
In other words, the substitution is not just by replacing values with string representations. It is plugging parameters into a compiled statement.
So, you need to construct the string with the procedure names first and then call it.
I think what you want is something like:
execute immediate l_pre_sql || l_after_sql || '(:a, :b, :c)' using . . .

Related

using comma separated values inside IN clause for NUMBER column

I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/

Referencing an array element in select Query in PL/SQL

I am writing some Pl/SQl in which I used an array Variable of Length 5.
Then I stored all the Column name of another table into the above declared array.
Now I am looking for a solution by which I can use the Array element in select Query to fetch the data from another table which exactly has the column name.
Like
arr(1):='Name'
arr(2):='Course'
The Query in Pl/Sql should be something like this (for reference only)
select arr(1) from Mttable;
==== This generates error when I write the Query in this way
Note- All work should be done in Pl/SQL on Oracle 10g
Please Help.
You can build up a query in a string and execute the string. My PL/SQL is rusty, but something like:
begin
query := 'select ' || arr(1) || ' from Mttable';
execute immediate query;
end;

Oracle SQL: variables used in place of table names

I am converting a MSSQL script to Oracle, and I haven't been able to figure out the syntax to use a variable in place of a table name or column.
Here is a simple example that I've been try to make work in Oracle SQL Developer so I can better understand the syntax:
set serveroutput on format wrapped;
declare
VR_TABLE VARCHAR2(256);
VR_UPDATE VARCHAR2(256);
begin
VR_TABLE :='SYSTEM_STATUS';
EXECUTE IMMEDIATE 'select UPDATE_VERSION INTO VR_UPDATE from ' || VR_TABLE || 'where rownum < 2 ;'
end;
Where VR_TABLE is the variable table name that will get changed each iteration of the loop.
Can somebody point out what I'm doing wrong, or link me to a site that would be useful for me to read? I've read a few tutorials on this, but I haven't had any luck thus far.
You need to have a space between the table name and the subsequent WHERE clause
The INTO needs to be part of the EXECUTE IMMEDIATE, not part of the dynamic SQL statement.
The dynamic SQL statement should not have a trailing semicolon
The EXECUTE IMMEDIATE statement should end with a semicolon
Putting those together, something like this should work
declare
VR_TABLE VARCHAR2(256);
VR_UPDATE VARCHAR2(256);
begin
VR_TABLE :='SYSTEM_STATUS';
EXECUTE IMMEDIATE 'select UPDATE_VERSION from ' || VR_TABLE || ' where rownum < 2'
INTO VR_UPDATE;
end;
Of course, since you're not doing anything with VR_UPDATE, nothing will be displayed when this anonymous block is executed.
INTO part of the query should not be directly included in the query
string.
Syntax
EXECUTE IMMEDIATE(<SQL>)
[INTO<variable>]
[USING <bind_variable_value>]
The above syntax shows EXECUTE IMMEDIATE command.
Clause INTO is optional and used only if the dynamic SQL contains a select statement that fetches values. The variable type should match with the variable type of the select statement.
Clause USING is optional and used only if the dynamic SQL contains any bind variable.
https://www.guru99.com/dynamic-sql-pl-sql.html#2
You can visit this site for a better understanding of Dynamic SQL.

How to pass inputs to a dynamic query in Oracle SQL?

I am trying to create a function that dynamically runs a query based on inputs. The first input for the function, input_id, is the argument for the dynamic query. The second input, IN_QUERY_ID, specifies which query to use.
create or replace
FUNCTION getResultID(
INPUT_ID NUMBER,
IN_QUERY_ID NUMBER
)
RETURN VARCHAR2
AS
RESULT_ID VARCHAR2(256);
query_str VARCHAR2(256);
BEGIN
select CONSTRUCTOR INTO query_str from query_str_ref
where QUERY_ID=IN_QUERY_ID;
EXECUTE IMMEDIATE query_str INTO RESULT_ID USING INPUT_ID;
RETURN Result_ID;
END getResultID;
I'm getting an error that I'm not properly ending the statement after "RESULT_ID=IN_QUERY_ID;" I'm wondering if I'm missing some other step.
You haven't declared Result_ID as a variable in the function.
The good news is that it's not your function that's wrong. According to the dbms_output that #sebas encouraged you to produce, the string you're trying to execute dynamically is:
select FIRST_NAME||LAST_NAME||to_char(BIRTH_DATE,'yyyy/mm/dd') as HOST_ID FROM INPUT_DATA_TABLE WHERE INPUT_ID=NEW:INPUT_ID;
There are two thing wrong with that. The NEW:INPUT_ID is causing the ORA-00933, because the NEW looks spurious; if you remove that it will recognise the :INPUT_ID as a bind variable. (NEW looks like it's come from a trigger but is probably a coincidence). And you should not have a trailing ; on the string, execute doesn't need it and it will break with an invalid character error.
So it should work if the query_str_ref entry is changed to:
select FIRST_NAME||LAST_NAME||to_char(BIRTH_DATE,'yyyy/mm/dd') as HOST_ID FROM INPUT_DATA_TABLE WHERE INPUT_ID=:INPUT_ID

Trying to replace dbms_xmlgen.xmlget with sys_xmlagg

I'm working on parameterizing some JDBC queries against Oracle 10gR2.
Most of the queries are of the form:
String value = "somevalue";
String query = "select dbms_xmlgen.xmlget('select c1, c2 from t1 where c1 = ''"
+ somevalue + "'' ') xml from dual;";
I can't parameterize that as is, since the actual select is in a quoted string inside xmlget and parameters are not expanded inside a string. JDBC will see that query as having no parameters.
I've been fairly successful in emulating the behavior of dbms_xmlgen.xmlget with:
String query = "SELECT xmltype.getclobval(sys_xmlagg(xmlelement(\"ROW\","
+ "xmlforest(c1, c2)))) xml from t1 where c1 = ?";
The only issue I have not been able to resolve is the case where the query returns no rows.
With dbms_xmlgen.xmlget, no rows returns an empty CLOB. But, with sys_xmlagg, no rows results in a CLOB consisting of:
<?xml version="1.0"?><ROWSET></ROWSET>
I'm looking for a solution that will give me an empty CLOB instead of an empty document.
I don't have access to an Oracle DB at the moment, so please forgive inaccuracies.
The parameterization of the DBMS_XMLGEN call seems to be the goal. This is accomplished by using a little PL/SQL. The Oracle Docs for the DBMS_XMLGEN package describe a few operations which should help. First, create a context from a SYS_REFCURSOR using this form:
DBMS_XMLGEN.NEWCONTEXT (
queryString IN SYS_REFCURSOR)
RETURN ctxHandle;
Then, use the context in another form of GetXML:
DBMS_XMLGEN.GETXML (
ctx IN ctxHandle,
tmpclob IN OUT NCOPY CLOB,
dtdOrSchema IN number := NONE)
RETURN BOOLEAN;
Using this method also gives the benefit of potentially reusing the CLOB (not making a new temporary one), which may help with performance. There is another form which is more like the one you were using in your example, but loses this property.
One more thing... The return of GETXML in this example should tell you whether there were rows returned or not. This should be more reliable than checking the contents of the CLOB when the operation completes. Alternately, you can use the NumRowsProcessed function on the context to get the count of the rows included in the CLOB.
Roughly, your code would look something like this:
DECLARE
srcRefCursor SYS_REFCURSOR;
ctxHandle ctxHandle;
somevalue VARCHAR2(1000);
myClob CLOB;
hasRows boolean;
BEGIN
OPEN srcRefCursor FOR
SELECT c1, c2
FROM t1
WHERE c1 = somevalue; --Note parameterized value
ctxHandle := DBMS_XMLGEN.NEWCONTEXT(srcRefCursor);
hasRows := DBMS_XMLGEN.GETXML(
ctxHandle,
myClob -- XML stored in myCLOB
);
IF (hasRows) THEN
/* Do work on CLOB here */
END IF;
DBMS_XMLGEN.CLOSECONTEXT(ctxHandle);
END;