Table not found at cursor%NOTFOUND Oracle - oracle

I was asked to migrate this code from Sql server
It's used to count all rows with a cursor in a database and we need it to work both on sql server and oracle. The sql server part is working fine, but the oracle part i can't figure out.
DECLARE #tablename VARCHAR(500)
DECLARE #rowname VARCHAR(500)
DECLARE #sql AS NVARCHAR(1000)
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL
DROP TABLE #Temp1
CREATE TABLE #Temp1
(
tablename varchar(max) ,
rowCounter NUMERIC(38) ,
idColumn varchar(max),
SumIdCol NUMERIC(38)
);
DECLARE db_cursor CURSOR FOR
SELECT s.name + '.' + o.name as name, c.COLUMN_NAME
FROM sys.all_objects o
join sys.schemas s on o.schema_id=s.schema_id
join INFORMATION_SCHEMA.COLUMNS c on o.name=c.TABLE_NAME
WHERE type = 'U' and s.name not like 'sys' and c.ORDINAL_POSITION=1 and c.DATA_TYPE='int'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #tablename,#rowname
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql =
'
insert into #Temp1 values('+
''''+#tablename+''','+
'(SELECT count(1) FROM '+#tablename+' ),'+
''''+#rowname+''''+','+
'(SELECT SUM(['+#rowname+']) FROM '+#tablename+' )'+
')'
EXEC sp_executesql #sql
FETCH NEXT FROM db_cursor INTO #tablename,#rowname
END
CLOSE db_cursor
DEALLOCATE db_cursor
select * from #Temp1
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL
DROP TABLE #Temp1
Into oracle. I decided to take on PL/SQL and wrote this:
DECLARE
v_tablename varchar2(400);
v_rowname varchar2(400);
cursor db_cursor is
select ALL_OBJECTS.OWNER || '.' || USER_TABLES.TABLE_NAME AS Tablename, USER_TAB_COLS.COLUMN_NAME AS columnName
from USER_TABLES
inner join user_tab_cols ON USER_TAB_COLS.TABLE_NAME=USER_TABLES.TABLE_NAME
inner join ALL_OBJECTS ON user_tab_cols.TABLE_NAME=ALL_OBJECTS.OBJECT_NAME
WHERE ALL_OBJECTS.OBJECT_TYPE = 'TABLE' AND USER_TAB_COLS.COLUMN_ID = 1;
begin
open db_cursor;
loop
fetch db_cursor into v_tablename,v_rowname;
EXIT WHEN db_cursor%NOTFOUND;
execute immediate 'INSERT INTO Temp1(tablename,rowCounter,idColumn,SumIdCol)
SELECT ''' || v_tablename || ''' ,COUNT(*), ''' || v_rowname ||''',SUM(''' || v_rowname ||''') FROM ' || v_tablename;
end loop;
CLOSE db_cursor;
END;
The issue i have is that i get Invalid table or view at line 42 (This line is EXIT WHEN db_cursor%NOTFOUND;)
I know this solution isn't the best but any help would be appreciated. Thanks

I'm not quite sure what are the last two columns in the temp1 table supposed to contain; the first column (why?) and some "sum" value, but - which one?
Anyway: in Oracle, you might start with this code (that compiles and does something; could/should be improved, perhaps):
Target table:
SQL> create table temp1
2 (tablename varchar2(70),
3 rowcounter number,
4 idcolumn varchar2(30),
5 sumidcol number
6 );
Table created.
Anonymous PL/SQL block; note that you need just all_tables and all_tab_columns as they contain all info you need. If you use all_objects, you have to filter tables only (so, why use it at all?).
As all_ views contain all tables/columns you have access to, I filtered only tables owned by user SCOTT because - if I left them all, I'd get various owners (such as SYS and SYSTEM - do you need them too?) and 850 tables.
SQL> select count(distinct owner) cnt_owner, count(*) cnt_tables
2 from all_tables;
CNT_OWNER CNT_TABLES
---------- ----------
19 850
SQL>
Maybe you'd actually want to use user_ views instead.
This code lists all tables, the first column and number of rows in each table:
SQL> declare
2 l_cnt number;
3 begin
4 for cur_r in (select a.owner,
5 a.table_name,
6 c.column_name
7 from all_tables a join all_tab_columns c on c.owner = a.owner
8 and c.table_name = a.table_name
9 where c.column_id = 1
10 and a.owner in ('SCOTT')
11 )
12 loop
13 execute immediate 'select count(*) from ' || cur_r.owner ||'.'||
14 '"' || cur_r.table_name ||'"' into l_cnt;
15
16 insert into temp1 (tablename, rowcounter, idcolumn, sumidcol)
17 values (cur_r.owner ||'.'||cur_r.table_name, l_cnt, cur_r.column_name, null);
18 end loop;
19 end;
20 /
PL/SQL procedure successfully completed.
Result is then:
SQL> select * from temp1 where rownum <= 10;
TABLENAME ROWCOUNTER IDCOLUMN SUMIDCOL
----------------------------------- ---------- --------------- ----------
SCOTT.ACTIVE_YEAR 1 YEAR
SCOTT.BONUS 0 ENAME
SCOTT.COPY_DEPARTMENTS 1 DEPARTMENT_ID
SCOTT.DAT 0 DA
SCOTT.DEPARTMENTS 1 DEPARTMENT_ID
SCOTT.DEPT 4 DEPTNO
SCOTT.DEPT_BACKUP 4 DEPTNO
SCOTT.EMP 14 EMPNO
SCOTT.EMPLOYEE 4 EMP_ID
SCOTT.EMPLOYEES 9 EMPLOYEE_ID
10 rows selected.
SQL>

