Pivotting on oracle v10.2.0 with two dynamic values - oracle

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.

Related

PL/SQL - pass query in for loop stored in variable

i have a scenario where i need to create a dynamic query with some if/else statements, i have prepared the query but unable to use this inside loop below is the snippet of what i am trying to acheive.
Query :
select user_name into username from table1 where user ='user1';
for(query)
Loop
Dbms_output.put_line('user name ' || user_name);
END Loop;
is this possible to use the vaiable in the for loop ?
Not really. Notice in the 19c PL/SQL Reference it says:
cursor
Name of an explicit cursor (not a cursor variable) that is not open when the cursor FOR LOOP is entered.
You have to code this the long way, by explicitly fetching the cursor into a record until you hit cursorname%notfound, e.g.
create or replace procedure cheese_report
( cheese_cur in sys_refcursor )
as
type cheese_detail is record
( name varchar2(15)
, region varchar2(30) );
cheese cheese_detail;
begin
loop
fetch cheese_cur into cheese;
dbms_output.put_line(cheese.name || ', ' || cheese.region);
exit when cheese_cur%notfound;
end loop;
close cheese_cur;
end;
Test:
declare
cheese sys_refcursor;
begin
open cheese for
select 'Cheddar', 'UK' from dual union all
select 'Gruyere', 'France' from dual union all
select 'Ossau Iraty', 'Spain' from dual union all
select 'Yarg', 'UK' from dual;
cheese_report(cheese);
end;
/
Cheddar, UK
Gruyere, France
Ossau Iraty, Spain
Yarg, UK
In Oracle 21c you can simplify this somewhat, though you still have to know the structure of the result set:
create or replace procedure cheese_report
( cheese_cur in sys_refcursor )
as
type cheese_detail is record
( cheese varchar2(15)
, region varchar2(30) );
begin
for r cheese_detail in values of cheese_cur
loop
dbms_output.put_line(r.cheese || ', ' || r.region);
end loop;
end;
You could parse an unknown ref cursor using dbms_sql to find the column names and types, but it's not straightforward as you have to do every processing step yourself. For an example of something similar, see www.williamrobertson.net/documents/refcursor-to-csv.shtml.

Searching LONG datatype in Oracle

