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;
Related
I have a code which accepts a table name as parameter and creates subpartitions on that table name for the partitions. My table is partitioned on list of source system codes and range subpartitoned on monthly basis.
Question: If suppose I have variable list of tables and I want to create subpartitions for all then I need to modify this procedure every time to pass varying list of tables.
Can I use PLSQL VARRAY or nested table to hold my list of tables, and pass this VARRAY or nested table as a parameter to the below procedure and create subpartitions for all the table names the VARRAY or nested table is holding?
Your help is much appreciated. Many thanks!
Code:
CREATE OR REPLACE PROCEDURE execute_subpartition ( table_name IN varchar2)
IS
tbl_nm varchar2(30) := table_name;
sqlstr VARCHAR2(1000);
CURSOR TabSubPartition IS
SELECT TABLE_NAME, PARTITION_NAME
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = tbl_nm
ORDER BY PARTITION_NAME;
BEGIN
FOR aSubPart IN TabSubPartition LOOP
IF TRUNC(LAST_DAY(SYSDATE)) = TRUNC(SYSDATE)
sqlstr := 'ALTER TABLE TUCEL001.' || aSubPart.TABLE_NAME || ' MODIFY PARTITION ' ||
aSubPart.PARTITION_NAME ||' ADD SUBPARTITION ' || aSubPart.PARTITION_NAME || '_' ||
TO_CHAR(TRUNC(LAST_DAY(SYSDATE) + 1), 'MON_YYYY') ||' VALUES LESS THAN (TIMESTAMP ''' ||
TRIM(to_char(add_months((TRUNC(LAST_DAY(SYSDATE))+1), 1), 'SYYYY-MM-DD HH24:MI:SS',
'NLS_CALENDAR=GREGORIAN')) || ''')';
dbms_output.put_line(sqlstr);
EXECUTE IMMEDIATE sqlstr;
ELSE
dbms_output.put_line('the condition did not match');
END IF;
END LOOP;
Exception
WHEN OTHERS
THEN
dbms_output.put_line('encountered an error, because the sub-partitions which are being created
already exists');
END;
My 11g doesn't have partitioning enabled so I can't demonstrate it.
But, see if this example (of creating some tables) helps. You don't have to declare your own type - sys.odcivarchar2list should do. Read it using the table function and use its column_value in your dynamic SQL.
Procedure:
SQL> create or replace procedure p_test (par_tables in sys.odcivarchar2list) as
2 l_str varchar2(200);
3 begin
4 for cur_r in (select column_value as table_name
5 from table(par_tables)
6 )
7 loop
8 dbms_output.put_line('table name = ' || cur_r.table_name);
9 l_str := 'create table ' || dbms_assert.qualified_sql_name(cur_r.table_name) ||
10 ' (id number,' ||
11 ' name varchar2(20))';
12 execute immediate(l_str);
13 end loop;
14 end;
15 /
Procedure created.
Testing:
SQL> exec p_test(sys.odcivarchar2list('tab_a', 'tab_b'));
PL/SQL procedure successfully completed.
SQL> desc tab_a;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SQL> desc tab_b;
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SQL>
I would like to create a reusable Stored Procedure which can disable constraints in a table, when that table is passed as a parameter. I created a stored procedure which has the table name hard coded.Could you please help
Create or Replace Procedure Disable_Constraints
as Begin
Declare
V_OWNER VARCHAR2(30);
V_TABLE_NAME VARCHAR2(30);
V_CONSTRAINT_NAME VARCHAR2(30);
CURSOR C IS
select OWNER,TABLE_NAME,CONSTRAINT_NAME
from all_constraints
where table_name='EMP_CONSTRAINTS';
BEGIN
OPEN C;
LOOP
FETCH C INTO V_OWNER,V_TABLE_NAME,V_CONSTRAINT_NAME;
EXIT WHEN C%NOTFOUND;
dbms_utility.exec_ddl_statement ('alter table ' || V_OWNER || '.' || V_TABLE_NAME || ' disable constraint ' || V_CONSTRAINT_NAME);
END LOOP;
CLOSE C;
END;
END DISABLE_CONSTRAINTS;
Thanks,
Khader
For example:
Sample table:
SQL> CREATE TABLE test
2 (
3 id NUMBER CONSTRAINT pkt PRIMARY KEY,
4 name VARCHAR2 (20) CONSTRAINT cht CHECK (name LIKE 'A%')
5 );
Table created.
Procedure:
SQL> CREATE OR REPLACE PROCEDURE p_disc (par_table_name IN VARCHAR2)
2 IS
3 BEGIN
4 FOR cur_r
5 IN (SELECT constraint_name
6 FROM user_constraints
7 WHERE UPPER (table_name) =
8 UPPER (DBMS_ASSERT.sql_object_name (par_table_name)))
9 LOOP
10 EXECUTE IMMEDIATE
11 'alter table '
12 || DBMS_ASSERT.sql_object_name (par_table_name)
13 || ' disable constraint '
14 || cur_r.constraint_name;
15 END LOOP;
16 END;
17 /
Procedure created.
Testing:
SQL> SELECT constraint_name, status FROM user_constraints WHERE table_name = 'TEST';
CONSTRAINT_NAME STATUS
------------------------------ --------
CHT ENABLED
PKT ENABLED
SQL> EXEC p_disc('test');
PL/SQL procedure successfully completed.
SQL> SELECT constraint_name, status FROM user_constraints WHERE table_name = 'TEST';
CONSTRAINT_NAME STATUS
------------------------------ --------
CHT DISABLED
PKT DISABLED
SQL>
I would like to know where is the mistake in this procedure
set serveroutput on
create or replace procedure insert_table(column_name in varchar2, dat in varchar2)
as
table_name varchar2(100):= column_name || '_' || dat;
sql_create varchar2(100) := 'create table '||table_name||'(FIRSTNAME varchar2(100))';
sql_str varchar2(100);
CURSOR c_emp is
select FIRSTNAME
from employees
where column_name = dat;
begin
execute immediate sql_create;
for r_reg in c_emp
loop
sql_str:='INSERT INTO '||table_name||'('''||r_reg.firstname||''')';
execute immediate sql_str;
end loop;
end;
/
execute insert_table('CITY','London');
Edit:
Ok i add the correction mentioned below in the syntax error, but how can I do so that the parameter of the column name can be taken at the cursor, because for now it is of type varchar and 'CITY' should be a row name.
You will need to use the dynamic query in the cursor as follows. Also, you missed VALUES keyword in the INSERT statement which I have added (Please see inline comment in the codes)
Oracle sample data creation:
SQL> CREATE TABLE "EMPLOYEES" (
2 "ID" NUMBER,
3 "FIRSTNAME" VARCHAR2(100 BYTE),
4 "CITY" VARCHAR2(100 BYTE),
5 PRIMARY KEY ( "ID" ) USING INDEX ENABLE
6 );
Table created.
SQL>
Now, Let's create your procedure
SQL> CREATE OR REPLACE PROCEDURE INSERT_TABLE (
2 COLUMN_NAME IN VARCHAR2,
3 DAT IN VARCHAR2
4 ) AS
5
6 TABLE_NAME VARCHAR2(100) := COLUMN_NAME || '_' || DAT;
7 SQL_CREATE VARCHAR2(100) := 'create table ' || TABLE_NAME || '(FIRSTNAME varchar2(100))';
8 C_EMP SYS_REFCURSOR; -- declaration of cursor
9 LV_FNAME EMPLOYEES.FIRSTNAME%TYPE; -- to store the each value from cursor
10 BEGIN
11 EXECUTE IMMEDIATE SQL_CREATE;
12 OPEN C_EMP FOR 'SELECT FIRSTNAME
13 FROM EMPLOYEES
14 WHERE ' || COLUMN_NAME || ' = ''' || DAT || '''';
15 -- above statement used dynamic query in cursor
16 LOOP
17 FETCH C_EMP INTO LV_FNAME;
18 EXIT WHEN C_EMP%NOTFOUND;
19 EXECUTE IMMEDIATE 'INSERT INTO ' || TABLE_NAME || ' VALUES (''' || LV_FNAME || ''')'; -- added VALUES keyword in INSERT statement.
20 END LOOP;
21
22 COMMIT;
23 END INSERT_TABLE;
24 /
Let's execute it and see the result now.
SQL>
SQL> EXECUTE INSERT_TABLE('CITY', 'London');
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> SELECT * FROM CITY_LONDON;
FIRSTNAME
--------------------------------------------------------------------------------
TEJASH
SQL>
Ohh Yes, It created the desired table and also data is populated correctly.
Cheers!!
You are missing quotes. This
sql_str:='INSERT INTO '||table_name||'('||r_reg.firstname||');';
should be
sql_str:='INSERT INTO '||table_name||'('''||r_reg.firstname||''');';
This will turn INSERT INTO CITY_London (Peter); into INSERT INTO CITY_London ('Peter');.
And according to the docs (e.g. https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/dynamic.htm):
When constructing a single SQL statement in a dynamic string, do not include a semicolon (;) at the end inside the quotation mark.
So the two SQL strings for EXECUTE IMMEDIATE should be:
sql_create varchar2(100) := 'create table ' || table_name || '(FIRSTNAME varchar2(100))';
and
sql_str := 'INSERT INTO ' || table_name || '(''' || r_reg.firstname || ''')';
It is the answer for your last question. You can pass parameters to the cursor like there:
declare
CURSOR c_emp(p_param varchar2) is
select p_param val
from dual;
begin
for r_reg in c_emp('b')
loop
dbms_output.put_line(r_reg.val);
end loop;
end;
I write one dynamic SQL which the result of it is a table with 2 columns and multiple rows, I want to insert it to another table with 4 columns that 2 of them will be filled by the result of dynamic SQL, I try to use collection but don't know how to insert result to another table
CREATE OR REPLACE PROCEDURE P_C_SM_Failure_error_Code_P2P AS
v_month VARCHAR2(16); -- to get Month for each table
v_day VARCHAR2(16); -- to get day for each table
v_ERRCODE t_c_rpt_resultmsg.code%TYPE;
v_ERRMSG t_c_rpt_resultmsg.MESSAGE%TYPE;
v_param VARCHAR2(16);
v_sql VARCHAR2(3000);
v_result number;
type t_c_result is record (Err_code varchar2(2000), Err_count number);
type v_t_result is table of t_c_result index by PLS_INTEGER;
v_t1_result v_t_result;
BEGIN
v_sql :='0';
v_param := 'Gateway_G';
v_result := '0';
select to_char(sysdate - 1,'MM') into v_month from dual;
select to_char(sysdate - 1,'DD') into v_day from dual;
-- Get count of P2P
v_sql := '(select count(*), error_code from (
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC01 where
orgaccount = '''||v_param||''' and destaccount = '''||v_param||''' and
sm_status <> 1 union all
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC02 where
orgaccount = '''||v_param||''' and destaccount = '''||v_param||''' and
sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql bulk collect into v_t1_result;
--insert into t_c_rpt_result2 values (trunc(sysdate, 'DD'), v_errcount,
v_err_code,'Failure_error_Code_P2P');
--for indx in 1 .. v_t1_result.COUNT
--loop
--dbms_output.put_line (v_t1_result (indx).Err_code);
--end loop;
You may append the constant values of date and the error message to the subquery and run a dynamic insert. It should also work if you remove the outer parentheses of your dynamic sql since constants can be included in group by. Always remember to pass values as bind variables rather than concatenating them (v_param). Also, specify the column names explicitly in an INSERT statement.
v_sql := '(select count(*) as cnt, error_code
from (
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC01
where orgaccount = :x and destaccount = :x and sm_status <> 1
union all
select error_code from sm_histable'||v_month||''||v_day||'#ORASMSC02
where orgaccount = :x and destaccount = :x and sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql bulk collect into v_t1_result using v_param;
EXECUTE IMMEDIATE 'insert into t_c_rpt_result2(err_dt,err_msg,errcount,error_code)
select :dt,:msg,cnt,error_code from '|| v_sql
USING trunc(sysdate, 'DD'),'Failure_error_Code_P2P',v_param;
I think you are looking at an excellent use case for FORALL. The collection you are populating needs to be done with execute immediate since you are dynamically constructing the table name. But the insert into t_c_rpt_result2 looks static to me.
BEGIN
v_sql :=
'(select count(*) as cnt, error_code
from (
select error_code from sm_histable'
|| v_month
|| ''
|| v_day
|| '#ORASMSC01
where orgaccount = :x and destaccount = :x and sm_status <> 1
union all
select error_code from sm_histable'
|| v_month
|| ''
|| v_day
|| '#ORASMSC02
where orgaccount = :x and destaccount = :x and sm_status <> 1 )
group by error_code)';
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_t1_result USING v_param;
FORALL indx IN 1 .. v_t1_result.COUNT
INSERT INTO t_c_rpt_result2 (err_dt,
err_msg,
errcount,
ERROR_CODE)
VALUES (TRUNC (SYSDATE, 'DD'),
'Failure_error_Code_P2P',
v_t1_result (indx).cnt,
v_t1_result (indx).ERROR_CODE);
END;
Find more examples of FORALL on LiveSQL here. Of course, even if your insert was dynamic, you can use FORALL - put the execute immediate directly "inside" the FORALL statement. But I don't think that complexity is justified here.
Hope that helps!
I have created the below procedure to only retain the last two months data only and delete the rest one against a table in oracle, below is the procedure but i am getting exception, please advise how to overcome from this
create or replace package TEST_TABLE AS
PROCEDURE TEST_TABLE;
END TEST_TABLE;
create or replace PACKAGE BODY TEST_TABLE AS
PROCEDURE TEST_TABLE IS
BEGIN
FOR cc IN
(
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
)
LOOP
BEGIN
IF sysdate >= ADD_MONTHS(cc.high_value,2) THEN
EXECUTE IMMEDIATE
'ALTER TABLE TEST_TABLE DROP PARTITION ' || cc.partition_name;
Dbms_Output.Put_Line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION WHEN Others THEN Dbms_Output.Put_Line( SQLERRM );
END TEST_TABLE;
END TEST_TABLE;
The error that I am getting is:
Error(12,6): PL/SQL: Statement ignored
Error(12,20): PLS-00306: wrong number or types of arguments in call to 'ADD_MONTHS'
Firstly, It's insane to call table name, package name and procedure name all by TEST_TABLE as being done by you, as if there's no other name available. I've named them appropriately.
HIGH_VALUE cannot be directly used in DATE related functions as it's of LONG TYPE. There's a simple method to convert it to date using dynamic SQL(EXECUTE IMMEDIATE)
CREATE OR replace PACKAGE BODY PKG_test_table AS
PROCEDURE pr_test_table
IS
v_high_value DATE;
BEGIN
FOR cc IN (
SELECT partition_name,
high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
) LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :v_high_val := '|| cc.high_value || '; END;'
USING OUT v_high_value;
IF
SYSDATE >= add_months(v_high_value,2)
THEN
EXECUTE IMMEDIATE 'ALTER TABLE TEST_TABLE DROP PARTITION '
|| cc.partition_name;
dbms_output.put_line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
END pr_TEST_TABLE;
END PKG_test_table;
/
Calling the procedure
BEGIN
PKG_test_table.pr_test_table;
END;
/
Your procedure does not accept any parameter. You can't pass any arguments to it.
The HIGH_VALUE column from USER_TAB_PARTITIONS is a long data type, I'm not going copy code from another web site, but if you google "oracle convert high value to date" you should get some ideas on how to create a function that you can use to convert the 'long' to a date.
My reputation is too low to post this as a comment, so I added it as an answer, it should help though it is not a good answer :(
As the error says it all ADD_MONTHS takes a DATE and you are passing in as LONG.
Try something like this and it should be ok.
Example:
DECLARE
DT LONG(1000) := 'TO_DATE('||''''||'2018-08-01 00:00:00'||''''||',' ||''''|| 'SYYYY-MM-DD HH24:MI:SS'||''''||','||''''||'NLS_CALENDAR=GREGORIAN'||''''||')';
BEGIN
DBMS_OUTPUT.PUT_LINE(DT);
EXECUTE IMMEDIATE
'BEGIN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(ADD_MONTHS('||DT||',2),'||''''||'YYYY-MM-DD HH24:MI:SS'||''''||
')); END;';
END;
Output:
TO_DATE('2018-08-01 00:00:00','SYYYY-MM-DD HH24:MI:SS','NLS_CALENDAR=GREGORIAN')
2018-10-01 00:00:00
Oracle does not allow functions over long such as cast, substr, add_months over long type however … read below.
Long type
describe user_tab_partitions;
...
SUBPARTITION_COUNT NUMBER
HIGH_VALUE LONG
HIGH_VALUE_LENGTH NUMBER
...
Function to convert long to varchar2
FUNCTION long_to_varchar2 ( p_table_owner IN VARCHAR2,p_table_name IN VARCHAR2, p_partition_name IN VARCHAR2) RETURN VARCHAR2
is
l_tmp long;
BEGIN
select high_value
into l_tmp
from all_tab_partitions
where table_owner = p_table_owner
and table_name = p_table_name
and partition_name = p_partition_name ;
RETURN l_tmp;
END long_to_varchar2;
3.Use your new function
select tpar."OWNER",tpar."TABLE_NAME",tpar."PART_MIN",tpar."PART_MIN_HV",tpar."PART_MAX",tpar."PART_MAX_HV",tpar."NR_PART"
,pkey.column_name as partitioned_by
,ptab.partitioning_type as partition_type
,ptab.status
from
(select p1.table_owner as owner
,p1.table_name
,pmin.partition_name as part_min
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmin.partition_name),11,10),'yyyy-mm-dd') as part_min_hv
,pmax.partition_name as part_max
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmax.partition_name),11,10),'yyyy-mm-dd') as part_max_hv
,p1.nr_part+1 as nr_part
from (select min(part.partition_position) as minp
,max(part.partition_position) as maxp
,count(*) as nr_part
,part.table_name
,part.table_owner
from all_tab_partitions part,
dba_tables tbls
where part.table_name=tbls.table_name
and part.table_owner=tbls.owner
and part.PARTITION_NAME <> 'P_CURRENT'
group by part.table_name, part.table_owner) p1
,all_tab_partitions pmin
,all_tab_partitions pmax
where p1.table_name = pmin.table_name
and p1.table_owner = pmin.table_owner
and p1.minp=pmin.partition_position
and p1.table_name = pmax.table_name
and p1.table_owner = pmax.table_owner
and p1.maxp = pmax.partition_position) tpar
,ALL_PART_KEY_COLUMNS pkey
,ALL_PART_TABLES ptab
where tpar.owner=pkey.owner
and tpar.table_name=pkey.name
and tpar.owner=ptab.owner
and tpar.table_name=ptab.table_name
and pkey.object_type='TABLE';
The only issue is that you will be doing an implicit varchar2 to date conversion and I see no way of doing it otherwise.