Related
I've created two tables: Employees and Departments
CREATE TABLE EMP
( emp_id number(3) PRIMARY KEY,
dept_id Number(3) NOT NULL,
emp_name Varchar2(50) NOT NULL,
address Varchar2(100),
phone Varchar2(20) NOT NULL,
salary Number(8,2) NOT NULL,
CONSTRAINT fk_DEPT FOREIGN KEY (dept_id) REFERENCES DEPT(DEPT_ID));
CREATE TABLE DEPT
( dept_id number(3) PRIMARY KEY,
dept_name varchar2(50) NOT NULL,
emp_cnt Number(3) NOT NULL)
I need to create trigger that changes value in DEPT.emp_cnt after inserting or deleting data in EMP table.
Here is my attempt
create or replace trigger add_emp_to_the_dep
after insert or delete on EMP
for each row
begin
update DEPT
set emp_cnt = :new.emp_id
where DEPT.dept_id = :new.dept_id;
if INSERTING then
emp_cnt += 1;
else DELETING then
emp_cnt -= 1;
end if;
end;
Wrong syntax; there's no such thing as emp_cnt += 1; in Oracle's PL/SQL.
Try something like this instead:
create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;
/
You can use a compound trigger to collate the changes and make the minimum number of updates:
CREATE TRIGGER add_emp_to_the_dep
FOR INSERT OR UPDATE OR DELETE ON emp
COMPOUND TRIGGER
TYPE ids_type IS TABLE OF EMP.DEPT_ID%TYPE;
TYPE cnt_type IS TABLE OF PLS_INTEGER;
TYPE idx_type IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
ids ids_type := ids_type();
cnts cnt_type := cnt_type();
idxs idx_type := idx_type();
PROCEDURE modify_dept_cnt (
id EMP.DEPT_ID%TYPE,
cnt PLS_INTEGER
)
IS
BEGIN
IF id IS NULL THEN
RETURN;
END IF;
IF NOT idxs.EXISTS(id) THEN
ids.EXTEND;
cnts.EXTEND;
ids(ids.COUNT) := id;
cnts(cnts.COUNT) := cnt;
idxs(id) := ids.COUNT;
ELSE
cnts(idxs(id)) := cnts(idxs(id)) + cnt;
END IF;
END modify_dept_cnt;
AFTER EACH ROW
IS
BEGIN
modify_dept_cnt(:NEW.DEPT_ID, 1);
modify_dept_cnt(:OLD.DEPT_ID, -1);
END AFTER EACH ROW;
AFTER STATEMENT
IS
BEGIN
FORALL i IN 1 .. ids.count
UPDATE dept
SET emp_cnt = emp_cnt + cnts(i)
WHERE dept_id = ids(i);
END AFTER STATEMENT;
END;
/
Then, if you do:
INSERT INTO emp (emp_id, dept_id, emp_name, phone, salary)
SELECT 1, 1, 'Alice', '0', 100 FROM DUAL UNION ALL
SELECT 2, 1, 'Betty', '1', 100 FROM DUAL UNION ALL
SELECT 3, 2, 'Carol', '2', 100 FROM DUAL UNION ALL
SELECT 4, 1, 'Debra', '3', 100 FROM DUAL UNION ALL
SELECT 5, 3, 'Emily', '4', 100 FROM DUAL UNION ALL
SELECT 6, 3, 'Fiona', '5', 100 FROM DUAL;
It will collate all the changes and UPDATE the DEPT table only 3 times, as employees for 3 unique DEPT_ID are added, rather than performing 6 updates, one for each inserted row.
db<>fiddle here
create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;
I need to copy stats from production to test database but all sample code I found use create_stat_table procedure, but I cannot create tables on source database. Is there any chance to do this different way? Maybe I can generate some INSERT statements from system views (like dba_tab_statistics) and run on destination db?
You can use the get_*_stats to fetch the details from prod, then pass these by calling set_*_stats in test.
For example, get the stats like this:
create table t as
select level tid, date'2022-01-01' + level c1
from dual
connect by level <= 100;
exec dbms_stats.gather_table_stats ( user, 't' ) ;
declare
numrows number;
numblks number;
avgrlen number;
begin
dbms_stats.get_table_stats (
ownname => user,
tabname => 'T',
numrows => numrows,
numblks => numblks,
avgrlen => avgrlen
);
dbms_output.put_line (
numrows || '; ' || numblks || '; ' || avgrlen
);
end;
/
100; 4; 11
Then load them like this (I'm dropping the table in the same DB for simplicity)
drop table t purge;
create table t ( tid integer, c1 date );
NUM_ROWS BLOCKS AVG_ROW_LEN
---------- ---------- -----------
<null> <null> <null>
select num_rows, blocks, avg_row_len
from user_tables
where table_name = 'T';
begin
dbms_stats.set_table_stats (
ownname => user,
tabname => 'T',
numrows => 10,
numblks => 4,
avgrlen => 11
);
end;
/
select num_rows, blocks, avg_row_len
from user_tables
where table_name = 'T';
NUM_ROWS BLOCKS AVG_ROW_LEN
---------- ---------- -----------
10 4 11
Copying the stats for all the columns and indexes this way is a faff though. If you need this, speak with whoever manages the database and get them to copy these over for you!
I'm trying to return a type as output parameter, but i'm getting error :
[Error] Compilation (12: 27): PLS-00382: expression is of wrong type
Here is the code:
create or replace TYPE OBJ_DCP FORCE as OBJECT (
ID NUMBER,
xxx_PROFILES_ID NUMBER,
DCP_NAME VARCHAR2(500 BYTE),
L_R_NUMBER VARCHAR2(150 BYTE),
STREET VARCHAR2(150 BYTE)
};
and
create or replace TYPE T_DCP_TYPE as TABLE OF OBJ_DCP;
and
create or replace procedure get_dcp_profiles(p_id in number, dcp_array_var OUT T_DCP_TYPE) is
cursor c_dcp_profiles is
select *
from table1 ca -- table1 has same structure of OBJ_DCP
where ca.id = p_id;
i number := 1;
begin
for r in c_dcp_profiles loop
dcp_array_var(i) := r; -- this line is errored
i := i + 1;
end loop;
end get_dcp_profiles;
Types are OK.
Sample table:
SQL> SELECT * FROM table1 ORDER BY id;
ID XXX_PROFILES_ID D L S
---------- --------------- - - -
1 1 a b c
1 2 d e f
2 3 x y z
SQL>
Procedure, modified:
SQL> CREATE OR REPLACE PROCEDURE get_dcp_profiles (
2 p_id IN NUMBER,
3 dcp_array_var OUT T_DCP_TYPE)
4 IS
5 CURSOR c_dcp_profiles IS
6 SELECT *
7 FROM table1 ca -- table1 has same structure of OBJ_DCP
8 WHERE ca.id = p_id;
9
10 i NUMBER := 1;
11 l_arr T_DCP_TYPE := T_DCP_TYPE ();
12 BEGIN
13 FOR r IN c_dcp_profiles
14 LOOP
15 l_arr.EXTEND;
16 l_arr (i) :=
17 OBJ_DCP (id => r.id,
18 xxx_profiles_id => r.xxx_profiles_id,
19 dcp_name => r.dcp_name,
20 l_r_number => r.l_r_number,
21 street => r.street);
22 i := i + 1;
23 END LOOP;
24
25 dcp_array_var := l_arr;
26 END get_dcp_profiles;
27 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_tab t_dcp_type;
3 BEGIN
4 get_dcp_profiles (1, l_tab);
5
6 FOR i IN 1 .. l_tab.COUNT
7 LOOP
8 DBMS_OUTPUT.put_line (l_tab (i).id || ' ' || l_tab (i).dcp_name);
9 END LOOP;
10 END;
11 /
1 a
1 d
PL/SQL procedure successfully completed.
SQL>
You do not need to use cursors or to manually populate the collection.
If you declare the table as an object-derived table:
CREATE TABLE table1 OF OBJ_DCP;
Then you can simplify the procedure down to:
CREATE PROCEDURE get_dcp_profiles(
p_id IN number,
dcp_array_var OUT T_DCP_TYPE
)
IS
BEGIN
SELECT VALUE(ca)
BULK COLLECT INTO dcp_array_var
FROM table1 ca
WHERE ca.id = p_id;
END get_dcp_profiles;
/
For the sample data:
INSERT INTO table1 (id, xxx_Profiles_id, DCP_NAME, L_R_NUMBER, STREET)
SELECT 1, 42, 'ABC name', 'ABC number', 'ABC street' FROM DUAL UNION ALL
SELECT 1, 63, 'DEF name', 'DEF number', 'DEF street' FROM DUAL UNION ALL
SELECT 2, 12, 'GHI name', 'GHI number', 'GHI street' FROM DUAL;
Then:
DECLARE
dcps t_dcp_type;
BEGIN
get_dcp_profiles (1, dcps);
FOR i IN 1 .. dcps.COUNT LOOP
DBMS_OUTPUT.PUT_LINE (dcps(i).id || ' ' || dcps(i).dcp_name);
END LOOP;
END;
/
Outputs:
1 ABC name
1 DEF name
If you do not want to use an object-derived table then you can use:
CREATE OR REPLACE PROCEDURE get_dcp_profiles(
p_id IN number,
dcp_array_var OUT T_DCP_TYPE
)
IS
BEGIN
SELECT OBJ_DCP(id, xxx_profiles_id, dcp_name, l_r_number, street)
BULK COLLECT INTO dcp_array_var
FROM table1
WHERE id = p_id;
END get_dcp_profiles;
/
db<>fiddle here
I have a procedure that does the INSERT INTO and then the UPDATE of some fields (both in the same procedure), I'm using this answer from #Clive Number of rows affected by an UPDATE in PL/SQLto know the amount of data that has been updated to put in a log, but it brings me the total number of rows instead of just the records that have been updated.
Is that the right way to know?
What I need is to know how many rows were INSERTED from the INSERT STATEMENT and how many rows were UPDATED from the UPDATE STATEMENT.
My query:
CREATE OR REPLACE PROCEDURE OWNER.TABLE_NAME
AS
-- VARIABLE
v_qtd_regs number := 0;
v_code number;
v_errm VARCHAR2(500);
start_time pls_integer;
end_time pls_integer;
elapse_time number;
proc_name varchar2(100);
i NUMBER;
BEGIN
proc_name := 'PRDWBI_CGA_D_COLUMNS';
start_time := dbms_utility.get_time;
DS_FUNCESP.PRDSBI_GRAVA_LOG( 'I', 'DataWarehouse', proc_name, 'Início Carga' );
-- INSERT INTO TABLE:
INSERT INTO OWNER.TABLE_NAME
(COLUMN_ID, COLUMNS_NAME, COLUMN_NAME2)
(SELECT 1 AS COLUMN_ID, 'TEST' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL);
COMMIT;
-- UPDATE SOME COLUMNS I NEED
UPDATE OWNER.TABLE_NAME y
SET (y.COLUMNS_NAME, y.COLUMN_NAME2) =
(SELECT 'TEST2' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL x WHERE x.COLUMN_ID = y.COLUMN_ID)
WHERE EXISTS (SELECT 'TEST2' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL x WHERE x.COLUMN_ID = y.COLUMN_ID);
-- TO KNOW HOW MANY ROWS WERE UPDATED
i := SQL%rowcount;
COMMIT;
--dbms_output.Put_line(i);
SELECT COUNT(1) INTO v_qtd_regs FROM OWNER.TABLE_NAME where LinData >= TRUNC(SYSDATE);
end_time := dbms_utility.get_time;
elapse_time := ((end_time - start_time)/100);
v_errm := SUBSTR(SQLERRM, 1 , 500);
DS_FUNCESP.PRDSBI_GRAVA_LOG('T', 'DataWarehouse', proc_name, v_errm, v_qtd_regs, elapse_time );
COMMIT;
EXCEPTION
WHEN OTHERS THEN
v_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1 , 500);
DS_FUNCESP.PRDSBI_GRAVA_LOG('E', 'Error', proc_name, v_errm);
END;
QUESTION EDITED TO SHOW A REAL EXAMPLE:
I created a table that takes data from "SYS.DBA_TAB_COLUMNS" just to use as an example, as shown below:
CREATE TABLE "DW_FUNCESP"."D_TEST"
(
"ID_COLUMN" NUMBER(10,0) GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1
START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
"NM_OWNER" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"NM_TABLE" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"CD_COLUMN" NUMBER(20,0) NOT NULL ENABLE ,
"NM_COLUMN" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"DS_COLUMN" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"LINDATE" DATE DEFAULT SYSDATE NOT NULL ENABLE ,
"LINORIGIN" VARCHAR2(100 CHAR) NOT NULL ENABLE
)
Then I created a procedure to identify the inserted and updated records, as below:
CREATE OR REPLACE PROCEDURE DW_FUNCESP.PRDWBI_CGA_D_TEST
AS
-- variaveis de suporte as informações que deve gravar
rows_inserted integer;
rows_updated integer;
BEGIN
-- Insert Into table
INSERT INTO DW_Funcesp.D_TEST
(NM_OWNER, NM_TABLE, CD_COLUMN, NM_COLUMN, DS_COLUMN, LINDATE, LINORIGIN)
(SELECT
NVL(x.NM_OWNER ,'NOT FOUND') AS NM_OWNER ,
NVL(x.NM_TABLE ,'NOT FOUND') AS NM_TABLE ,
NVL(x.CD_COLUMN ,-1) AS CD_COLUMN ,
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE ,
'SYS.DBA_TAB_COLUMNS' AS LINORIGIN
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
LEFT JOIN DW_FUNCESP.D_TEST y
ON y.NM_OWNER = x.NM_OWNER
AND y.NM_TABLE = x.NM_TABLE
AND y.NM_COLUMN = x.NM_COLUMN
WHERE y.ID_COLUMN IS NULL);
rows_inserted := sql%rowcount;
-- Update the table
UPDATE DW_FUNCESP.D_TEST z
SET (z.NM_COLUMN, z.DS_COLUMN, z.LINDATE) =
(SELECT
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
WHERE z.NM_OWNER = x.NM_OWNER
AND z.NM_TABLE = x.NM_TABLE
AND z.CD_COLUMN = x.CD_COLUMN)
WHERE EXISTS (SELECT
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
WHERE z.NM_OWNER = x.NM_OWNER
AND z.NM_TABLE = x.NM_TABLE
AND z.CD_COLUMN = x.CD_COLUMN);
rows_updated := sql%rowcount;
dbms_output.Put_line('inserted=>' || to_char(rows_inserted) || ', updated=>' || to_char(rows_updated));
COMMIT;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END;
So my first insert output was:
inserted=>2821, updated=>2821
So I chose a data to be changed and it was updated, I made the following select to choose which data should be updated to bring in the DBMS output again:
SELECT * FROM DW_FUNCESP.D_TEST WHERE NM_TABLE = 'D_TEST';
I commented in a column as shown in the image, to bring in the update:
COMMENT ON COLUMN DW_FUNCESP.D_TEST.LINORIGIN IS 'The origin of the data';
I ran the procedure again, and the output was:
inserted=>0, updated=>2821
The result for that update:
Shouldn't you have brought just 1 updated data in the output, as only 1 updated? And not all the rows?
e.g.: inserted=>0, updated=>1
So my question remains, am I asking it correctly? Is it possible to obtain this result in the same procedure? Is it the update that is incorrect (despite having updated the data)?
You are not getting the rows inserted and rows updated. SQL%rowcount contains ONLY the number rows from the last select or DML statement. Since you set your variable only after the Update your only get the number of updates. If you want both then you need a separate variable for each.
Hint: There is no need to commit after each DML, actually that is ofter considered a very poor practice. You need to study as bit on transactions. The basic idea being that all operations complete successfully or none of them complete successfully. Look up ATOMIC and Atomicity.
So your revised procedure becomes:
create or replace procedure owner.table_name
as
-- VARIABLE
v_qtd_regs number := 0;
v_code number;
v_errm varchar2(500);
start_time pls_integer;
end_time pls_integer;
elapse_time number;
proc_name varchar2(100);
rows_inserted integer;
rows_updated integer;
begin
proc_name := 'PRDWBI_CGA_D_COLUMNS';
start_time := dbms_utility.get_time;
ds_funcesp.prdsbi_grava_log( 'I', 'DataWarehouse', proc_name, 'Início Carga' );
insert into owner.table_name
(column_id, columns_name, column_name2)
(select 1 as column_id, 'TEST' as column_name, sysdate as column_name2 from dual);
rows_inserted := sql%rowcount;
update owner.table_name y
set (y.columns_name, y.column_name2) =
(select 'TEST2' as column_name, sysdate as column_name2 from dual x where x.column_id = y.column_id)
where exists (select 'TEST2' as column_name, sysdate as column_name2 from dual x where x.column_id = y.column_id);
rows_updated := sql%rowcount;
dbms_output.Put_line('inserted=>' || to_char(rows_inserted) || ', updated=>' || tp_char(rows_updated));
select count(1) into v_qtd_regs from owner.table_name where lindata >= trunc(sysdate);
end_time := dbms_utility.get_time;
elapse_time := ((end_time - start_time)/100);
v_errm := substr(sqlerrm, 1 , 500);
ds_funcesp.prdsbi_grava_log('T', 'DataWarehouse', proc_name, v_errm, v_qtd_regs, elapse_time );
commit;
exception
when others then
v_code := sqlcode;
v_errm := substr(sqlerrm, 1 , 500);
ds_funcesp.prdsbi_grava_log('E', 'Error', proc_name, v_errm);
end;
Try to add the instruction i := SQL%rowcount; after each DML:
after INSERT to have the number of inserted rows
after UPDATE to have the number of updated rows
I would use ORA_SCN as the other answers suggest if you are interested what rows have been inserted or updated. But you want only to know how many, so I would leave the counting to Oracle (might be timeconsuming for larger number of rows).
Please have a look at the data dictionary view USER_TAB_MODIFICATIONS (or ALL_TAB_MODIFICATIONS if the table is in another schema than the procedure.
CREATE TABLE d (
id NUMBER GENERATED ALWAYS AS IDENTITY,
dt DATE DEFAULT SYSDATE,
foo VARCHAR2(128 BYTE)
);
Gathering the table statistics will reset the modifications view:
EXEC DBMS_STATS.GATHER_TABLE_STATS(NULL,'D');
Now after your INSERT, the modifications view will have the number of inserted rows:
INSERT INTO d(foo) SELECT object_name FROM all_objects;
67,141 rows inserted.
SELECT inserts, updates, deletes FROM user_tab_modifications WHERE table_name='D';
INSERTS UPDATES DELETES
67141 0 0
Likewise, after the UPDATE, the updated rows:
UPDATE d SET foo=lower(foo),dt=SYSDATE WHERE mod(id,10)=0;
6,714 rows updated.
SELECT inserts, updates, deletes FROM user_tab_modifications WHERE table_name='D';
INSERTS UPDATES DELETES
67141 6714 0
For clarity, I've used SQL instead of PL/SQL. You might have to grant some special privs to the schema containing the procedure. Add a comment with my name if you run into problems with that.
I'm using the below query to get the Max Range of all the Columns of all tables in a Oracle Database
select OWNER,TABLE_NAME,COLUMN_NAME,DATA_PRECISION,(POWER(10,DATA_PRECISION) -1)
from ALL_TAB_COLUMNS where OWNER = 'MASTER' and DATA_TYPE = 'NUMBER' and
DATA_PRECISION is NOT NULL order by TABLE_NAME ASC;
OWNER TABLE_NAME COLUMN_NAME DATA_PRECISION (POWER(10,DATA_PRECISION) -1)
MASTER TABLE_1 COL_1 7 9999999
MASTER TABLE_1 COL_5 7 9999999
MASTER TABLE_2 COL_3 10 9999999999
I am trying to get a 6th & 7th Column, which should be max of that column in that particular table and difference between Max Range( i.e (POWER(10,DATA_PRECISION) -1)) and actual max value in the table.
Ex:
OWNER TABLE_NAME COLUMN_NAME DATA_PRECISION (POWER(10,DATA_PRECISION) -1) MAX_VALUE DIFF
MASTER TABLE_1 COL_1 7 9999999 9994637 5362
MASTER TABLE_1 COL_5 7 9999999 9997637 2362
MASTER TABLE_2 COL_3 10 9999999999 8933999999 1066000000
How to achieve this ?
Would i be able to join tables, using TABLE_NAME ?
Bet, this would be helpful to a lot of people.
The easiest way to go about this is to write a small function that you can provide a table name and column like so:
CREATE OR REPLACE FUNCTION get_max_value (p_table_name VARCHAR2, p_column VARCHAR2)
RETURN NUMBER IS
v_query VARCHAR2(1000);
v_max_value NUMBER;
BEGIN
v_query := 'SELECT MAX (' || p_column ||') FROM '
|| p_table_name ;
EXECUTE IMMEDIATE v_query
INTO v_max_value;
RETURN v_max_value;
END;
And then a slightly modified version of your query above:
SELECT owner,
table_name,
column_name,
data_precision,
(POWER(10,data_precision) -1) ,
get_max_value (owner || '.' || table_name, column_name) max_val
FROM all_tab_columns
WHERE 1 = 1
AND owner = 'MASTER'
AND data_type = 'NUMBER'
AND data_precision is NOT NULL
AND table_name NOT LIKE '%$%'
ORDER BY c.table_name ASC
Just be careful, depending on the number of your tables and the number of rows in each, execution can take a while.
Convert DBA_TAB_COLUMNS.HIGH_VALUE to the maximum value as described in this post by Jonathan Lewis.
The maximum and minimum values are recorded when table statistics are gathered. If the default estimate percent is used, and the statistics were gathered recently, the value will be accurate.
create or replace function raw_to_num(i_raw raw)
return number
as
m_n number;
begin
dbms_stats.convert_raw_value(i_raw,m_n);
return m_n;
end;
/
select owner,table_name,column_name,data_precision
,(power(10,data_precision) -1) max_range
,raw_to_num(high_value) max_value
,(power(10,data_precision) -1) - raw_to_num(high_value) diff
from dba_tab_columns
where data_type = 'NUMBER'
and owner = user
--Add conditions for specific users and tables.
order by owner, table_name, column_name;