You are getting the Table not found exception because you have lower-case table names and you are using unquoted identifiers, which Oracle will implicitly convert to upper-case and then because they are upper- and not lower-case they are not found.
You need to use quoted identifiers so that Oracle respects the case-sensitivity in the table names. You can also use an implicit cursor and can pass the values using bind variables (where possible) rather than using string concatenation:
BEGIN
FOR rw IN (SELECT t.owner,
t.table_name,
c.column_name
FROM all_tables t
INNER JOIN all_tab_cols c
ON (t.owner = c.owner AND t.table_name = c.table_name)
WHERE t.owner = USER
AND c.column_id = 1)
LOOP
EXECUTE IMMEDIATE 'INSERT INTO Temp1(tablename,rowCounter,idColumn)
SELECT :1, COUNT(*), :2 FROM "' || rw.owner || '"."' || rw.table_name || '"'
USING rw.table_name, rw.column_name;
END LOOP;
END;
/
fiddle

Related

Oracle View or Table Function that returns union of queries stored as text in another table

Let's say that I have a "Rules" table that has a column that contains oracle queries in a varchar2 column:
Row
Query
1
select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < *some date math goes here*
2
select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < *some date math goes here*
If this were never going to change, I'd just make a view with a union of these queries.
Our requirement is that we be able to add to or to modify these rules on-the-fly at the whims of leadership.
So, what I need is either:
a very smart view (I think impossible) that executes and unions all of these stored query strings
or
a table function that returns the results of the union of these stored query strings. (I think this is the more likely solution)
It will only ever be those two columns: The hardcoded name of the table and the ID of the record.
Can someone help get me started on this?
Thanks
You can use a PIPELINED function.
First create the types:
CREATE TYPE request_data IS OBJECT (tablename VARCHAR2(30), request_id NUMBER);
CREATE TYPE request_list IS TABLE OF request_data;
Then the function:
CREATE FUNCTION get_requests RETURN request_list PIPELINED
IS
BEGIN
FOR r IN (SELECT "QUERY" FROM table_name ORDER BY "ROW")
LOOP
DECLARE
c_cursor SYS_REFCURSOR;
v_tablename VARCHAR2(30);
v_request_id NUMBER;
BEGIN
OPEN c_cursor FOR r."QUERY";
LOOP
FETCH c_cursor INTO v_tablename, v_request_id;
EXIT WHEN c_cursor%NOTFOUND;
PIPE ROW (request_data(v_tablename, v_request_id));
END LOOP;
CLOSE c_cursor;
EXCEPTION
WHEN NO_DATA_NEEDED THEN
CLOSE c_cursor;
RETURN;
END;
END LOOP;
END;
/
Then, if you have the sample data:
CREATE TABLE table_name ("ROW", "QUERY") AS
SELECT 1, q'[select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < SYSDATE]' FROM DUAL UNION ALL
SELECT 2, q'[select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < SYSDATE]' FROM DUAL
CREATE TABLE table_1 (request_id, status, resolve_date) AS
SELECT 42, 4, SYSDATE - 1 FROM DUAL;
CREATE TABLE table_2 (request_id, parent_id) AS
SELECT 57, 42 FROM DUAL;
Then you can use the function in a table collection expression:
SELECT *
FROM TABLE(get_requests());
Which outputs:
TABLENAME
REQUEST_ID
Hardcoded_Tablename_1
42
Table_2
57
db<>fiddle here
One option might be a function that returns refcursor.
SQL> select * from rules;
CROW QUERY
---------- ----------------------------------------------------------------------------------------------------
1 select 'EMP' tablename, empno from emp where hiredate = (select max(hiredate) from emp)
2 select 'DEPT' tablename, d.deptno from emp e join dept d on d.deptno = e.deptno where e.hiredate = (
select min(hiredate) from emp)
Function creates union of all queries from the rules table and uses it as a source for the refcursor:
SQL> create or replace function f_test return sys_refcursor
2 is
3 l_rc sys_refcursor;
4 l_str clob;
5 begin
6 for cur_r in (select query from rules order by crow) loop
7 l_str := l_str || cur_r.query ||' union all ';
8 end loop;
9 l_str := rtrim(l_str, ' union all ');
10
11 open l_rc for l_str;
12 return l_rc;
13 end;
14 /
Function created.
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
TABL EMPNO
---- ----------
EMP 7876
DEPT 20
SQL>

