I was writing some program that uses the WM_CONCAT function. When I run this query:
SELECT WM_CONCAT(DISTINCT employee_id)
FROM employee
WHERE ROWNUM < 20;
It works fine. When I try to compile the relatively same query in a package function or procedure, it produces this error: PL/SQL: ORA-30482: DISTINCT option not allowed for this function
FUNCTION fetch_raw_data_by_range
RETURN VARCHAR2 IS
v_some_string VARCHAR2(32000);
BEGIN
SELECT WM_CONCAT(DISTINCT employee_id)
INTO v_some_string
FROM employee
WHERE ROWNUM < 20;
RETURN v_some_string;
END;
I realize WM_CONCAT is not officially supported, but can someone explain why it would work as a stand alone query with DISTINCT, but not compile in a package?
Problem is that WM_CONCAT is stored procedure written on pl/sql.
There is a open bug #9323679: PL/SQL CALLING A USER DEFINED AGGREGRATE FUNCTION WITH DISTINCT FAILS ORA-30482.
Workaround for problems like this is using dynamic sql.
So if you wrap your query in
EXECUTE IMMEDIATE '<your_query>';
Then it should work.
But as OldProgrammer has suggested already, you better avoid using this WM_CONCAT at all.
PL/SQL will not let you to use distinct in an aggregated function, and this issue shows that the SQL-engine and the PL/SQL-engine do not use the same parser.
One of the solutions to this problem is to use sub query as below,
SELECT WM_CONCAT(employee_id)
INTO v_some_string
FROM (select DISTINCT employee_id
FROM employee)
WHERE ROWNUM < 20;
Another solution is to use dynamic SQL as Nagh suggested,
FUNCTION fetch_raw_data_by_range
RETURN VARCHAR2 IS
v_some_string VARCHAR2(32000);
v_sql VARCHAR2(200);
BEGIN
v_sql :='SELECT WM_CONCAT(DISTINCT employee_id)
FROM employee
WHERE ROWNUM < 20';
execute immediate v_sql INTO v_some_string;
RETURN v_some_string;
END;
Related
I need to get a query result like this:
|Person1 |Person2 |Person3 |...
------------------------------------------------------------------------------------
Date1 |function(Person1Id,Date1)|function(Person2Id,Date1)|function(Person3Id,Date1)|...
Date2 |function(Person1Id,Date2)|function(Person2Id,Date2)|function(Person3Id,Date2)|...
Date3 |function(Person1Id,Date3)|function(Person2Id,Date3)|function(Person3Id,Date3)|...
.
.
.
Dates are coming from the user and PersonIds are coming from a table. What I need to is just sending ids and dates o the function and get the result of it. Since I am working on oracle v10.2.0 pivoting does not work and writing case...when statements for each person will not work because there are lots of people in the table I am fetching.
Any help appreciated.
You can use Conditional Aggregation within DB version 10g such as
SELECT myDate,
MAX(CASE WHEN PersonId=1 THEN myfunc(PersonId,myDate) END) AS Person1,
MAX(CASE WHEN PersonId=2 THEN myfunc(PersonId,myDate) END) AS Person2,
MAX(CASE WHEN PersonId=3 THEN myfunc(PersonId,myDate) END) AS Person3
FROM t
GROUP BY myDate
Update : Yet, there exists an option, even in DB 10g for dynamic pivoting by using SYS_REFCURSOR, eg. using PL/SQL rather than using SQL only, and show the result set on the command line if you're using SQL Developer. Create a stored function
CREATE OR REPLACE FUNCTION get_person_rs RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_str VARCHAR2(32767);
BEGIN
WITH tt AS
(
SELECT PersonId,
ROW_NUMBER() OVER (ORDER BY PersonId) AS rn
FROM t
GROUP BY PersonId
)
SELECT TO_CHAR(RTRIM(XMLAGG(XMLELEMENT(e,
'MAX(CASE WHEN PersonId = '||PersonId||
' THEN myfunc(PersonId,myDate)
END) AS Person'||rn
, ',')).EXTRACT('//text()').GETCLOBVAL(), ','))
INTO v_str
FROM tt;
v_sql :=
'SELECT myDate, '|| v_str ||'
FROM t
GROUP BY myDate';
OPEN v_recordset FOR v_sql;
RETURN v_recordset;
END;
in which, ROW_NUMBER() Analytic function, which is available in 10g, is used, but LISTAGG() string aggregation function is not yet in 10g. So XMLAGG is used instead. This generated SQL string within the function is also exactly same as the above one, eg. in Conditionally Aggregated Logic.
Then run the below code :
VAR rc REFCURSOR
EXEC :rc := get_person_rs;
PRINT rc
from SQL Developer's Command Line in order to see the expected result set dynamically generated by currently existing data.
I have a code in oracle pl sql, want to really want to understand how much context switching is there
If x=0 then
curserx= select a from mytable1;
Else
curserx=select a from mytable1 where id=:x;
End;
Loop
Fetch on cursorx
Select c from mytable2 where a=curserx.a;
End loop;
This is just a sample code so please pardon any text casing and logic error.
I converted your pseudo code into PL/SQL and include comments indicating where I believe you will have a context switch from the PL/SQL engine to the SQL engine.
Note that if you are querying a non-trivial number of rows, you could use FETCH BULK COLLECT INTO and retrieve multiple rows with each fetch, greatly reducing context switches.
DECLARE
l_x_value INTEGER;
l_cursor SYS_REFCURSOR;
l_fetched mytble1.a%TYPE;
BEGIN
/* context switch to open */
IF x = 0
THEN
OPEN l_cursor FOR SELECT a FROM mytable1;
ELSE
OPEN l_cursor FOR
SELECT a
FROM mytable1
WHERE id = l_x_value;
END IF;
LOOP
/* context switch per fetch */
FETCH l_cursor INTO l_fetched;
EXIT WHEN l_cursor%NOTFOUND;
/* context switch for implicit cursor */
SELECT c
INTO l_fetched
FROM mytable2
WHERE a = curserx.a;
END LOOP;
/* context switch to close */
CLOSE l_cursor;
END;
But that's not all! Remember that the context switch works both ways: SQL -> PL/SQL and PL/SQL -> SQL. You can reduce the overhead of going from SQL to PL/SQL by declaring your function with the UDF pragma (12c+) or defining it with the WITH FUNCTION clause (also 12c+). There is still a context switch but some of the work is done at compile time instead of run time.
So in the code below, for each invocation of the function from within the SELECT, there is a switch.
CREATE OR REPLACE FUNCTION full_name (first_in IN VARCHAR2,
last_in IN VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
RETURN first_in || ' ' || last_in;
END;
/
DECLARE
l_name VARCHAR2 (32767);
BEGIN
SELECT full_name (first_name, last_name) INTO l_name
FROM employees
WHERE employee_id = 100;
DBMS_OUTPUT.PUT_LINE (l_name);
END;
/
Finally a cautionary note: you should do everything you can to avoid executing SQL inside a function that is then called from SQL. The standard read consistency model that works for your SQL statement will not be "carried in" to the function's SQL. In other words, if you "outer" SELECT starts running at 10:00 and runs for an hour, and at 10:05, someone deletes rows from a table that is used in both the outer query and the query in the function (and commits), those two queries will be working with different states of those tables.
create or replace PROCEDURE newprocedur(outname OUT VARCHAR2,outroll OUT NUMBER) AS
CURSOR c1 IS
select Name,Rollno,Section
from emp;
BEGIN
Open c1;
fetch c1 into outname,outroll;
Here out of 3 columns in cursor is it possible to fetch only two columns using FETCH like i did above??
You're on 11g. You don't need to define variables or a cursor. Let Oracle do the heavy lifting for you.
create or replace PROCEDURE newprocedur(outname OUT VARCHAR2,outroll OUT NUMBER)
AS
BEGIN
select Name,Rollno
into outname,outroll
from emp;
END;
This will raise an error if EMP has more than one row. There are various ways of fixing that. For instance, using ROWNUM to artificially limit the result set. Or taking EMP_ID as an input parameter to select the desired record. It depends on whether this is a toy example or the start of a real world API.
"how to use ROWNUM in above eg?"
ROWNUM is a pseudo-column, and is covered in the documentation. However, for the sake of completeness, we can use it to restrict a result set like this:
select Name,Rollno
from emp
where rownum <= 1;
I've a query that creates a SQL Statement as a field. I want to execute this statement and return the recordset in SSRS report.
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID=' ||FILE_ID||';'
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID and prior FIELD_NUMBER=FIELD_NUMBER-1
This generates a SQL stetment - however I want this SQL to be executed.
This is an extension of this question.
I've tried to use execute immediate , cursors, dbms_sql but it does not produce output. Using it on toad. All it says is "PL/SQL procedure successfully completed"
Using the following
Declare
sql_stmt VARCHAR2(3000);
l_cursor SYS_REFCURSOR;
TYPE RefCurTyp IS REF CURSOR;
v_cursor RefCurTyp;
CURSOR c1 is
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID=' ||FILE_ID||';'
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID and prior FIELD_NUMBER=FIELD_NUMBER-1;
BEGIN
open c1;
FETCH C1 into sql_stmt ;
dbms_output.put_line(sql_stmt);
close c1;
EXECUTE IMMEDIATE sql_stmt;
open v_cursor for sql_stmt;
return l_cursor;
close l_cursor ;
END;
An anonymous PL/SQL block cannot return any data to the caller. If you want to return a SYS_REFCURSOR to the calling application, you would need to create a function (or a procedure). For example
CREATE OR REPLACE FUNCTION get_results
RETURN sys_refcursor
IS
l_sql_stmt VARCHAR2(3000);
l_cursor SYS_REFCURSOR;
BEGIN
select 'select '||FILE_ID||' FILE_ID,'||
ltrim(sys_connect_by_path('REC_FLD_'||FIELD_NUMBER||' "'||FIELD_NAME||'"',','),',')||
' from RESPONSE_DETAILS where FILE_ID = ' ||FILE_ID||';'
into l_sql_stmt
from (select t.*,count(*) over (partition by FILE_ID) cnt from RESPONSE_METADATA t)
where cnt=FIELD_NUMBER
start with FIELD_NUMBER=1
connect by prior FILE_ID=FILE_ID
and prior FIELD_NUMBER=FIELD_NUMBER-1;
dbms_output.put_line(l_sql_stmt);
open l_cursor for sql_stmt;
return l_cursor;
END;
I am assuming from your code that you expect your SELECT statement to return a single SQL statement-- your code is fetching only one row from a query that potentially returns multiple SQL statements. I'm assuming that you only fetch one because you only expect the SELECT statement to return one row. Otherwise, since your query lacks an ORDER BY, you are executing arbitrarily one of N SQL statements that your code is generating.
If you are regularly going to be calling this method, you would almost certainly want to use bind variables in your dynamic SQL statement for the file_id rather than generating non-sharable SQL statements. I haven't made that change here.
There is another StackOverflow thread on calling a stored function returning a sys_refcursor from SSRS.
I wrote an Oracle function (for 8i) to fetch rows affected by a DML statement, emulating the behavior of RETURNING * from PostgreSQL. A typical function call looks like:
SELECT tablename_dml('UPDATE tablename SET foo = ''bar''') FROM dual;
The function is created automatically for each table and uses Dynamic SQL to execute a query passed as an argument. Moreover, a statement that executes the query dynamically is also wrapped in a BEGIN .. END block:
EXECUTE IMMEDIATE 'BEGIN'||query||' RETURNING col1, col2 BULK COLLECT INTO :1, :2;END;' USING OUT col1_t, col2_t;
The reason behind this perculiar construction is that it seems to be the only way to get values from the DML statement that affects multiple rows. Both col1_t and col2_t are declared as collections of the types corresponding to the table columns.
Finally, to the problem. When the query passed contains a subselect, execution of the function produces a syntax error. Below is a simpe example to illustrate this:
CREATE TABLE xy(id number, name varchar2(80));
CREATE OR REPLACE FUNCTION xy_fn(query VARCHAR2) RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'BEGIN '||query||'; END;';
ROLLBACK;
RETURN 5;
END;
SELECT xy_fn('update xy set id = id + (SELECT min(id) FROM xy)') FROM DUAL;
The last statement produces the following error: (the SELECT that is mentioned there is the SELECT min(id))
ORA-06550: line 1, column 32: PLS-00103: Encountered the symbol
"SELECT" when expecting one of the following: ( - + mod not null
others avg count current exists max min prior sql stddev sum
variance execute forall time timestamp interval date
This problem occurs on 8i (8.1.6), but not 10g.
If BEGIN .. END blocks are removed - the problem disappears.
If a subselect in a query is replaced with something else, i.e. a constant, the problem disappears.
Unfortunately, I'm stuck with 8i and removing BEGIN .. END is not an option (see the explanation above).
Is there a specific Oracle 8i limitation in play here? Is it possible to overcome it with dynamic SQL?
Not sure why you need to do all this work. Oracle 8i supported RETURNING INTO with bulk collection. Find out more
So you should just be able to execute this statement in non-dynamic SQL. Something like this:
UPDATE tablename
SET foo = 'bar'
returning col1, col2 bulk collect into col1_t, col2_t;
Stripped of all the irrelevancies, I think your question is simple.
This update statement runs in SQL:
update xy set id = id + (SELECT min(id) FROM xy);
And this anonymous block also runs:
begin
update xy set id = id + 100;
end;
But combining the two doesn't work:
begin
update xy set id = id + (SELECT min(id) FROM xy);
end;
Probably you have run into a limitation of older Oracle. Prior to 9i, the SQL engine and the PL/SQL SQL engine were always out of sync. So latest features supported in SQL often weren't supported in PL/SQL. It seems like you have one of those.
Since 9i Oracle have striven to keep the two engines in sync, so it is much rarer to find things which work in SQL but not in PL/SQL.
Given the nature of your task, upgrading your version of Oracle is out. So all I can suggest is that you have two procedures, one which supports the sub query syntax (by avoiding the need for such subqueries. Something like this:
CREATE OR REPLACE FUNCTION xy_sqfn
(main_query VARCHAR2
, sub_query VARCHAR2 )
RETURN NUMBER
IS
n pls_integer;
BEGIN
execute immediate sub_query into n;
EXECUTE IMMEDIATE 'BEGIN '||main_query||'; END;'
using n;
RETURN 5;
END;
call it like this
result := xy_sqfn ('update xy set id = id + :1'
, 'SELECT min(id) FROM xy');
Now this approach won't work for correlated sub-queries. So it you have any of them, you'll need to do something different again.
Incidentally, using the AUTONOMOUS TRANSACTION pragma to fudge executing DML in a SELECT statement is quite horrible. Why not just run the functions in PL/SQL? Or use procedures? I suppose you'll say it doesn't matter because you're just writing some shonky code to support a data migration. Which is fair enough, but for the benefit of future seekers: don't do this! It's very bad practice!