Removing duplicated subquery in pl/sql procedure - oracle

How to simplify following procedure?
I want to remove duplication of WITH clause.
IF p_YN = 'Y' THEN
OPEN p_cursor FOR
WITH VIEW_A AS
(
[very long select statements]
)
SELECT COL_A, COL_B, SUM(COL_C), SUM(COL_D) FROM VIEW_A
GROUP BY COL_A, COL_B;
ELSE
OPEN p_cursor FOR
WITH VIEW_A AS
(
[very long select statements]
)
SELECT COL_1, COL_2, SUM(COL_3), SUM(COL_4) FROM VIEW_A
GROUP BY COL_1, COL_2;
END IF;
I considered UNON ALL.
WITH VIEW_A AS
(
[very long select statements]
)
OPEN p_cursor FOR
SELECT COL_A, COL_B, SUM(COL_C), SUM(COL_D) FROM VIEW_A
GROUP BY COL_A, COL_B
WHERE p_YN = 'Y'
UNION ALL
SELECT COL_1, COL_2, SUM(COL_3), SUM(COL_4) FROM VIEW_A
GROUP BY COL_1, COL_2
WHERE p_YN <> 'Y';
But this doesn't work because two statements have different columns.

If UNION ALL can do what you need, maybe you can simply add same "fake" columns:
WITH VIEW_A AS
(
[very long select statements]
)
OPEN p_cursor FOR
SELECT COL_A, COL_B, SUM(COL_C), SUM(COL_D), NULL AS COL_1, NULL AS COL_2, NULL AS COL_3, NULL AS COL_4
FROM VIEW_A
GROUP BY COL_A, COL_B
WHERE p_YN = 'Y'
UNION ALL
SELECT NULL AS COL_A, NULL AS COL_B, NULL AS COL_C, NULL AS COL_D, COL_1, COL_2, SUM(COL_3), SUM(COL_4)
FROM VIEW_A
GROUP BY COL_1, COL_2
WHERE p_YN <> 'Y';
NULL should be changed in something else, depending on the types of your columns; besides, this may work or not, depending on what you need to do with the opened cursor.

Related

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

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.

XMLAGG - ORA-00932: inconsistent datatypes: expected - got CLOB on CLOB

I have the following SQL query:
SELECT DISTINCT
prod_no,
prod_text,
RTRIM (
XMLAGG (XMLELEMENT (e, prod_desc, ',').EXTRACT (
'//text()') ORDER BY prod_desc).getclobval (),
',')
FROM mytable
WHERE prod_no = 'XCY'
GROUP BY prod_no,
prod_text
When I execute I am getting
ORA-00932: inconsistent datatypes: expected - got CLOB
Update 1
DDL and sample data
CREATE TABLE mytable
(
prod_no VARCHAR2 (30 BYTE) NOT NULL,
prod_text VARCHAR2 (30 BYTE) NOT NULL,
prod_desc CLOB
);
SET DEFINE OFF;
INSERT INTO mytable (prod_no, prod_text, prod_desc)
VALUES ('XCY', 'DECKS', 'THIS IS TEST');
INSERT INTO mytable (prod_no, prod_text, prod_desc)
VALUES ('ABC', 'DECKS', 'THIS IS TEST 2');
COMMIT;
Issue is with DISTINCT and ORDER BY. Oracle doesn't allow these operation on CLOB. You are using group by, so you don't need DISTINCT anyway.
The below will work, if you don't mind the order of description.
SELECT
prod_no,
prod_text,
RTRIM (
XMLAGG (XMLELEMENT (e, prod_desc, ',') ).EXTRACT (
'//text()').getclobval (),
',')
FROM mytable
WHERE prod_no = 'XCY'
GROUP BY prod_no,
prod_text;
If you must order by it, you can cast the CLOB to varchar2 and order by it:
SELECT
prod_no,
prod_text,
RTRIM (
XMLAGG (XMLELEMENT (e, prod_desc, ',')
ORDER BY cast(prod_desc as varchar2(4000))).EXTRACT (
'//text()').getclobval (),
',')
FROM mytable
WHERE prod_no = 'XCY'
GROUP BY prod_no,
prod_text

picking 1 column from 2 tables and comparing them

In my Oracle database there are two tables which are TEMP_HR and PAY_SLIP_APR_16. Both of them have a common column named EMP_ID. TEMP_HR has over 10,000 records and PAY_SLIP_APR_16 has around 6,000 records. I want to know how many EMP_ID of PAY_SLIP_APR_16 is matched with TEMP_HR. If any ID doesn't match then print it. And here is my simple approach but I think its a very bad approach. So any faster method?
DECLARE
INPUT_EMP_NO VARCHAR2(13 BYTE);
INPUT_EMP_ID VARCHAR2(13 BYTE);
ROW_COUNT_1 NUMBER(6,0);
ROW_COUNT_2 NUMBER(6,0);
MATCHED_ID NUMBER;
UNMATCHED_ID NUMBER;
BEGIN
ROW_COUNT_1:=0;
ROW_COUNT_2:=0;
MATCHED_ID:=0;
UNMATCHED_ID:=0;
SELECT COUNT(*) INTO ROW_COUNT_1 FROM PAY_SLIP_APR_16;
SELECT COUNT(*) INTO ROW_COUNT_2 FROM TEMP_HR;
FOR A IN 1..ROW_COUNT_1 LOOP
BEGIN
SELECT EMP_ID INTO INPUT_EMP_ID FROM (SELECT EMP_ID, ROWNUM AS RN FROM PAY_SLIP_APR_16) WHERE RN=A;
FOR B IN 1..ROW_COUNT_2 LOOP
SELECT EMP_NO INTO INPUT_EMP_NO FROM (SELECT EMP_NO, ROWNUM AS RON FROM TEMP_HR) WHERE RON=B;
IF(INPUT_EMP_ID=INPUT_EMP_NO)THEN
MATCHED_ID:=MATCHED_ID+1;
EXIT;
ELSE
CONTINUE;
END IF;
END LOOP;
UNMATCHED_ID:=UNMATCHED_ID+1;
DBMS_OUTPUT.PUT_LINE(INPUT_EMP_ID);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(INPUT_EMP_ID||' -> '||SQLERRM);
END;
END LOOP;
DBMS_OUTPUT.PUT_LINE('MATCHED -> '||MATCHED_ID);
DBMS_OUTPUT.PUT_LINE('UNMATCHED -> '||UNMATCHED_ID);
END;
Use an outer join filtering for missed joins:
select p.*
from PAY_SLIP_APR_16 p
left join TEMP_HR t on t.EMP_ID = p.EMP_ID
where t.EMP_ID is null
An index on TEMP_HR(EMP_ID) will make this query fly.
You should use SQL sets!
To check which EMP_ID isn't in TEMP_HR you may try this:
select EMP_ID FROM PAY_SLIP_APR_16
where EMP_ID not in (select EMP_NO from TEMP_HR);
Then the other way around:
select EMP_NO FROM TEMP_HR
where EMP_NO not in (select EMP_ID from PAY_SLIP_APR_16);

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>

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;

Resources