oracle procedure to list table names and corresponding count

I am stucked with an issue.
I have a table called gd_table_order which contains tablenames.
And i need to get the table_name and count of each table to a target one.
target is a view.
I only gave an example with 2 columns, there are 10 columns like this.So in the procedure we need to enter this as a parameter.
And there will be corresponsding 10 views to be generate. below is a sample of 2 views.
V_CHECK_RECORDS_* is the output view name
Could you please help?
I am not sure what is exact requirement here, But you can use the following approach to fetch the number of records from each table.
SQL> -- This is sample data
SQL> WITH SAMPLE_DATA(TNAME) AS
2 (SELECT 'CUSTOMERS' FROM DUAL UNION ALL
3 SELECT 'INTERVAL_TAB' FROM DUAL)
4 -- Your query starts from here
5 SELECT TABLE_NAME,
6 TO_NUMBER(
7 EXTRACTVALUE( XMLTYPE(
8 DBMS_XMLGEN.GETXML('select count(*) c from ' || U.TABLE_NAME)
9 ), '/ROWSET/ROW/C')) COUNT
10 FROM USER_TABLES U JOIN SAMPLE_DATA S ON S.TNAME = U.TABLE_NAME;
TABLE_NAME COUNT
--------------- ----------
CUSTOMERS 1
INTERVAL_TAB 0
SQL>
-- Update
You can generate the view as follows :
-- UPDATED THIS SECTION
CREATE OR REPLACE VIEW V_CHECK_RECORDS_AUS AS
SELECT TABLE_NAME,
TO_NUMBER(
EXTRACTVALUE( XMLTYPE(
DBMS_XMLGEN.GETXML('select count(*) c from '
|| U.TABLE_NAME || ' WHERE oe_name=''BUL''')
), '/ROWSET/ROW/C')) NUM_ROWS
FROM USER_TAB_COLUMNS U JOIN GD_TABLE_ORDER S ON S.TABLE_NAME_AUS = U.TABLE_NAME
WHERE U.COLUMN_NAME = 'OE_NAME';
In the same way you can generate other views.
-- Further Update
CREATE OR REPLACE VIEW V_CHECK_RECORDS_AUS AS
SELECT TABLE_NAME,
CASE WHEN U.COLUMN_NAME IS NOT NULL THEN TO_NUMBER(
EXTRACTVALUE( XMLTYPE(
DBMS_XMLGEN.GETXML('select count(*) c from '
|| U.TABLE_NAME || ' WHERE ' || U.COLUMN_NAME || '=''BUL''')
), '/ROWSET/ROW/C'))
ELSE 0 END NUM_ROWS
FROM GD_TABLE_ORDER S LEFT JOIN USER_TAB_COLUMNS U
ON S.TABLE_NAME_AUS = U.TABLE_NAME AND U.COLUMN_NAME = 'OE_NAME';

Is there a way to make a PLSQL script that lists all columns that IS NULL for every record in a table?

