order by of nested table not taking variable value - oracle

SELECT CAST(MULTISET(SELECT *
FROM TABLE(table_a)
ORDER BY loc_sort_column DESC
) as table_a_type
)
INTO table_b
FROM dual;
I have this statement that tosses 1 collection of data into another nested table after sorting it.
The problem I am having is that it is not sorting. I have a variable loc_sort_column that in this case will be a integer/number between 1 and 11 selecting the column but it is not working.
I have stuck 1 in there and it works fine but it doesn't seem to like the variable that contains 1.
Is this an order of operation or something?

What you are doing is ordering by a constant. Following AskTom:
you cannot dynamically return a column position, you dynamically
returned A CONSTANT, A NUMBER, A CONSTANT VALUE - JUST A VALUE
order by < ordinal position> | < expression>
you are returning an expression - not an ordinal position.
What you can do is to use DECODE function to decode column position to real column. Please check my code sample:
CREATE OR REPLACE TYPE my_type FORCE AS OBJECT
(
col_1 NUMBER
,col_2 NUMBER
);
/
CREATE OR REPLACE TYPE my_nested_table AS TABLE of my_type;
/
DECLARE
l_table_a my_nested_table := my_nested_table();
l_table_b my_nested_table := my_nested_table();
l_sort_column NUMBER := 2;
BEGIN
l_table_a.extend(100);
FOR l_i IN 1..l_table_a.COUNT LOOP
l_table_a(l_i) := my_type(ROUND(DBMS_RANDOM.RANDOM,0), ROUND(DBMS_RANDOM.RANDOM,0));
END LOOP;
SELECT CAST(
MULTISET(
SELECT *
FROM TABLE(l_table_a)
ORDER BY DECODE(l_sort_column, 1, col_1
, 2, col_2) DESC
) AS my_nested_table
)
INTO l_table_b
FROM dual;
DBMS_OUTPUT.PUT_LINE('col_1 col_2');
FOR l_i IN 1..l_table_b.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(l_table_b(l_i).col_1 || ' ' || l_table_b(l_i).col_2);
END LOOP;
END;
/

Related

Oracle how to address column in PL/SQL FOR LOOP by it's position instead of column name

