(Oracle)How to test user function convenently without making another table - oracle

To test function 'test2' below, I have tried like these.
SELECT test2('A') FROM DUAL;SELECT test2('C') FROM DUAL;SELECT test2('E') FROM DUAL;
But is there a convenient way to do this at once ? (without making another table)
I guess query might look like this
SELECT test2(p.c1) FROM ( .... ) p ?
Table and function as below
CREATE TABLE T2 (
C1 VARCHAR2(1),
C2 NUMBER
);
INSERT INTO T2 VALUES ('A',1);
INSERT INTO T2 VALUES ('B',4);
INSERT INTO T2 VALUES ('C',3);
INSERT INTO T2 VALUES ('D',2);
INSERT INTO T2 VALUES ('E',4);
CREATE OR REPLACE FUNCTION test2
(p1 IN VARCHAR2)
RETURN NUMBER AS V_VALUE NUMBER;
BEGIN
SELECT(
SELECT C2
FROM T2
WHERE C1=p1)
INTO V_VALUE
FROM DUAL;
RETURN V_VALUE;
END;
/

I think you might be after something like:
WITH test_data AS (SELECT 'A' val FROM dual UNION ALL
SELECT 'B' val FROM dual UNION ALL
SELECT 'C' val FROM dual UNION ALL
SELECT 'D' val FROM dual UNION ALL
SELECT 'E' val FROM dual)
SELECT test2(val)
FROM test_data;
Using select ... from dual union all select ... from dual ... is a great way of setting up temporary test data for simple tests, without needing to create extra objects to hold the data.

Related

How to assign multiple values to a variable using into clause in oracle query?