I am working with a huge database with several columns and records. I want to browse a specific table and make a list of the columns that are empty for every record.
Is this possible without refering to all the specific column names?
Thanks for help!
It's possible but if you have a lot data it will last a long time.
create table xxx as select * from dba_objects where rownum < 10000;
prepare test table get table stats. It can be long lasting process.
begin
dbms_stats.gather_table_stats(user,'XXX',estimate_percent =>100);
-- ..
-- others tables to analizye
end;
Generate reports.
select table_name,column_name from user_tab_cols where coalesce(low_value,high_value) is null and table_name in('XXX');
You can use the below script to find out the null columns in your database -
DECLARE
COUNT_COL INT;
SQL_STR VARCHAR2(100);
BEGIN
FOR I IN (SELECT OBJECT_NAME, COLUMN_NAME
FROM USER_OBJECTS UO
JOIN USER_TAB_COLS UTC ON UO.OBJECT_NAME = UTC.TABLE_NAME) LOOP
SQL_STR := 'SELECT COUNT(1) FROM ' || I.OBJECT_NAME || ' WHERE ' || i.COLUMN_NAME || ' IS NOT NULL';
EXECUTE IMMEDIATE SQL_STR INTO COUNT_COL;
IF COUNT_COL = 0 THEN
DBMS_OUTPUT.PUT_LINE(I.COLUMN_NAME);
END IF;
END LOOP;
END;
Here is the fiddle.
Try for all record in table:
SELECT a.owner, a.table_name, b.column_name
FROM all_tables a, all_tab_columns b
WHERE a.table_name = '<TABLE_NAME>'
AND a.table_name = b.table_name
AND a.num_rows = b.num_nulls
For all table
SELECT a.owner, a.table_name, b.column_name
FROM all_tables a, all_tab_columns b
WHERE a.table_name = b.table_name
AND a.num_rows = b.num_nulls

How to execute query output

We have written a sql that return sum and average of numeric columns present in schema.
Could you please help us if there is there a way to execute the query and queryoutput together in a single query. We don't have create or insert permission.
select 'select avg(' || column_name ||'), sum('||column_name|| '), '|| table_name
||' from '|| table_name ||' ' ||'union all'
from all_tab_columns
where data_type= 'NUMBER'
and owner not in ('SYS','PUBLIC','WMSYS','SYSTEM')
and column_name not in ('BATCHNUM')
The output of above query is
select avg(amt), sum(amt2), table1 from table1
union all
select avg(amt2), sum(amt5), table2 from table2
Here is what we are trying to achieve in a single query
schema Tablename columname average sum
Test testable amount 10 1000
Test testable amounttrans 100 4000
Test2 transtable amount 100 5000
thank you
You may create a dynamic View( Ask your superuser / dba to create it for you if you don't have permission)
In this example, I'll use few rows from HR schema.You may modify it to add schema name as well using all_tab_columns
DECLARE
v_sql CLOB := empty_clob();
BEGIN
for rec IN (select 'select '''||table_name||''' as tablename,
'''||column_name||''' as columname,
avg(' || column_name ||') as average,sum('
|| column_name || ') as "SUM" from '
|| table_name
||' ' as qry
FROM user_tab_columns --all_tab_columns
where data_type= 'NUMBER'
--and owner not in ('SYS','PUBLIC','WMSYS','SYSTEM')
AND column_name NOT IN ('BATCHNUM'
)
AND ROWNUM < 10
) LOOP v_sql := v_sql
|| rec.qry
|| ' UNION ALL ';
END LOOP;
v_sql := regexp_replace(v_sql,' UNION ALL $');
EXECUTE IMMEDIATE 'CREATE OR REPLACE view my_view as ' || v_sql;
END;
/
Now, Query the view.
SQL> set sqlformat ansiconsole
SQL> select * from my_view;
TABLENAME COLUMNAME AVERAGE SUM
REGIONS REGION_ID 2.5 10
COUNTRIES REGION_ID 2.4 60
LOCATIONS LOCATION_ID 2100 48300
DEPARTMENTS DEPARTMENT_ID 140 3780
DEPARTMENTS MANAGER_ID 154.909090909090909090909090909090909091 1704
DEPARTMENTS LOCATION_ID 1777.777777777777777777777777777777777778 48000
JOBS MIN_SALARY 6573.052631578947368421052631578947368421 124888
JOBS MAX_SALARY 13215.1578947368421052631578947368421053 251088
EMPLOYEES EMPLOYEE_ID 153 16371
If you're running Oracle 12c and above, you may use DBMS_SQL.RETURN_RESULT
use the the same block above except for these changes..
DECLARE
..
v_cur SYS_REFCURSOR;
BEGIN
...
END LOOP;
v_sql := regexp_replace(v_sql,' UNION ALL $');
OPEN v_cur for v_sql;
DBMS_SQL.RETURN_RESULT(v_cur);
END;
/
ResultSet #1
TABLENAME COLUMNAME AVERAGE SUM
----------- ------------- ---------- ----------
REGIONS REGION_ID 2.5 10
COUNTRIES REGION_ID 2.4 60
LOCATIONS LOCATION_ID 2100 48300
DEPARTMENTS DEPARTMENT_ID 140 3780
DEPARTMENTS MANAGER_ID 154.909091 1704
DEPARTMENTS LOCATION_ID 1777.77778 48000
JOBS MIN_SALARY 6573.05263 124888
JOBS MAX_SALARY 13215.1579 251088
EMPLOYEES EMPLOYEE_ID 153 16371
9 rows selected.

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