Is there a way to address column by it's position in PL/SQL, something like this
BEGIN
FOR r IN (SELECT * FROM table)
LOOP
DBMS_OUTPUT.PUT_LINE(r.(1));
END LOOP;
END;
I tried both bracket types but always get
PLS-00103: Encountered the symbol "(" when expecting one of the following
I also tried BULK COLECT INTO with table of varray but then I get
PLS-00642: local collection types not allowed in SQL statements
And if I declare table with %ROWTYPE and bulk it then it seems to behave the same way as the normal loop.
I tried to look into documentation but couldn't find any example of this scenario.
Using the DBMS_SQL package and the COLUMN_VALUE procedure: but that's a lot of work... probably overkilling for your actual needs: you have to open a sys_refcursor for the query, convert it into a cursor number with DBMS_SQL.TO_CURSOR_NUMBER, collect the descriptions of the columns with DBMS_SQL.DESCRIBE_COLUMNS and loop on the column count and fetch the value with DBMS_SQL.COLUMN_VALUE into a specific variable according to its type...
In general, no. Oracle does not support accessing records by position; only by identifier.
You can approach the problem slightly differently and, instead of using SELECT *, you can explicitly list the columns to select and give them numeric column aliases:
BEGIN
FOR r IN (SELECT dummy AS "1" FROM DUAL)
LOOP
DBMS_OUTPUT.PUT_LINE(r."1");
END LOOP;
END;
/
You are still referring to the columns by name; only now their names are the "numeric" aliases.
Note: Personally, I would stick to using the column names and not try to find work-arounds to use positions as positions can change if columns are deleted from the table.
If you want to have a matrix in PL/SQL you could define one as an indexed table of an indexed table of whatever data type you're dealing with, something like:
type t_row is table of varchar2(1) index by pls_integer;
type t_tab is table of t_row index by pls_integer;
l_matrix t_tab;
The tricky part is populating it. DBMS_SQL is certainly one approach, but partly for fun, you could use XML. The DBMS_XMLGEN package lets you run a query and get the results back as an XML document with a ROWSET root node. You can then count the ROW nodes under that, and assuming there are any, count the nodes within the first ROW - which gives the matrix dimensions.
You can then use nested loops to populate the matrix, and once done, you can refer to an element as for example l_matrix(3)(2).
This is a working example, where the table being queried has values that are all a single character, for simplicity; you would need to change the matrix definition to handle the data you expect.
DECLARE
-- elements of matrix need to be data type and size suitable for your data
type t_row is table of varchar2(1) index by pls_integer;
type t_tab is table of t_row index by pls_integer;
l_matrix t_tab;
l_xml xmltype;
l_rows pls_integer;
l_cols pls_integer;
BEGIN
l_xml := dbms_xmlgen.getxmltype('SELECT * FROM your_table');
SELECT XMLQuery('count(/ROWSET/ROW)'
PASSING l_xml
RETURNING CONTENT).getnumberval()
INTO l_rows
FROM DUAL;
IF l_rows = 0 THEN
dbms_output.put_line('No data');
RETURN;
END IF;
SELECT XMLQuery('count(/ROWSET/ROW[1]/*)'
PASSING l_xml
RETURNING CONTENT).getnumberval()
INTO l_cols
FROM DUAL;
dbms_output.put_line('Rows: ' || l_rows || ' Cols: ' || l_cols);
-- populate matrix
FOR i IN 1..l_rows LOOP
FOR j in 1..l_cols LOOP
l_matrix(i)(j) := l_xml.extract('/ROWSET/ROW[' || i || ']/*[' || j || ']/text()').getstringval();
END LOOP;
END LOOP;
-- refer to a specific element
dbms_output.put_line('Element 3,2 should be H; is actually: ' || l_matrix(3)(2));
-- display all elements as list
FOR i IN 1..l_matrix.COUNT LOOP
FOR j IN 1..l_matrix(i).COUNT LOOP
dbms_output.put_line('Row ' || i || ' col ' || j || ': ' || l_matrix(i)(j));
END LOOP;
END LOOP;
-- display all elements as grid
FOR i IN 1..l_matrix.COUNT LOOP
FOR j IN 1..l_matrix(i).COUNT LOOP
IF j > 0 THEN
dbms_output.put(' ');
END IF;
-- format/pad real data to align it; simple here with single-char values
dbms_output.put(l_matrix(i)(j));
END LOOP;
dbms_output.new_line;
END LOOP;
END;
/
With my sample table that outputs:
Rows: 4 Cols: 3
Element 3,2 should be H; is actually: H
Row 1 col 1: A
Row 1 col 2: B
Row 1 col 3: C
Row 2 col 1: D
Row 2 col 2: E
Row 2 col 3: F
Row 3 col 1: G
Row 3 col 2: H
Row 3 col 3: I
Row 4 col 1: J
Row 4 col 2: K
Row 4 col 3: L
A B C
D E F
G H I
J K L
fiddle
As #astenx said in a comment, you can use JSON instead of XML; which would allow you to populate the matrix like this.
That has raised something else I'd overlooked. My dummy table doesn't have an ID column or an obvious way of ordering the results. For the JSON version I used rownum, but in both that and the original query the order of the rows (in the result set, and thus in the matrix) is currently indeterminate. You need to have a column you can order by in the query, or some other way to determine the sequence of rows.

Issue selecting from Associative Array using a Table Collection Expression