I am looking for a way to search columns with a LONG datatype.
I know those are deprecated (and I've always hated working with them...), but for some reason Oracle themselves continue to use them in their own tables and views...
Basically I want to build a query on SYS.USER_TAB_SUBPARTITIONS with the WHERE-clause filtering a specific HIGH_VALUE.
HIGH_VALUE is of the LONG datatype and the only way I know to filter those things, is by using the undocumented function dbms_metadata_util.long2varchar
When executing a query with this function however, the returned value is NULL.
select sys.dbms_metadata_util.long2varchar(2000,'SYS.USER_TAB_SUBPARTITIONS','HIGH_VALUE', rowid) from USER_TAB_SUBPARTITIONS;
This is most likely because USER_TAB_SUBPARTITIONS is not actually a table, but a view. And views don't have rowids...
However, it seems to be a strange kind of view, as its definition does not show any underlying base table. Instead it just creates a synonym on itself.
So, to my actual question(s): Is there any other way to query LONG? Does anybody know the "base table" of USER_TAB_SUBPARTITIONS?
Yes, data type LONG in Oracle System-Views is a pain. When I have to use such values I use this one:
DECLARE
high_value INTEGER;
BEGIN
FOR aPart IN (SELECT * FROM USER_TAB_SUBPARTITIONS) LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT high_value;
SELECT ...
WHERE ... = high_value;
end loop;
END;
Note, in this example HIGH_VALUE is an integer value. However, it can be anything else (e.g. a TIMESTAMP), consider this in your procedure. For example like this:
FUNCTION IntervalType(tableName IN VARCHAR2) RETURN VARCHAR2 IS
EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);
ds INTERVAL DAY TO SECOND;
ym INTERVAL YEAR TO MONTH;
str VARCHAR2(1000);
BEGIN
SELECT INTERVAL
INTO str
FROM USER_PART_TABLES
WHERE TABLE_NAME = tableName;
EXECUTE IMMEDIATE 'BEGIN :ret := '||str||'; END;' USING OUT ym;
RETURN 'YEAR TO MONTH Interval of '||ym;
EXCEPTION
WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
EXECUTE IMMEDIATE 'BEGIN :ret := '||str||'; END;' USING OUT ds;
RETURN 'DAY TO SECOND Interval of '||ds;
END IntervalType;
If you query ALL_VIEWS or DBA_VIEWS, you will find the definition of the view USER_TAB_SUBPARTITIONS
SELECT TEXT
FROM all_views
WHERE view_name = 'USER_TAB_SUBPARTITIONS';
You will see that the HIGH_VALUE comes from hiboundval column of sys.tabsubpart$.
There is one other way we use to extract the HIGH_VALUE . You may use SUBSTR() to extract the exact values from the extracted HIGH_VALUE.
DECLARE
v_high_value VARCHAR2(100);
BEGIN
SELECT EXTRACTVALUE (
DBMS_XMLGEN.GETXMLTYPE (
'SELECT high_value
FROM all_tab_partitions
WHERE partition_name='''
|| YOUR_PARTITION_NAME
|| '''
AND table_owner='''
|| YOUR_TABLE_OWNER
|| '''
AND table_name='''
|| YOUR_TABLE
|| ''''),
'ROWSET/ROW/HIGH_VALUE') INTO v_high_value
FROM DUAL;
END;
/
You may refer Ask TOM article here

Returning Count of records from sys_refcursor in Oracle

Oracle Version : 11.2.0.2
I have a table ABC which has n number of columns , out of which I need COUNT of status, age and type columns individually to be given as OUT parameters to other application.
My Other application ( web application ) would display these as below:
STATUS 10
AGE 05
TYPE 20
where in 10 , 05 , 20 are COUNT of values from table ABC which I need to pass on to the web application.
It sounds like you want to us a SELECT statement along the lines of:
SELECT COUNT(DISTINCT STATUS) AS STATUS_COUNT,
COUNT(DISTINCT AGE) AS AGE_COUNT,
COUNT(DISTINCT TYPE) AS TYPE_COUNT
FROM YOUR_TABLE
Alternatively, if you just want a count of the values, use
SELECT COUNT(STATUS) AS STATUS_COUNT,
COUNT(AGE) AS AGE_COUNT,
COUNT(TYPE) AS TYPE_COUNT
FROM YOUR_TABLE
Best of luck.
In this case there can be two approaches which can be insorporated to satisfy your goal.
1) Using ref cursor to OUT the count's
2) Using scalar variables to OUT the params.
Since i dont have workspace with me please pardon any syntax error.
--1) Using refcursor
CREATE OR REPLACE PROCEDURE test_out_ref(
p_ref_out OUT sys_refcursor )
AS
BEGIN
OPEN p_ref_out FOR
SELECT COUNT(DISTINCT STATUS) STATUS_COUNT,
COUNT(DISTINCT AGE) AGE_COUNT,
COUNT(DISTINCT TYPE) TYPE_COUNT
FROM YOUR_TABLE ;
END;
--2 nd approach may be just passing the count value to scalar variables as OUT param
CREATE OR REPLACE PROCEDURE test_out_ref(
p_status_cnt_out OUT NUMBER,
p_age_cnt_out OUT NUMBER,
p_type_cnt_out OUT NUMBER)
AS
BEGIN
SELECT COUNT(DISTINCT STATUS) STATUS_COUNT,
COUNT(DISTINCT AGE) AGE_COUNT,
COUNT(DISTINCT TYPE) TYPE_COUNT
INTO
p_status_cnt_out,
p_age_cnt_out,
p_type_cnt_out
FROM YOUR_TABLE ;
END;
If suddenly someone are looking for how to get only a number of rows in sys_refcursor
without knowing the structure of the data of that cursor, then there is simplest and neat solution ever:
declare
l_cursor sys_refcursor;
l_cur_id number;
l_rowcount number := 0;
begin
l_cursor := some_function_returning_cursor();
-- solution starts
l_cur_id := DBMS_SQL.TO_CURSOR_NUMBER(l_cursor);
WHILE DBMS_SQL.FETCH_ROWS(l_cur_id) > 0 LOOP
l_rowcount := l_rowcount + 1;
end loop;
-- solution ends
dbms_output.put_line('number of rows is ' || l_rowcount);
end;
Note that after this you will not able to read from cursor because it fetched/closed already.
Documentation:
DBMS_SQL.TO_CURSOR_NUMBER https://docs.oracle.com/database/121/ARPLS/d_sql.htm#ARPLS68279
DBMS_SQL.FETCH_ROWS https://docs.oracle.com/database/121/ARPLS/d_sql.htm#ARPLS68267

