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.
Related
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
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>
I have the following query, which returns the number of rows per table in a schema.
Can this also be modified to RETURN the number of columns per table too?
CREATE table stats (
table_name VARCHAR2(128),
num_rows NUMBER,
num_cols NUMBER
);
/
DECLARE
val integer;
BEGIN
for i in (SELECT table_name FROM all_tables WHERE owner = 'Schema')
LOOP
EXECUTE IMMEDIATE 'SELECT count(*) from ' || i.table_name INTO val;
INSERT INTO stats VALUES (i.table_name,val);
END LOOP;
END;
/
You can use the ALL_TAB_COLS dictionary table:
DECLARE
val integer;
BEGIN
FOR i IN (
SELECT table_name,
COUNT(*) AS num_cols
FROM all_tab_cols
WHERE owner = 'Schema'
GROUP BY table_name
)
LOOP
EXECUTE IMMEDIATE 'SELECT count(*) from ' || i.table_name INTO val;
INSERT INTO stats VALUES (i.table_name,val, i.num_cols);
END LOOP;
END;
/
db<>fiddle here
How to fetch odd columns in Oracle using a query when number of columns and name of columns are not known?
E.g.:
I need to get output in below format
Column1 column3 column5 column7
And so on....
You need to use the dynamic queries in the procedure as follows:
SQL> CREATE OR REPLACE PROCEDURE ODD_COLUMNS (
2 TABLE_NAME_P IN VARCHAR2,
3 DATAA OUT SYS_REFCURSOR
4 ) AS
5 V_SQL VARCHAR2(4000);
6 BEGIN
7 SELECT
8 'SELECT '
9 ||
10 LISTAGG(COLUMN_NAME, ',') WITHIN GROUP(
11 ORDER BY
12 COLUMN_ID
13 )
14 || ' FROM "'
15 || TABLE_NAME_P
16 || '"'
17 INTO V_SQL
18 FROM
19 USER_TAB_COLS
20 WHERE
21 TABLE_NAME = TABLE_NAME_P
22 AND MOD(COLUMN_ID, 2) = 1;
23
24 OPEN DATAA FOR V_SQL;
25
26 END ODD_COLUMNS;
27 /
Procedure created.
SQL>
Now, Let's test it:
SQL> variable rc refcursor;
SQL> exec ODD_COLUMNS('EMP',:rc);
PL/SQL procedure successfully completed.
SQL> print rc;
EMP_ID E
---------- -
10 N
20 Y
SQL>
SQL> exec ODD_COLUMNS('MY_TABLE1',:rc);
PL/SQL procedure successfully completed.
SQL> print rc;
ID REQ_QTY
---------- ----------
1001 10
1001 20
1001 30
1002 40
1003 10
1003 20
6 rows selected.
SQL>
Cheers!!
This cannot be done simply, but it is possible using the Oracle data dictionary and some dynamic SQL.
To find out the odd-numbered columns you need to look at the ALL_TAB_COLUMNS view. Column COLUMN_ID sequences the columns 1,2,3. So this will find all the odd-numbered columns in the SCOTT.EMP table:
select column_name, column_id
from all_tab_columns
where owner = 'SCOTT'
and table_name = 'EMP'
and mod(column_id,2) = 1
order by column_id;
This will return something like:
COLUMN_NAME COLUMN_ID
----------- ---------
EMPNO 1
JOB 3
HIREDATE 5
COMM 7
We can use the LISTAGG function to make that into a comma-separated list:
select listagg(column_name,',') within group (order by column_id) as result
from user_tab_columns
where table_name = 'EMP'
and mod(column_id,2) = 1;
RESULT
------
EMPNO,JOB,HIREDATE,COMM
Now we can add to that SQL to generate the select statement you want:
select 'select ' || listagg(column_name,',') within group (order by column_id) || ' from ' || table_name as sql
from user_tab_columns
where table_name = 'EMP'
and mod(column_id,2) = 1
group by table_name;
SQL
---
select EMPNO,JOB,HIREDATE,COMM from EMP
(Note I had to add a group by clause because table_name is not being aggregated by LISTAGG).
You could use that SQL within some PL/SQL code to populate a variable v_sql, then use the DBMS_SQL package to run it. But that is a complex topic in itself and I won't go into it here.
I would like to replace all the cells of a table that match a specific word. I wrote this query:
UPDATE table_name
SET column_name=REPLACE(column_name
,'string_to_be_replaced'
, 'string_replaced')
What will the procedure that will replace the values for all the columns of table_name not only one column as in the code above?
It is something that I will have to do it againg and again to update some tables.
Thanks
Here is some test data:
SQL> select * from t23;
ID NAME JOB
---------- -------------------- --------------------
10 JACK JANITOR
20 JAN TUTOR
30 MOHAN JAZZ DANCER
40 JOAN MECHANIC
SQL>
I want to replace all instances of 'JA' with 'MO'. This means I need to update NAME and JOB. Obviously I could write an UPDATE statement but I can also generate one using the magic of the data dictionary:
SQL> select column_name, data_type
2 from user_tab_cols
3 where table_name = 'T23';
COLUMN_NAME DATA_TYPE
------------------------------ ----------
ID NUMBER
NAME VARCHAR2
JOB VARCHAR2
SQL>
This seems like a one-off task, for which I need an anonymous PL/SQL block rather than a permanent procedure. So here is a script, saved as gen_upd_stmt.sql.
declare
stmt varchar2(32767);
target_string varchar2(20) := 'JA';
replace_string varchar2(20) := 'MO';
begin
stmt := 'update t23 set ';
for lrec in ( select column_name
, row_number() over (order by column_id) as id
from user_tab_cols
where table_name = 'T23'
and data_type = 'VARCHAR2'
)
loop
if lrec.id > 1 then
stmt := stmt || ',';
end if;
stmt := stmt || lrec.column_name || '=replace('
|| lrec.column_name || ', ''' || target_string
|| ''',''' || replace_string
|| ''')';
end loop;
-- uncomment for debugging
-- dbms_output.put_line(stmt);
execute immediate stmt;
dbms_output.put_line('rows updated = '|| to_char(sql%rowcount));
end;
/
Note that generating dynamic SQL is a gnarly process, because syntax errors are thrown at run time rather than compile time. Escaping quotes can be particularly pestilential. It's a good idea to display the generated statement to make debugging easier.
Also, I restricted the targeted columns to those with the correct datatype. This isn't strictly necessary, as replace() will handle type casting for us (in most cases). But it's more efficient with big tables to exclude columns we know won't match.
Anyway, let's roll!
SQL> set serveroutput on
SQL> #gen_upd_stmt
rows updated = 4
PL/SQL procedure successfully completed.
SQL>
As expected all four rows are updated but not all are changed:
SQL> select * from t23;
ID NAME JOB
---------- -------------------- --------------------
10 MOCK MONITOR
20 MON TUTOR
30 MOHAN MOZZ DANCER
40 JOAN MECHANIC
SQL>
For completeness the generated statement was this:
update t23 set NAME=replace(NAME, 'JA','MO'),JOB=replace(JOB, 'JA','MO')
With a larger table or more complicated requirement I would probably introduce line breaks with chr(13)||chr(10) to make the generated code more readable (for debugging).
You can try something like this. Rest update it as per your requirement.
DECLARE
L_statement VARCHAR2(4000) := 'UPDATE :table_name SET ';
CURSOR c_get_cols IS
SELECT column_name
FROM dba_tab_cols
WHERE table_name = :table_name;
TYPE Cur_tab IS TABLE OF c_get_cols%ROWTYPE;
L_tab Cur_tab;
BEGIN
OPEN c_get_cols;
FETCH C_get_cols INTO L_tab;
CLOSE C_get_cols;
FOR i IN 1..L_tab.COUNT
LOOP
L_statement := L_statement || L_tab(i).column_name || ' = REPLACE(column_name, :string_to_be_replaced, :string_replaced)';
IF i != L_tab.COUNT
THEN
L_statement := L_statement || ',';
END IF;
END LOOP;
EXECUTE IMMEDIATE L_statement;
END;
/
I tried it using cursor: (replace owner and table_name with respective values)
DECLARE
COL_NAME ALL_TAB_COLUMNS.COLUMN_NAME%TYPE;
string_to_be_replaced VARCHAR2(20) ;
string_replaced VARCHAR2 (20) ;
exc_invalid_id EXCEPTION;
PRAGMA EXCEPTION_INIT(exc_invalid_id, -904);
CURSOR c1 IS
SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS WHERE owner=<owner_name> AND TABLE_NAME=<table_name> ;
BEGIN
string_to_be_replaced :='';
string_replaced :='';
OPEN C1;
LOOP
FETCH C1 INTO COL_NAME ;
EXIT WHEN C1%NOTFOUND;
EXECUTE immediate('UPDATE <owner_name>.<table_name> SET '||col_name||'=REPLACE('||col_name||','''||string_to_be_replaced||''','''||string_replaced||''')');
END LOOP;
CLOSE C1;
END;