I seem to be having an issue when using a Table Collection Expression to select from an Associative Array (Note: I am using Oracle 12c, so this is allowed: Oracle Documentation)
Take the following "simple" example:
First, create a package which declares a record and the associative array:
CREATE OR REPLACE PACKAGE table_test
IS
TYPE pt_DateSpan IS RECORD
(
StartDate DATE,
EndDate DATE
);
TYPE pt_DateSpanTable IS TABLE OF pt_DateSpan INDEX BY PLS_INTEGER;
END;
/
Then, I wrote the following anonymous block to test the functionality:
DECLARE
l_tTest table_test.pt_DateSpanTable;
PROCEDURE lp_outputAArray (p_aaInput table_test.pt_DateSpanTable) IS
l_nTableSize INTEGER;
BEGIN
--I know I can use p_aaInput.COUNT, but I want to select using TABLE() to show that the functionality "works"
SELECT COUNT(*)
INTO l_nTableSize
FROM TABLE(p_aaInput);
dbms_output.put_line('Table Size: '||l_nTableSize);
FOR i IN 1..p_aaInput.COUNT LOOP
dbms_output.put_line(i||': '||to_char(p_aaInput(i).StartDate, 'MM/DD/YYYY')||' - '||to_char(p_aaInput(i).EndDate, 'MM/DD/YYYY'));
END LOOP;
END lp_outputAArray;
BEGIN
--ADD RECORD TO ASSOCIATIVE ARRAY
SELECT to_date('01/01/2000', 'MM/DD/YYYY'), to_date('01/01/2010', 'MM/DD/YYYY')
BULK COLLECT INTO l_tTest
FROM DUAL;
lp_outputAArray(l_tTest);
--SELECT THE ASSOCIATIVE ARRAY INTO ITSELF
SELECT t.StartDate, t.EndDate
BULK COLLECT INTO l_tTest
FROM TABLE(l_tTest) t;
lp_outputAArray(l_tTest);
END;
/
This block produces the following output:
Table Size: 1
1: 01/01/2000 - 01/01/2010
Table Size: 0
My question is why is the second output not identical to the first?
Also, I realize that I don't need to use BULK COLLECT in most of this example, it is a simplified version of my actual code which does SELECT from actual tables.
My final goal was to use UNION ALL to allow me to append values to my Associative Array instead of replacing it when performing a series of SELECT statements. Something like this:
SELECT *
BULK COLLECT INTO l_tTest
FROM (SELECT t.StartDate, t.EndDate
FROM TABLE(l_tTest) t
UNION ALL
SELECT to_date('01/01/2011', 'MM/DD/YYYY'), to_date('01/01/2019', 'MM/DD/YYYY')
FROM DUAL);
I would appreciate any help you could provide.
It appears that when you use:
SELECT ...
BULK COLLECT INTO array
FROM ...
Then the first thing that happens is that the array you BULK COLLECT INTO is re-initialised to an empty array.
Therefore, when you want to use it in the table collection expression it is already empty and no rows are generated.
Instead, you could use a non-associative array and use the MULTISET operators in PL/SQL:
CREATE OR REPLACE PACKAGE table_test
IS
TYPE range IS RECORD
(
StartDate DATE,
EndDate DATE
);
TYPE range_table IS TABLE OF range
--INDEX BY PLS_INTEGER
;
END;
/
DECLARE
l_ranges table_test.range_table := table_test.range_table();
l_ranges2 table_test.range_table := table_test.range_table();
PROCEDURE output_ranges(
range_array table_test.range_table
)
IS
idx PLS_INTEGER;
BEGIN
dbms_output.put_line('Table Size: '||range_array.COUNT);
idx := range_array.FIRST;
LOOP
EXIT WHEN idx IS NULL;
dbms_output.put_line(
idx||': '||range_array(idx).StartDate||' - '||range_array(idx).EndDate
);
idx := range_array.NEXT(idx);
END LOOP;
END output_ranges;
BEGIN
l_ranges.EXTEND(2);
l_ranges(1) := table_test.range(DATE '2000-01-01', DATE '2001-01-01');
l_ranges(2) := table_test.range(DATE '2001-01-01', DATE '2002-01-01');
l_ranges2.EXTEND(2);
l_ranges2(1) := table_test.range(DATE '2002-01-01', DATE '2003-01-01');
l_ranges2(2) := table_test.range(DATE '2003-01-01', DATE '2004-01-01');
output_ranges(l_ranges);
output_ranges(l_ranges2);
l_ranges := l_ranges MULTISET UNION ALL l_ranges2;
output_ranges(l_ranges);
END;
/
Which outputs:
Table Size: 2
1: 01-JAN-00 - 01-JAN-01
2: 01-JAN-01 - 01-JAN-02
Table Size: 2
1: 01-JAN-02 - 01-JAN-03
2: 01-JAN-03 - 01-JAN-04
Table Size: 4
1: 01-JAN-00 - 01-JAN-01
2: 01-JAN-01 - 01-JAN-02
3: 01-JAN-02 - 01-JAN-03
4: 01-JAN-03 - 01-JAN-04

PL/SQL : Need to compare data for every field in a table in plsql