WM_CONCAT with DISTINCT Clause - Compiled Package versus Stand-Alone Query Issue

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;

Dynamic query to get dynamic columns from rows

I have been trying to create PL/SQL statement that creates a dynamic query in order to get dynamic columns. Since I don't have much idea about these Oracle PL/SQL statements; I am confused about few things.
Is it mandatory to have stored procedure for creating dynamic queries ?
The following query does not throw any error and even results nothing. What I am trying to do in the following query is to get sum of FKOD_AMOUNT according to FKBAB_SOURCE_ID which is foreign key for PFS_SOURCE_ID.
declare
sql_query varchar2(3000) := 'select FKOM_OFFICE_ID,FKBAM_BUDGET_ID ';
begin
for x in (select distinct PFS_SOURCE_ID,PFS_SOURCE_ENG from PBS_FC_SOURCE WHERE PFS_UPPER_SOURCE_ID!=0 )
loop
sql_query := sql_query ||
' , sum(case when FKBAB_SOURCE_ID = '||x.PFS_SOURCE_ID||' then FKOD_AMOUNT ELSE 0 end) as '||x.PFS_SOURCE_ENG;
dbms_output.put_line(sql_query);
end loop;
sql_query := sql_query || ' FROM FMS_K_OFFICEWISE_DTL
JOIN FMS_K_OFFICEWISE_MST ON FMS_K_OFFICEWISE_MST.FKOM_OFFICE_MST_ID=FMS_K_OFFICEWISE_DTL.FKOD_OFFICE_MST_ID
JOIN FMS_K_BUDGET_ALLOCATION_DTL ON FMS_K_BUDGET_ALLOCATION_DTL.FKBAD_BUDGET_ALLOC_DTL_ID=FMS_K_OFFICEWISE_DTL.FKOD_BUDGET_ALLOC_AD_ID
JOIN FMS_K_BUDGET_ALLOCATION_MST ON FMS_K_BUDGET_ALLOCATION_MST.FKBAM_BUDGET_ALLOC_ID=FMS_K_BUDGET_ALLOCATION_DTL.FKBAB_BUDGET_ALLOC_ID
JOIN PBS_FC_BUDGET ON PBS_FC_BUDGET.PFB_BUDGET_ID=FMS_K_BUDGET_ALLOCATION_MST.FKBAM_BUDGET_ID
GROUP BY FKOM_OFFICE_ID,FKBAM_BUDGET_ID ';
dbms_output.put_line(sql_query);
end;
How can I execute 'sql_query' ?
To have an answer(removed my comments), this is pl/sql, not a select statement, so you can't just run it.
With execute immediate you can use INTO clause to store the results into some variables or arrays.
But you may make the query a cursor, put the cursor into a pipelined function and then
select * from table(your_pipelined_function)

Resources