Invalid SQL statement error with refcursor - oracle

I am trying to get the results of the underlying statements. The SQL statements works without any problems. However, in order to print the results I would like to use refcursor. I get the following errors:
ORA-00900: invalid SQL statement
ORA-01008: not all variables bound
ORA-00900: invalid SQL statement
VARIABLE reader refcursor;
DECLARE
line varchar2(32767);
BEGIN
line := 'SELECT role_id,';
FOR n IN (SELECT name
FROM (SELECT competence.skill_role.role_id,
competence.skill_label.name,
competence.skill_role.target_value
FROM competence.skill_role,
competence.skill_label
WHERE competence.skill_label.skill_id =
competence.skill_role.skill_id
AND competence.skill_label.language_id = 1)
matrix_result ) LOOP
line := line || '(decode(name,''' || n.name ||
''',target_value)) "' || n.name || '",';
END LOOP;
line := RTRIM(line, ',') ||
' FROM (SELECT competence.skill_role.role_id,
competence.skill_label.name,
competence.skill_role.target_value
FROM competence.skill_role, competence.skill_label
WHERE competence.skill_label.skill_id =
competence.skill_role.skill_id
AND competence.skill_label.language_id = 1) matrix_result';
--dbms_output.put_line(line);
--execute immediate line;
OPEN :reader FOR line;
END;
/
PRINT :reader;
Table data
CREATE TABLE competence.skill_role
(skill_id NUMBER,
role_id NUMBER,
target_value NUMBER)
/
INSERT ALL
INTO competence.skill_role VALUES (3432030, 1421866, 2)
INTO competence.skill_role VALUES (3434962, 1421866, 2)
INTO competence.skill_role VALUES (3488025, 3488804, 4)
SELECT * FROM competence.skill_role
SKILL_ID ROLE_ID target_value
---------- ------- -----------
3432030 1421866 2
3434962 1421866 2
3488025 3488804 4
CREATE TABLE competence.skill_label
(skill_id NUMBER,
name vchar2 (30))
/
INSERT ALL
INTO competence.skill_label VALUES (3432030, 'Alueen projektipätevyys')
INTO competence.skill_label VALUES (3434962, 'Moottorin koekäyttö')
INTO competence.skill_label VALUES (3488025, 'Etähallintajärjestelmät')
SELECT * FROM arc_competence.skill_label
SKILL_ID NAME
---------- -------
3432030, Alueen projektipätevyys
3434962, Moottorin koekäyttö
3488025, Etähallintajärjestelmät
I would like to have the following result from the first query. From your answer (if I understood correctly), it seems that I need to run the resultant query manually to get the answer. I'd like to have the result without running the resultant query :-) I don't have the access to the client machine at the moment but I am going there now.
ROLE_ID Alueen projektipätevyys Moottorin koekäyttö Etähallintajärjestelmät
1421866 2 2
3488804 4

If I correct your code so that it compiles
Your INSERT ALL statements are missing a SELECT
The name column in skill_label is defined as a vchar2(30) rather than a varchar2(30)
Your anonymous block references a column language_id that your DDL does not include
the code runs without error. If the only problem is that you want to combine the first two rows into a single row, you just need to add a MAX to all the columns other than ROLE_ID and to add a GROUP BY role_id to your query.
SQL> ed
Wrote file afiedt.buf
1 CREATE TABLE skill_role
2 (skill_id NUMBER,
3 role_id NUMBER,
4* target_value NUMBER)
SQL> /
Table created.
SQL> ed
Wrote file afiedt.buf
1 INSERT ALL
2 INTO skill_role VALUES (3432030, 1421866, 2)
3 INTO skill_role VALUES (3434962, 1421866, 2)
4 INTO skill_role VALUES (3488025, 3488804, 4)
5* select * from dual
SQL> /
3 rows created.
SQL> ed
Wrote file afiedt.buf
1 CREATE TABLE skill_label
2 (skill_id NUMBER,
3* name varchar2 (30))
SQL> /
Table created.
SQL> ed
Wrote file afiedt.buf
1 INSERT ALL
2 INTO skill_label VALUES (3432030, 'Alueen projektipΣtevyys')
3 INTO skill_label VALUES (3434962, 'Moottorin koekΣytt÷')
4 INTO skill_label VALUES (3488025, 'EtΣhallintajΣrjestelmΣt')
5* select * from dual
SQL> /
3 rows created.
SQL> commit;
Commit complete.
SQL> variable reader refcursor;
SQL> ed
Wrote file afiedt.buf
1 DECLARE
2 line varchar2(32767);
3 BEGIN
4 line := 'SELECT role_id,';
5 FOR n IN (SELECT name
6 FROM (SELECT skill_role.role_id,
7 skill_label.name,
8 skill_role.target_value
9 FROM skill_role,
10 skill_label
11 WHERE skill_label.skill_id =
12 skill_role.skill_id
13 )
14 matrix_result ) LOOP
15 line := line || 'max(decode(name,''' || n.name ||
16 ''',target_value)) "' || n.name || '",';
17 END LOOP;
18 line := RTRIM(line, ',') ||
19 ' FROM (SELECT skill_role.role_id,
20 skill_label.name,
21 skill_role.target_value
22 FROM skill_role, skill_label
23 WHERE skill_label.skill_id =
24 skill_role.skill_id
25 ) matrix_result ' ||
26 ' GROUP BY role_id' ;
27 dbms_output.put_line(line);
28 --execute immediate line;
29 OPEN :reader FOR line;
30* END;
31 /
PL/SQL procedure successfully completed.
SQL> print reader
ROLE_ID Alueen projektipΣtevyys Moottorin koekΣytt÷ EtΣhallintajΣrjestelmΣt
---------- ----------------------- ------------------- -----------------------
1421866 2 2
3488804 4

Related

E-mail result of 'select' statement having multiple rows as result set in ORACLE

I am new to ORACLE PL/SQL world. I am trying to figure out a way to calculate something as below.
Let's say you have a MASTER_TABLE as below :
SELECT * FROM MASTER_TABLE;
+----------+----------+------------------+-----------------------+
| SCHEMA | TABLE_NM | REQUIRED_COLUMNS | TABLE_FILTER |
+----------+----------+------------------+-----------------------+
| USER_SCH | A | A1,A2,A3 | EXAM_DT > SYSDATE - 1 |
| USER_SCH | B | B1,B2 | TRUNC(SYSDATE) |
+----------+----------+------------------+-----------------------+
I would like to generate SELECT query from above table such as below:
SELECT 'SELECT SCHEMA || '.' || TABLE_NM ||' WHERE '|| TABLE_FILTER FROM MASTER_TABLE;
Obviously, the result of above query would generate multiple select statements.
Now, I want to execute all such SELECT statements and send out the resultset via e-mail.
The tricky part is, the columns mentioned in the MASTER_TABLE varies (i.e. For table 'A' there can be 3 REQUIRED_COLUMNS to be selected, For table 'B' there can be 2 REQUIRED_COLUMNS to be selected - As shown in the MASTER_TABLE)
I have the e-mail utility ready which basically takes an argument as your_message and sends it out via e-mail.
Here is what I have tried :
Created CURSOR to generate such select statements.
Tried inserting the resultset (LIST OF SELECT QUERIES) to another temp table by concatenating the columns.
(i.e.
SELECT
'SELECT '
|| replace(required_columns, ',', '||'',''||')
|| ' AS MSG_BDY'
|| ' FROM '
|| schema
|| '.'
|| table_nm
|| ' WHERE '
|| table_filter
as my_select_stmt
FROM
master_table;
I am stuck after this.
Can you please help me out ? or is there any approach to achieve this ?.
Note : Tables mentioned in MASTER_TABLE can have 1 or more rows.
I don't have your tables so I used Scott's.
Master table:
SQL> select * From master_table;
SCHEM TABL REQUIRED_COLUMN TABLE_FILTER
----- ---- --------------- ----------------------
scott emp ename, job, sal hiredate < sysdate - 1
scott dept dname, loc deptno = 20
SQL>
Procedure which simulates your mailing procedure; I'll just display those values.
SQL> CREATE OR REPLACE PROCEDURE p_mail (par_result IN SYS.odcivarchar2list)
2 AS
3 BEGIN
4 FOR i IN par_result.FIRST .. par_result.LAST
5 LOOP
6 DBMS_OUTPUT.put_line (par_result (i));
7 END LOOP;
8 END;
9 /
Procedure created.
SQL>
Procedure you actually need; as you composed the select statement(s), now you only have to run them. In order to do so, use dynamic SQL (e.g. execute immediate):
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 retval SYS.odcivarchar2list;
3 BEGIN
4 FOR cur_r
5 IN (SELECT 'SELECT '
6 || REPLACE (required_columns, ',', '||'',''||')
7 || ' AS MSG_BDY'
8 || ' FROM '
9 || schema
10 || '.'
11 || table_nm
12 || ' WHERE '
13 || table_filter
14 AS my_select_stmt
15 FROM master_table)
16 LOOP
17 EXECUTE IMMEDIATE cur_r.my_select_stmt BULK COLLECT INTO retval;
18
19 -- you'd call your mailing procedure here
20 p_mail (retval);
21 END LOOP;
22 END;
23 /
SMITH,CLERK,920
ALLEN,SALESMAN,1600
WARD,SALESMAN,1250
JONES,MANAGER,2975
MARTIN,SALESMAN,1250
BLAKE,MANAGER,2850
CLARK,MANAGER,2450
SCOTT,ANALYST,3000
KING,PRESIDENT,10000
TURNER,SALESMAN,1500
ADAMS,CLERK,1300
JAMES,CLERK,950
FORD,ANALYST,3000
MILLER,CLERK,1300
RESEARCH,DALLAS
PL/SQL procedure successfully completed.
SQL>
[EDIT: what if you wanted to display 'null' for missing values?]
Well, that's a new moment - probably not very simple. See if this helps.
In order to help myself, I modified master_table and added ID column to uniquely identify every row. It'll be used to split required columns' list to rows, apply NVL to them, apply CAST to columns (because NVL complains if datatypes don't match), aggregate them back using listagg. As this is quite a lot to do, I'm going to create a view and use it instead of the table itself.
SQL> CREATE OR REPLACE VIEW v_master_table
2 AS
3 SELECT id,
4 schema,
5 table_nm,
6 LISTAGG ('NVL(cast(' || col || ' as varchar2(20)), ''null'')', '||'',''||')
7 WITHIN GROUP (ORDER BY lvl)
8 required_columns,
9 table_filter
10 FROM (SELECT id,
11 schema,
12 table_nm,
13 table_filter,
14 COLUMN_VALUE lvl,
15 TRIM (REGEXP_SUBSTR (required_columns,
16 '[^,]+',
17 1,
18 COLUMN_VALUE))
19 col
20 FROM master_table
21 CROSS JOIN
22 TABLE (
23 CAST (
24 MULTISET (
25 SELECT LEVEL
26 FROM DUAL
27 CONNECT BY LEVEL <=
28 REGEXP_COUNT (required_columns,
29 ',')
30 + 1) AS SYS.odcinumberlist)))
31 GROUP BY id,
32 schema,
33 table_nm,
34 table_filter;
View created.
For example, it now looks like this:
SQL> select * from v_master_table where id = 2;
ID SCHEM TABL REQUIRED_COLUMNS TABLE_FILTER
--- ----- ---- ------------------------------------------------------------------------------------- ------------
2 scott dept NVL(cast(dname as varchar2(20)), 'null')||','||NVL(cast(loc as varchar2(20)), 'null') deptno = 20
SQL>
The mailing procedure remains the same, no change.
Anonymous PL/SQL block is slightly changed - I removed REPLACE you previously used as view does it now; also, source is the view, not the table.
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> DECLARE
2 retval SYS.odcivarchar2list;
3 BEGIN
4 FOR cur_r
5 IN (SELECT 'SELECT '
6 || required_columns
7 || ' AS MSG_BDY'
8 || ' FROM '
9 || schema
10 || '.'
11 || table_nm
12 || ' WHERE '
13 || table_filter
14 AS my_select_stmt
15 FROM v_master_table)
16 LOOP
17 EXECUTE IMMEDIATE cur_r.my_select_stmt BULK COLLECT INTO retval;
18
19 -- you'd call your mailing procedure here
20 p_mail (retval);
21 END LOOP;
22 END;
23 /
CLARK,09.06.1981,null
KING,17.11.1981,null
MILLER,23.01.1982,null
RESEARCH,DALLAS
PL/SQL procedure successfully completed.
SQL>

Finding max value of a field available in all tables

Example scenario:
5 tables are there and one common field among them is com_field (DATE data type). Now, I need to find the maximum of com_field in each of the five tables. Can someone give the logic?
I know UNION could be used but I need the flexibility not to miss any new table added to the OWNER.
The result I am expecting is like the below.
Table Max(com_field)
Tbl1 10/21/2019
Tbl2 10/18/2019
Tbl3 10/28/2019
Tbl4 09/30/2019
Tbl5 09/09/2019
Run this query:
SELECT
'SELECT '''||OWNER||'.'||TABLE_NAME ||''' AS TABLE_NAME , '||'MAX(COM_FIELD)AS COM_FIELD FROM ' ||OWNER||'.'||TABLE_NAME ||' UNION ALL'
FROM ALL_TAB_COLUMNS
WHERE COLUMN_NAME ='COM_FIELD'
then copy the outpu and delete last union all keyword. Then run the sql statement.
You can order it and see the max value
One option is to use dynamic SQL in a function that returns refcursor. Here's an example.
First, test case:
SQL> create table taba (com_field date);
Table created.
SQL> create table tabb (com_field date);
Table created.
SQL> create table tabc (com_field date);
Table created.
SQL> insert all
2 into taba values (sysdate)
3 into taba values (sysdate - 2)
4 into taba values (sysdate - 3)
5 into tabb values (sysdate + 2)
6 into tabc values (sysdate + 4)
7 into tabc values (sysdate + 5)
8 select * From dual;
6 rows created.
SQL>
Function:
SQL> create or replace function f_maxcom
2 return sys_refcursor
3 is
4 l_str varchar2(1000);
5 rc sys_refcursor;
6 begin
7 for cur_r in (select table_name
8 from user_tab_columns
9 where column_name = 'COM_FIELD'
10 )
11 loop
12 l_str := l_str ||
13 'select ' || chr(39) || cur_r.table_name || chr(39) || ', ' ||
14 'max(com_field) from ' || cur_r.table_name || ' union all ';
15 end loop;
16
17 l_str := rtrim(l_str, ' union all');
18
19 open rc for l_str;
20 return rc;
21 end;
22 /
Function created.
SQL>
Let's try it:
SQL> select f_maxcom from dual;
F_MAXCOM
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
'TAB MAX(COM_FI
---- ----------
TABA 29.10.2019
TABB 31.10.2019
TABC 03.11.2019
SQL>
Add another table to see what happens; function will stay as is:
SQL> create table littlefoot (id number, com_field date);
Table created.
SQL> insert into littlefoot values (100, sysdate);
1 row created.
SQL> select f_maxcom from dual;
F_MAXCOM
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
'LITTLEFOO MAX(COM_FI
---------- ----------
LITTLEFOOT 29.10.2019
TABA 29.10.2019
TABB 31.10.2019
TABC 03.11.2019
SQL>
Seems to be OK, eh?

how to get the 2nd row and the last row of a file using UTL_FILE

I have a file which consists of thousand rows and I need to get a portion of the 2nd row (about 50 characters) and the last row of the file. Please advise. Thank you.
Im trying to do something like UTL_FILE.READLINE(fileloc, filename, nrow, lastrow).
SAMPLE:
Filename: CLOSED_SO_20190124.txt
DATA in the FILE:
0246608377|22795124004|
650930363|1-8IGO3S82920|
0245563264|22669075004|
0245563264|22669075004|
164260364|1-2DFE-6573219|
650821459|1-6HWQUF11209|
EXPECTED OUTPUT:
650930363|1-8IGO3S82920|
650821459|1-6HWQUF11209|
Here's an example.
Directory name & its location, as well as sample file contents:
SQL> select directory_name, directory_path from all_directories;
DIRECTORY_NAME DIRECTORY_PATH
------------------------------ --------------------
EXT_DIR c:\temp
SQL> $type c:\temp\sofile.txt
0246608377|22795124004|
650930363|1-8IGO3S82920|
0245563264|22669075004|
0245563264|22669075004|
164260364|1-2DFE-6573219|
650821459|1-6HWQUF11209|
SQL>
The procedure: a local counter (l_cnt) knows line number; if it is equal to 2, display that line. Also, when nothing's being found (so exception handler section is executed), I've reached the end of the file so I'm displaying the last line as well.
SQL> set serveroutput on
SQL>
SQL> declare
2 l_file utl_file.file_type;
3 l_dir varchar2(20) := 'EXT_DIR';
4 l_name varchar2(20) := 'sofile.txt';
5 l_line varchar2(50);
6 l_cnt number := 0;
7 begin
8 l_file := utl_file.fopen (l_dir, l_name, 'R');
9 loop
10 begin
11 utl_file.get_line(l_file, l_line);
12 l_cnt := l_cnt + 1;
13 if l_cnt = 2 then
14 dbms_output.put_line('2nd : ' || l_line);
15 end if;
16 exception
17 when no_data_found then
18 dbms_output.put_line('last: ' || l_line);
19 exit;
20 end;
21 end loop;
22 utl_file.fclose(l_file);
23 end;
24 /
2nd : 650930363|1-8IGO3S82920|
last: 650821459|1-6HWQUF11209|
PL/SQL procedure successfully completed.
SQL>
In Oracle 11g
select col
from
(
select
rownum AS rnum,
LEFT(myCol, 50) col
from Table1
where Rownum < 3
)
WHERE rnum = 2
In Oracle 12c
select LEFT(mycol, 50) col
from Table1
ORDER BY val
OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY;
For the last row
select LEFT(mycol, 50) col
from my_table
where pk = ( select max(pk) from my_table )
then you can union them

Check if both column and table exist and run queries based on the result

I am trying to run some SQL queries on Oracle database, but before running the query I need to check if both table and column exists. If table exists and column does not exist, then run another query:
if table `testtable` exists and if table has column `testcolumn`
Run a SQL which returns the result
else if table `testtable` exists but column `testcolumn` not present
Run a different sql which also returns the result
else
print some defined string
You can use:
DECLARE
nCount NUMBER;
BEGIN
SELECT COUNT(*)
INTO nCount
FROM USER_TAB_COLS
WHERE TABLE_NAME = 'TESTTABLE' AND
COLUMN_NAME = 'TESTCOLUMN';
IF nCount > 0 THEN
-- Run a SQL which returns the result
ELSE
SELECT COUNT(*)
FROM USER_TABLES
WHERE TABLE_NAME = 'TESTTABLE';
IF nCount > 0 THEN
Run a different sql which also returns the result
ELSE
print some defined string
END;
You'll have to add code to run whatever SQL you're trying to run, and to print whatever message you need.
Best of luck.
Here's one option - check contents of USER_TAB_COLUMNS and - depending on what you find - use refcursor in order to return the result.
SQL> create or replace function f_test
2 return sys_refcursor
3 is
4 l_cnt number;
5 cur_r sys_refcursor;
6 begin
7 -- 1st test - this one fails
8 select count(*)
9 into l_cnt
10 from user_tab_columns
11 where table_name = 'EMP'
12 and column_name = 'DOES_NOT_EXIST';
13
14 if l_cnt > 0 then
15 open cur_r for select ename, job, sal from emp;
16 end if;
17
18 -- 2nd test - this one is OK
19 select count(*)
20 into l_cnt
21 from user_tab_columns
22 where table_name = 'DEPT'
23 and column_name = 'DEPTNO';
24
25 if l_cnt > 0 then
26 open cur_r for select dname, loc from dept;
27 end if;
28
29 return cur_r;
30 end;
31 /
Function created.
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME LOC
-------------- -------------
ACCOUNTING NEW YORK
RESEARCH DALLAS
SALES CHICAGO
OPERATIONS BOSTON
SQL>
It has to be some kind of a dynamic code because you can't just write a static SELECT statement that selects non-existent columns as you'd get ORA-00904: "DOES_NOT_EXIST": invalid identifier error.

Using SELECT within SELECT statement in ORACLE

I have a table name SAMPLETABLE this has the tablenames of the tables I require in column TABLENAMES. Lets say the tablenames are TABLEA, TABLEB and TABLEC.
On query
SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1
I get the output the output of TABLENAMES column with TABLEA value.
My problem is, now I want to use this selected value in a select statement. That is,
SELECT * FROM (SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1)
My idea is that it'd return the contents of TABLEA because when the nested SELECT returns TABLEA, the outer SELECT should capture and display it.
On the contrary, I get the output only of the inner statement, that is,
SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1
and
SELECT * FROM (SELECT TABLENAMES FROM SAMPLETABLE WHERE ROWNUM = 1)
return the same output.
I want the first SELECT statement to fetch the returned value of second SELECT and display the table. They above query doesn't do that, so how do I do it? And what is wrong with my idea?
I am on Oracle 10g, any help appreciated.
As table name is not known at compile time you need to use dynamic SQL(execute immediate, native dynamic SQL, for instance) to be able to select from a table, name of which is stored as a string literal - you cannot accomplish it with static SQL
Here is an example:
-- table which contains names of other tables
-- in the table_name column
SQL> create table Table_Names as
2 select 'employees' as table_name
3 from dual
4 ;
Table created
SQL> set serveroutput on;
-- example of an anonymous PL/SQL block
-- where native dynamic SQL (execute immediate statement)
-- is used to execute a dynamically formed select statement
SQL> declare
2 type T_record is record( -- example of record for fetched data
3 f_name varchar2(123),
4 l_name varchar2(123)
5 );
6
7 l_table_name varchar2(123); -- variable that will contain table name
8 l_select varchar2(201);
9 l_record T_Record; -- record we are going to fetch data into
10 begin
11 select table_name
12 into l_table_name -- querying a name of a table
13 from table_names -- and storing it in the l_table_name variable
14 where rownum = 1;
15
16 l_select := 'select first_name, last_name from ' ||
17 dbms_assert.simple_sql_name(l_table_name) ||
18 ' where rownum = 1'; -- forming a query
19
20 execute immediate l_select -- executing the query
21 into l_record;
22 -- simple output of data just for the sake of demonstration
23 dbms_output.put_line('First_name: ' || l_record.f_name || chr(10) ||
24 'Last name: ' || l_record.l_name);
25 exception
26 when no_data_found
27 then dbms_output.put_line('Nothing is found');
28 end;
29 /
First_name: Steven
Last name: King
PL/SQL procedure successfully completed
As a second option you could use weakly typed cursors - refcursors to execute a dynamically formed select statement:
SQL> variable refcur refcursor;
SQL> declare
2 l_table_name varchar2(123);
3 l_select varchar2(201);
4 begin
5 select table_name
6 into l_table_name
7 from table_names
8 where rownum = 1;
9
10 l_select := 'select first_name, last_name from ' ||
11 dbms_assert.simple_sql_name(l_table_name) ||
12 ' where rownum = 1';
13
14 open :refcur
15 for l_select;
16
17 exception
18 when no_data_found
19 then dbms_output.put_line('Nothing is found');
20 end;
21 /
PL/SQL procedure successfully completed.
SQL> print refcur;
FIRST_NAME LAST_NAME
-------------------- -------------------------
Steven King
SQL> spool off;
Find out more about cursors and cursor variables
You can do this with help of dynamic sql. Since the table name is obtained during run time you have to frame the query dynamically and run it.
Declare
Tab_Name Varchar2(30);
Begin
SELECT TABLENAMES into Tab_Name FROM SAMPLETABLE WHERE ROWNUM = 1;
Execute Immediate 'Select * into (Collection Variable) from ' || Tab_Name;
End
/
I just gave it as example. You declare a variable to get the data out or something else as you need. But when you try to use execute immediate with input parameter read about sql injection and then write your code.

Resources