I need to create a procedure which will take collection as an input and compare the data with staging table data row by row for every field (approx 50 columns).
Business logic :
whenever a staging table column value will mismatch with the corresponding collection variable value then i need to update 'FAIL' into staging table STATUS column and reason into REASON column for that row.
If matched then need to update 'SUCCESS' in STATUS column.
Payload will be approx 500 rows in each call.
I have created below sample script:
PKG Specification :
CREATE OR REPLACE
PACKAGE process_data
IS
TYPE pass_data_rec
IS
record
(
p_eid employee.eid%type,
p_ename employee.ename%type,
p_salary employee.salary%type,
p_dept employee.dept%type
);
type p_data_tab IS TABLE OF pass_data_rec INDEX BY binary_integer;
PROCEDURE comp_data(inpt_data IN p_data_tab);
END;
PKG Body:
CREATE OR REPLACE
PACKAGE body process_data
IS
PROCEDURE comp_data (inpt_data IN p_data_tab)
IS
status VARCHAR2(10);
reason VARCHAR2(1000);
cnt1 NUMBER;
v_eid employee_copy.eid%type;
v_ename employee_copy.ename%type;
BEGIN
FOR i IN 1..inpt_data.count
LOOP
SELECT ec1.eid,ec1.ename,COUNT(*) over () INTO v_eid,v_ename,cnt1
FROM employee_copy ec1
WHERE ec1.eid = inpt_data(i).p_eid;
IF cnt1 > 0 THEN
IF (v_eid=inpt_data(i).p_eid AND v_ename = inpt_data(i).p_ename) THEN
UPDATE employee_copy SET status = 'SUCCESS' WHERE eid = inpt_data(i).p_eid;
ELSE
UPDATE employee_copy SET status = 'FAIL' WHERE eid = inpt_data(i).p_eid;
END IF;
ELSE
NULL;
END IF;
END LOOP;
COMMIT;
status :='success';
EXCEPTION
WHEN OTHERS THEN
status:= 'fail';
--reason:=sqlerrm;
END;
END;
But in this approach i have below mentioned issues.
Need to declare all local variables for each column value.
Need to compare all variable data using 'and' operator. Not sure whether it is correct way or not because if there are 50 columns then if condition will become very heavy.
IF (v_eid=inpt_data(i).p_eid AND v_ename = inpt_data(i).p_ename) THEN
Need to update REASON column when any column data mismatched (first mismatched column name) for that row, in this approach i am not able to achieve.
Please suggest any other good way to achieve this requirement.
Edit :
There is only one table at my end i.e target table. Input will come from any other source as collection object.
REVISED Answer
You could load the the records into t temp table, but unless you want additional processing it's not necessary. AFAIK there is no way to identify the offending column (first one only) without slugging through column-by-column. However, your other concern having to declare a variable is not necessary. You can declare a single variable defined as %rowtype which gives you access to each column by name.
Looping through an array of data to find the occasional error is just bad (imho) with SQL available to eliminate the good ones in one fell swoop. And it's available here. Even though your input is a array we can use as a table by using the TABLE operator, which allows an array (collection) as though it were a database table. So the MINUS operator can till be employed. The following routine will set the appropriate status and identify the first miss matched column for each entry in the input array. It reverts to your original definition in package spec, but replaces the comp_data procedure.
create or replace package body process_data
is
procedure comp_data (inpt_data in p_data_tab)
is
-- define local array to hold status and reason for ecah.
type status_reason_r is record
( eid employee_copy.eid%type
, status employee_copy.status%type
, reason employee_copy.reason%type
);
type status_reason_t is
table of status_reason_r
index by pls_integer;
status_reason status_reason_t := status_reason_t();
-- define error array to contain the eid for each that have a mismatched column
type error_eids_t is table of employee_copy.eid%type ;
error_eids error_eids_t;
current_matched_indx pls_integer;
/*
Helper function to identify 1st mismatched column in error row.
Here is where we slug our way through each column to find the first column
value mismatch. Note: There is actually validate the column sequence, but
for purpose here we'll proceed in the input data type definition.
*/
function identify_mismatch_column(matched_indx_in pls_integer)
return varchar2
is
employee_copy_row employee_copy%rowtype;
mismatched_column employee_copy.reason%type;
begin
select *
into employee_copy_row
from employee_copy
where employee_copy.eid = inpt_data(matched_indx_in).p_eid;
-- now begins the task of finding the mismatched column.
if employee_copy_row.ename != inpt_data(matched_indx_in).p_ename
then
mismatched_column := 'employee_copy.ename';
elsif employee_copy_row.salary != inpt_data(matched_indx_in).p_salary
then
mismatched_column := 'employee_copy.salary';
elsif employee_copy_row.dept != inpt_data(matched_indx_in).p_dept
then
mismatched_column := 'employee_copy.dept';
-- elsif continue until ALL columns tested
end if;
return mismatched_column;
exception
-- NO_DATA_FOUND is the one error that cannot actually be reported in the customer_copy table.
-- It occurs when an eid exista in the input data but does not exist in customer_copy.
when NO_DATA_FOUND
then
dbms_output.put_line( 'Employee (eid)='
|| inpt_data(matched_indx_in).p_eid
|| ' does not exist in employee_copy table.'
);
return 'employee_copy.eid ID is NOT in table';
end identify_mismatch_column;
/*
Helper function to find specified eid in the initial inpt_data array
Since the resulting array of mismatching eid derive from a select without sort
there is no guarantee the index values actually match. Nor can we sort to build
the error array, as there is no way to know the order of eid in the initial array.
The following helper identifies the index value in the input array for the specified
eid in error.
*/
function match_indx(eid_in employee_copy.eid%type)
return pls_integer
is
l_at pls_integer := 1;
l_searching boolean := true;
begin
while l_at <= inpt_data.count
loop
exit when eid_in = inpt_data(l_at).p_eid;
l_at := l_at + 1;
end loop;
if l_at > inpt_data.count
then
raise_application_error( -20199, 'Internal error: Find index for ' || eid_in ||' not found');
end if;
return l_at;
end match_indx;
-- Main
begin
-- initialize status table for each input enter
-- additionally this results is a status_reason table in a 1:1 with the input array.
for i in 1..inpt_data.count
loop
status_reason(i).eid := inpt_data(i).p_eid;
status_reason(i).status :='SUCCESS';
end loop;
/*
We can assume the majority of data in the input array is valid meaning the columns match.
We'll eliminate all value rows by selecting each and then MINUSing those that do match on
each column. To accomplish this cast the input with TABLE function allowing it's use in SQL.
Following produces an array of eids that have at least 1 column mismatch.
*/
select p_eid
bulk collect into error_eids
from (select p_eid, p_ename, p_salary, p_dept from TABLE(inpt_data)
minus
select eid, ename, salary, dept from employee_copy
) exs;
/*
The error_eids array now contains the eid for each miss matched data item.
Mark the status as failed, then begin the long hard process of identifying
the first column causing the mismatch.
The following loop used the nested functions to slug the way through.
This keeps the main line logic clear.
*/
for i in 1 .. error_eids.count -- if all inpt_data rows match then count is 0, we bypass the enttire loop
loop
current_matched_indx := match_indx(error_eids(i));
status_reason(current_matched_indx).status := 'FAIL';
status_reason(current_matched_indx).reason := identify_mismatch_column(current_matched_indx);
end loop;
-- update employee_copy with appropriate status for each row in the input data.
-- Except for any cid that is in the error eid table but doesn't exist in the customer_copy table.
forall i in inpt_data.first .. inpt_data.last
update employee_copy
set status = status_reason(i).status
, reason = status_reason(i).reason
where eid = inpt_data(i).p_eid;
end comp_data;
end process_data;
There are a couple other techniques used you may want to look into if you are not familiar with them:
Nested Functions. There are 2 functions defined and used in the procedure.
Bulk Processing. That is Bulk Collect and Forall.
Good Luck.
ORIGINAL Answer
It is NOT necessary to compare each column nor build a string by concatenating. As you indicated comparing 50 columns becomes pretty heavy. So let the DBMS do most of the lifting. Using the MINUS operator does exactly what you need.
... the MINUS operator, which returns only unique rows returned by the
first query but not by the second.
Using that this task needs only 2 Updates: 1 to mark "fail", and 1 to mark "success". So try:
create table e( e_id integer
, col1 varchar2(20)
, col2 varchar2(20)
);
create table stage ( e_id integer
, col1 varchar2(20)
, col2 varchar2(20)
, status varchar2(20)
, reason varchar2(20)
);
-- create package spec and body
create or replace package process_data
is
procedure comp_data;
end process_data;
create or replace package body process_data
is
package body process_data
procedure comp_data
is
begin
update stage
set status='failed'
, reason='No matching e row'
where e_id in ( select e_id
from (select e_id, col1, col2 from stage
except
select e_id, col1, col2 from e
) exs
);
update stage
set status='success'
where status is null;
end comp_data;
end process_data;
-- test
-- populate tables
insert into e(e_id, col1, col2)
select (1,'ABC','def') from dual union all
select (2,'No','Not any') from dual union all
select (3,'ok', 'best ever') from dual union all
select (4,'xx','zzzzzz') from dual;
insert into stage(e_id, col1, col2)
select (1,'ABC','def') from dual union all
select (2,'No','Not any more') from dual union all
select (4,'yy', 'zzzzzz') from dual union all
select (5,'no e','nnnnn') from dual;
-- run procedure
begin
process_data.comp_date;
end;
-- check results
select * from stage;
Don't ask. Yes, you to must list every column you wish compared in each of the queries involved in the MINUS operation.
I know the documentation link is old (10gR2), but actually finding Oracle documentation is a royal pain. But the MINUS operator still functions the same in 19c;