I have an oracle stored procedure like this
CREATE OR REPLACE PROCEDURE DEMO (V_IN CHAR, V_OUT VARCHAR2)
IS
BEGIN
FOR ITEM IN LOOP (SELECT DISTINCT (NAME)
FROM TABLE1 INTO V_OUT
WHERE ID = V_IN
LOOP
--CODE TO PRINT V_OUT
END LOOP;
END;
Now how should I create that V_OUT variable so that it can hold all the values coming from query? I'm doing this in oracle12C.
You don't put the INTO clause in the cursor query. And even if you did, you have it in the wrong place in the SQL statement.
You deal with that when you fetch a row from the query:
CREATE OR REPLACE PROCEDURE
DEMO (V_IN CHAR, V_OUT
VARCHAR2)IS
BEGIN
FOR ITEM IN LOOP
(SELECT DISTINCT (NAME)
FROM TABLE1
WHERE ID= V_IN
)
LOOP
dbms_output.put_line(item.name);
v_out := item.name;
END LOOP;
END;
But then the problem is that we just keep overlaying the previous value, so that when your procedure actually exits, the only value of v_out is that last assigned. If you truely need a collection of values, you need to declare your output variable to be a ref cursor, and adjust code accordingly. I've never actually worked with them, so perhaps someone else will chime in.
You can work with collections, like this:
--declare the pakage type
CREATE OR REPLACE PACKAGE PKG_TYPES AS
TYPE LIST_VARCHAR IS TABLE OF VARCHAR2(2000);
END;
--create the proc that will assemble the value list
CREATE OR REPLACE PROCEDURE DEMO ( V_IN IN varchar2, V_OUT IN OUT PKG_TYPES.LIST_VARCHAR) IS
BEGIN
FOR ITEM IN (
SELECT DISTINCT (NAME) name
FROM (SELECT 'X' ID, 'A' name FROM dual
UNION
SELECT 'X' ID, 'b' name FROM dual
UNION
SELECT 'y' ID, 'c' name FROM dual
) TABLE1
WHERE ID= V_IN
)
LOOP
V_OUT.EXTEND;
V_OUT(V_OUT.LAST) := item.name;
--CODE TO PRINT V_OUT
END LOOP;
END;
--use the list. I separated this step but it can be in the demo proc as well
DECLARE
names PKG_TYPES.LIST_VARCHAR := PKG_TYPES.LIST_VARCHAR();
BEGIN
demo('X',names) ;
FOR i IN names.first..names.last LOOP
Dbms_Output.put_line(i);
END LOOP;
END;
You will have to handle exceptions for when no value is returned from the cursor (when no ID is found).
If you need a collection variable - you can use a nested table and bulk collect like below.
To be able to return the value from the procedure you will need to declare the nested table type in some package or on DB schema level.
declare
type test_type is table of varchar2(2000);
test_collection test_type;
begin
select distinct(name) bulk collect into test_collection
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1;
for i in test_collection.first..test_collection.last loop
dbms_output.put_line(test_collection(i));
end loop;
end;
/
If you just need a string with concatenated values - you can use listagg to create it like below
declare
test_str varchar2(4000);
begin
select listagg(name, ', ') within group(order by 1)
into test_str
from (
select distinct name
from (
select 1 id, 'AAA' name from dual
union all
select 1 id, 'BBB' name from dual
union all
select 1 id, 'AAA' name from dual
union all
select 2 id, 'CCC' name from dual
)
where id = 1
);
dbms_output.put_line(test_str);
end;
/

Get XMLType from a cursor

Anyone knows if I can generate a XMLType from a cursor without having to specify each row's name manually ?
I would like to be able to loop over my query, and to get a separate XML for each row.
I couldn't get a solution using DBMS_XMLGEN.getXMLType, but perhaps I didn't use it properly.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor is
SELECT '1a' as "column1", '1b' as "column2" FROM DUAL
UNION ALL
SELECT '2a' as "column1", '2b' as "column2" FROM DUAL;
myXMLType XMLType;
BEGIN
FOR mySelect in mySelectCursor
LOOP
-- I would like to replace the following line of code
myXMLType := XMLType('<row><column1>' || mySelect."column1" || '</column1><column2>' || mySelect."column2" || '</column2></row>');
-- by something similar to this (not working) one
--myXMLType := mySelect.getXMLType();
dbms_output.put_line(myXMLType.getClobVal());
END LOOP;
END;
--The following code outputs
--<row><column1>1a</column1><column2>1b</column2></row>
--<row><column1>2a</column1><column2>2b</column2></row>
I finally have found a solution.
Thank you for your help.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor is
SELECT
VALUE(table_temp) as "XMLTYPE"
FROM
table(
XMLSequence(
Cursor(
SELECT '1a' as "column1", '1b' as "column2" FROM DUAL
UNION ALL
SELECT '2a' as "column1", '2b' as "column2" FROM DUAL
)
)
) table_temp;
BEGIN
FOR mySelect in mySelectCursor
LOOP
dbms_output.put_line('===');
dbms_output.put_line(mySelect."XMLTYPE".getClobVal());
END LOOP;
END;
-- Output is :
--===
-- <ROW>
-- <column1>1a</column1>
-- <column2>1b</column2>
-- </ROW>
--
--===
-- <ROW>
-- <column1>2a</column1>
-- <column2>2b</column2>
-- </ROW>
Here is the code snippet, you need to use DBMS_XMLGEN.getxml:
DECLARE
v_xml CLOB;
v_more BOOLEAN := TRUE;
BEGIN
v_xml := DBMS_XMLGEN.getxml('select * from dual');
dbms_output.put_line(v_xml);
END;
/
Here is the code snippet, to loop in a cursor
CREATE OR REPLACE TYPE test_type AS OBJECT (
col1 CHAR(2),
col2 CHAR(2)
);
/
create or replace view test_view
as SELECT '1a' as column1, '1b' as column2 FROM DUAL
UNION ALL
SELECT '2a' as column1, '2b' as column2 FROM DUAL;
DECLARE
CURSOR c_xml IS
SELECT SYS_XMLAGG(
SYS_XMLGEN(
test_type(column1, column2),
sys.xmlgenformatType.createFormat('TABLE')
),
sys.xmlgenformatType.createFormat('TEST_VIEW')
).getStringVal() AS xml_row
FROM test_view;
BEGIN
FOR cur_rec IN c_xml LOOP
dbms_output.put_line(cur_rec.xml_row);
END LOOP;
end;
/
Does this query fit you needs:
WITH t AS
(SELECT '1a' AS c1, '1b' AS c2 FROM DUAL
UNION ALL
SELECT '2a' AS c1, '2b' AS c2 FROM DUAL)
SELECT XMLELEMENT("row",
XMLELEMENT("column1", c1 ),
XMLELEMENT("column2", c2 )
) AS myXMLType
FROM t;
Another way would be this one:
CREATE OR REPLACE TYPE "row" AS OBJECT (
"column1" VARCHAR2(100),
"column2" VARCHAR2(100))
/
WITH t AS
(SELECT '1a' AS c1, '1b' AS c2 FROM DUAL
UNION ALL
SELECT '2a' AS c1, '2b' AS c2 FROM DUAL)
SELECT XMLTYPE("row"(c1,c2)) AS myXMLType
FROM t;
Here is the solution with XMLTABLE rather than XMLSEQUENCE.
CREATE OR REPLACE PROCEDURE "MY_SCHEMA"."TEST" AS
CURSOR mySelectCursor IS
SELECT
VALUE(table_temp) AS "XMLTYPE"
FROM
XMLTABLE('/ROWSET/ROW' PASSING
DBMS_XMLGEN.GETXMLTYPE('
SELECT ''1a'' as "column1", ''1b'' as "column2" FROM DUAL
UNION ALL
SELECT ''2a'' as "column1", ''2b'' as "column2" FROM DUAL
')
) table_temp;
BEGIN
FOR mySelect IN mySelectCursor
LOOP
dbms_output.put_line(mySelect."XMLTYPE".getClobVal());
END LOOP;
END;
-- Output is :
--<ROW><column1>1a</column1><column2>1b</column2></ROW>
--<ROW><column1>2a</column1><column2>2b</column2></ROW>

oracle select from table if values not exists

I need to write oracle query (Just query)
to select values from table, and if not found select from another table.
any help to do this in pl/sql?
SELECT * FROM firstTable
UNION ALL
SELECT * FROM secondTable WHERE (SELECT count(*) FROM FIRST_TABLE ) = 0
You can enclose the SELECT statement within a block and add an exception handler to it.
So, if there are no rows selected from the first table, then you select from the second table. The structure would be something like below:
Begin
select <many columns> into <many variables or row type>
From Table1
where <conditions>;
EXCEPTION
WHEN NO_DATA_FOUND THEN
select <many columns> into <many variables or row type>
From Table2
Where <Conditions>;
End;
References:
Another related SO question
Exception Handlers
Documentation for the SELECT INTO statement
Here is an example of a PL/SQL function that will perform a test, and then execute a secondary query based upon the results of the test. You can adjust it to fit your needs:
set serveroutput on;
declare
row_count number;
column1 varchar(10);
column2 varchar(10);
column3 number;
begin
/*Perform your test*/
select count(target_column) into row_count
from my_table
where condition_column = 'x';
/*Run your secondary query based on the output of the first*/
if row_count > 0 then
select
col_x into column1,
col_y into column2,
col_z into column3
from my_other_table_A;
else
select
col_a into column1,
col_b into column2,
col_c into column3
from my_other_table_B;
end if;
/*Show the results*/
dbms_output.put_line('column1: ' || column1);
dbms_output.put_line('column2: ' || column2);
dbms_output.put_line('column3: ' || column3);
end;
/

How to pass cursor values into variable?

I am trying to read values from two column1, column2 from table1 using cursor. Then I want to pass these values to another cursor or select into statement
so my PL/Sql script will use the values of these two columns to get data from another table called table2
Is this possible? And what's the best and fastest way to do something like that?
Thanks :)
Yes, it's possible to pass cursor values into variables. Just use fetch <cursor_name> into <variable_list> to get one more row from a cursor. After that you can use the variables in where clause of some select into statement. E.g.,
declare
cursor c1 is select col1, col2 from table1;
l_col1 table1.col1%type;
l_col2 table1.col2%type;
l_col3 table2.col3%type;
begin
open c1;
loop
fetch c1 into l_col1, l_col2;
exit when c1%notfound;
select col3
into l_col3
from table2 t
where t.col1 = l_col1 --Assuming there is exactly one row in table2
and t.col2 = l_col2; --satisfying these conditions
end loop;
close c1;
end;
If you use an implicit cursor, then it's even simpler:
declare
l_col3 table2.col3%type;
begin
for i in (select col1, col2 from table1)
loop
select col3
into l_col3
from table2 t
where t.col1 = i.col1 --Assuming there is exactly one row in table2
and t.col2 = i.col2; --satisfying these conditions
end loop;
end;
In these examples, it's more efficient to use a subquery
begin
for i in (select t1.col1
, t1.col2
, (select t2.col3
from table2 t2
where t2.col1 = t1.col1 --Assuming there is atmost one such
and t2.col2 = t1.col2 --row in table2
) col3
from table1 t1)
loop
...
end loop;
end;

How to use list of selected values on the body of procedure

I need help with the structure of the procedure.
First of all, I should retrieve list of table names and owners.
At the body of the procedure I want to use this list for testing and comparisons.
I made some example that used cursor, I know it is not true.
Please advise how to implement this.
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
CURSOR C01 IS
(SELECT TABLE_NAME, OWNER
FROM TABLE1
UNION
SELECT TABLE_NAME, OWNER
FROM TABLE2);
BEGIN
SELECT MAX(SCORE)
INTO V_SCORE
FROM TABLE4 Q
WHERE EXISTS (SELECT 'Y'
FROM C01 T --- ?????????
WHERE Q.TABLE_NAME = T.TAB_NAME
AND Q.OWNER = T.OWNER);
END TEST_PROCEDURE;
The idea is good, but goes through some small misconceptions. Cursors are not intented to replace views or full resultsets, they are just allowing you to parse one by one (like a cursor) a give resultset.
You could use a common table expression (CTE) in this case:
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
BEGIN
WITH C01 AS (
SELECT
TABLE_NAME,
OWNER
FROM TABLE1
UNION
SELECT
TABLE_NAME,
OWNER
FROM TABLE2
)
SELECT MAX(SCORE) INTO V_SCORE
FROM TABLE4 Q
WHERE
EXISTS (
SELECT 'Y'
FROM C01 T
WHERE Q.TABLE_NAME = T.TAB_NAME
AND Q.OWNER = T.OWNER
);
END TEST_PROCEDURE;
If you need this CTE multiple times, then why not creating a view?
CREATE OR REPLACE VIEW C01 AS
SELECT
TABLE_NAME,
OWNER
FROM TABLE1
UNION
SELECT
TABLE_NAME,
OWNER
FROM TABLE2
;
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
BEGIN
SELECT MAX(SCORE) INTO V_SCORE
FROM TABLE4 Q
WHERE
EXISTS (
SELECT 'Y'
FROM C01 T
WHERE Q.TABLE_NAME = T.TAB_NAME
AND Q.OWNER = T.OWNER
);
END TEST_PROCEDURE;
Or even better, since you seem to just check that a set of values exist, create a deterministic function for this:
CREATE OR REPLACE FUNCTION EXISTS_IN_TABLES(I_OWNER IN VARCHAR2, I_TABLE IN VARCHAR2) RETURNS NUMBER DETERMINISTIC AS
BEGIN
SELECT 1
FROM (
SELECT TABLE_NAME, OWNER
FROM TABLE1
UNION
SELECT TABLE_NAME, OWNER
FROM TABLE2
) T
WHERE I_TABLE = T.TAB_NAME
AND I_OWNER = T.OWNER;
RETURN 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN RETURN 0;
WHEN OTHERS THEN RAISE;
END;
CREATE OR REPLACE PROCEDURE TEST_PROCEDURE (P_CODE IN NUMBER) IS
BEGIN
SELECT MAX(SCORE) INTO V_SCORE
FROM TABLE4 Q
WHERE
EXISTS_IN_TABLES(Q.OWNER, Q.TABLE_NAME) = 1
;
END TEST_PROCEDURE;
The deterministic option optimizes the performances, but then you have to be sure the content of TABLE1 and TABLE2 does not change during current session.

Resources