looping through an array for the where condition pl/sql

Is it possible in pl/sql to loop through a number of id's that need to go in the WHERE clause of the pl/sql statement. The sql statement itself is pretty simple, but I need to iterate over a number of id's:
SELECT x_name
FROM table_x
WHERE x_id = {array of 90 id's};
How can I insert the 90 id's here so that sql iterates over them? I tried using the cursor For Loop, but I'm stuck. The code below is erroneous, but it might give an indication what Im trying to achieve here
DECLARE
TYPE x_id_array IS VARRAY(3) OF NUMBER;
CURSOR cur_x_id (x_ondz_id NUMBER) IS
SELECT x_name
FROM table_x
WHERE x_id = var_ondz_id;
loop_total integer;
x_id x_id_array;
name VARCHAR;
BEGIN
x_id_new := x_id_array(8779254, 8819930, 8819931); --3 for testing
loop_total := x_id_new.count;
FOR i in 1 .. loop_total LOOP
dbms_output.put_line('x_id: ' || x_id_new(i) || '= Name: ' || x_name );
END LOOP;
END;
/
The expected out put would be
x_id: 8779254= Name: Name_1
x_id: 8819930= Name: Name_2
x_id: 8819931= Name: Name_3
...
... etc for all 90 id's in the array
Any help is appreciated
We can use TABLE function on a collection to get a list of numbers / character.
SELECT *
FROM TABLE ( sys.odcinumberlist(8779254,8819930,8819931) );
8779254
8819930
8819931
Here I'm using Oracle's internal VARRAY with a limit of 32767. You may use your own NESTED TABLE type.
create OR REPLACE TYPE yourtype AS TABLE OF NUMBER;
and then select it.
SELECT *
FROM TABLE ( yourtype(8779254,8819930,8819931) );
So, your query can simply be written as
SELECT x_name
FROM table_x
WHERE x_id IN ( SELECT * FROM
TABLE ( yourtype(8779254,8819930,8819931) ) );
12.2 and above, you won't even need to specify TABLE.
SELECT * FROM yourtype(8779254,8819930,8819931) works.

PL/SQL - How to concatenate values from different queries into one resultset?

Image I have the following queries in a package:
SELECT col1 FROM table WHERE id=5;
SELECT otherCol FROM otherTable WHERE id=78;
I want to return one record with two columns containing the values 'col1' and 'otherCol'.
In my case, I'd have lots of subqueries and DECODE statements so for readability I want to split it up into different queries something ala:
var result = {}; -- Unfortunately PL/SQL doesn't cope very well with JavaScript.
SELECT col1 INTO someVar FROM table WHERE id=5;
IF someVar = 1 THEN
result.X = SomeQuery();
ELSE
result.X = SomeEntirelyDifferentQuery();
END IF;
SELECT col INTO result.Z FROM tab1;
SELECT coz INTO result.Q FROM tab2;
IF result.Z IS NULL THEN
result.B = Query1();
ELSE
result.B = Query2();
END IF;
... and so on.
RETURN result;
So basically I want to create an "empty record" and then based on some conditions assign different values to columns in that record
This could be solved with DECODE but the resulting query is both not very readable nor very performant.
You have to define an object type the function can return.
CREATE OR REPLACE TYPE fancy_function_result_row as object
(
X number,
Y number,
Q date,
Z varchar2(30)
);
create or replace function fancy_function(...)
return fancy_function_result_row
as
my_record fancy_function_result_row := fancy_function_result_row();
begin
my_record.X := 1;
etc...
end;
If you want to insert the record into a table, it might be a lot easier simply defining a rowtype of that table.
declare
row_my_table my_table%rowtype;
begin
row_my_table.X := etc..
insert into my_table values row_my_table;
end;
--To concat the values 'col1' and 'otherCol':
with r1 as (select col1 from table where id=5),
r2 as (select otherCol from otherTable WHERE id=78),
select r1.col1 || ' concat with ' || r2.othercol from r1, r2
--To do this condition using DECODE:
SELECT DECODE (col1,
1,
(SELECT 1 FROM query1),
(SELECT 1 FROM DifferentQuery)
)
FROM TABLE

Resources