Bulk Update in Oracle PL/SQL - oracle

I have a table like this:
table_name SQL_statement id_value result
employee select ... where ..= :1 101
customer select ....where ..= :1 903
there will be about 6-8 different tables
the ID_VALUE is supplied as parameter to SQL_Statement
the SQL statement will always return a single row.
the result of "c" above should be stored in result column
I know I can use EXECUTE IMMEDIATE, but that will create hard parsing in SGA.
What kind of BULK technique can be used here ?

If you can assume that the statement always extracts a single value with a fixed alias, you can try the following.
setup:
create table yourTable(table_name varchar2(30), SQL_statement varchar2(100), id_value number, result number);
insert into yourTable
(
select 'employee', 'select :1 *2 as result from dual', 1, null from dual union all
select 'customer', 'select :1 as result from dual', 10, null from dual
);
Given the alias result, you can use a single update statement.
update:
update yourTable
set result = extractvalue(xmltype(dbms_xmlgen.getxml(replace (SQL_statement, ':1', id_value))),'/ROWSET/ROW/RESULT')
result:
SQL> select *
2 from yourTable;
TABLE_NAME SQL_STATEMENT ID_VALUE RESULT
---------- ---------------------------------------- ---------- ----------
employee select :1 *2 as result from dual 1 2
customer select :1 as result from dual 10 10

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>

How to get date range partition names on a table between two dates (Given date range)

I am a developer. In my table, I had a date range partitions.
I want to get partition names which are defined for a table between two dates.
I tried with below query and it is returning all the partitions on a table.
select * from USER_TAB_PARTITIONS WHERE TABLE_NAME = 'TABLE NAME' ORDER BY PARTITION_NAME;
My requirement is , I will pass two dates as inputs and between those two dates i want to get partition names.
Please suggest query.
That's not very simple; the major obstacle is user_tab_partitions.high_value datatype, which is long, and it is difficult to work with. Usually you get
ORA-00932: inconsistent datatypes: expected - got LONG
error.
However, using a few steps, it can be done. Have a look at this example.
Create a partitioned table and insert a few rows into it:
SQL> CREATE TABLE test_part
2 (
3 datum DATE,
4 text VARCHAR2 (10)
5 )
6 PARTITION BY RANGE (datum)
7 INTERVAL ( NUMTODSINTERVAL (1, 'day') )
8 (PARTITION p0 VALUES LESS THAN (TO_DATE ('01.01.2020', 'dd.mm.yyyy')));
Table created.
SQL> INSERT INTO test_part
2 SELECT DATE '2015-08-15', 'Little' FROM DUAL
3 UNION ALL
4 SELECT DATE '2020-03-26', 'Foot' FROM DUAL;
2 rows created.
What does user_tab_partitions say?
SQL> SELECT table_name, partition_name, high_value
2 FROM user_tab_partitions
3 WHERE table_name = 'TEST_PART';
TABLE_NAME PARTITION_NAME HIGH_VALUE
--------------- --------------- -----------------------------------
TEST_PART P0 TO_DATE(' 2020-01-01 00:00:00', 'SY
YYY-MM-DD HH24:MI:SS', 'NLS_CALENDA
R=GREGORIA
TEST_PART SYS_P63 TO_DATE(' 2020-03-27 00:00:00', 'SY
YYY-MM-DD HH24:MI:SS', 'NLS_CALENDA
R=GREGORIA
So, you'd want to extract date part from the high_value column. The first step is kind of a stupid one - create a new table; basically CTAS:
SQL> CREATE TABLE temp_utp
2 AS
3 SELECT table_name, partition_name, TO_LOB (high_value) high_value
4 FROM user_tab_partitions;
Table created.
For simplicity (in further steps), I'll create a view based on that table which will extract date value (line #5):
SQL> CREATE OR REPLACE VIEW v_utp
2 AS
3 SELECT table_name,
4 partition_name,
5 TO_DATE (SUBSTR (high_value, 12, 10), 'rrrr-mm-dd') datum
6 FROM temp_utp;
View created.
The rest is easy now:
SQL> SELECT *
2 FROM v_utp
3 WHERE datum < DATE '2020-02-15';
TABLE_NAME PARTITION_NAME DATUM
--------------- --------------- ----------
TEST_PART P0 2020-01-01
SQL>
OK, you'd use two date parameters which would then lead to between in the final query, but that's easy to modify.
Major drawback here is CTAS which creates temp_utp table; you'd have to recreate it as frequently as you add new partitions into the main table. One option is to do it in a scheduled manner, e.g. using a database job (see dbms_job and/or dbms_scheduler documentation if you don't know how) which would schedule a stored procedure which will then use dynamic SQL, i.e. execute immediate to create temp_utp. You don't have to recreate a view - it will become valid as soon as a new temp_utp table is created.
I was trying to find solution for the same problem and found that creating a function to convert high value to date works fine like below
CREATE OR REPLACE FUNCTION get_high_value_date(p_partition_name IN VARCHAR2)
RETURN DATE
IS
l_varchar_date VARCHAR2(4000);
l_date DATE;
BEGIN
EXECUTE IMMEDIATE 'SELECT high_value FROM all_tab_partitions WHERE partition_name = :1 ' INTO l_varchar_date
USING p_partition_name;
EXECUTE IMMEDIATE 'SELECT '||l_varchar_date||' FROM dual' INTO l_date;
RETURN l_date;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ( 'Exc: '||SQLERRM );
RETURN NULL;
END;
Then you can use this for getting date value
SELECT get_high_value_date (partition_name) partition_date, partition_name
FROM all_tab_partitions
WHERE table_name = :table

How to find currval in oracle 12c for identity columns

I create a table in oracle 12 with a column as identity. The problem is that I want to find the current value of identity column. How I can find this, please someone help me to solve this problem...
You could make use of the data dictionary views *_TAB_IDENTITY_COLS
Here is a working example.
create TABLE t ( ID INTEGER GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR2(10));
Table created.
INSERT INTO t(NAME) VALUES ( 'TESTER' );
1 row(s) inserted.
select SEQUENCE_NAME FROM user_tab_identity_cols WHERE TABLE_NAME ='T' ;
SEQUENCE_NAME
-----------
ISEQ$$_1727054
Now you could get the currval from this sequence.
select ISEQ$$_1727054.CURRVAL FROM DUAL;
CURRVAL
-------
1
LIVESQL DEMO - Free OTN account required.
Why do you want to know? If to insert a child row you can use the returning clause of the insert statement like this:
insert into master (...) values (...)
returning master_id into l_master_id;
insert into child (master_id, ...) values (l_master_id, ...);
As I've written in this blog post, this query produces all the identities' backing sequences' currval values of your schema:
with
function current_value(p_table_name varchar2) return number is
v_current number;
begin
for rec in (
select data_default
from user_tab_cols
where table_name = p_table_name
and data_default is not null
and identity_column = 'YES'
)
loop
execute immediate replace(
'select ' || rec.data_default || ' from dual',
'.nextval',
'.currval'
) into v_current;
return v_current;
end loop;
return null;
end;
select *
from (
select table_name, current_value(table_name) current_value
from user_tables
)
where current_value is not null
order by table_name;
/
The output could be something like this:
TABLE_NAME CURRENT_VALUE
--------------------------
T1 3
T2 1

How get another column value from the same row if we update any column of that row in oracle trigger?

TABLE NAME : TEST
COLUMNS : ID,NAME
I am using ORACLE database.
I have written one trigger after update.
If I update name column value then I want to get ID of that updated name.
Please give me any suggestion.
Thanks....
you can use :OLD check the below example
CREATE OR REPLACE TRIGGER EX_TRIGGER
AFTER UPDATE ON TAB1
BEGIN
SET VAR = :OLD.ID
END;
Oracle Setup:
CREATE TABLE table_name ( id INT, name VARCHAR2(20) );
CREATE TABLE table_name_log( prev_id INT, id INT, update_date DATE );
CREATE TRIGGER log_id
AFTER UPDATE
ON table_name
FOR EACH ROW
BEGIN
IF :old.name <> :new.name THEN
INSERT INTO table_name_log (
prev_id, id, update_date
) VALUES (
:old.id, :new.id, SYSDATE
);
END IF;
END;
/
INSERT INTO table_name
SELECT 1, 'Alf' FROM DUAL UNION ALL
SELECT 2, 'Ben' FROM DUAL UNION ALL
SELECT 3, 'Carl' FROM DUAL;
Query:
UPDATE table_name SET name = 'Ann' WHERE id = 1;
SELECT * FROM table_name_log;
Output
PREV_ID ID UPDATE_DATE
------- -- -------------------
1 1 2016-06-02 09:56:23

How to create one table with same datatype of all columns from another table?

I have one table and I need to create one dummy table with same column names and data but with different datatypes for some of the columns.
For example: Table-1 has two columns C1 (varchar2) and C2(date).
I need to create a dummy table called Table-2 with columns C1 (varchar2) and C2(varchar2).
Please suggest the way to do it in oracle.
The best way to do this is to duplicate the table with create as select, without the data, for example -
create table Table-2 as select * from Table-1 where 1=0;
And then alter the datatypes of the required columns manually like so -
alter table Table-2 modify (C2 varchar2);
After the column was altered you can push the data from Table-1 into Table-2, using proper conversions. in your example -
insert into Table-2 select C1, to_char(C2,'dd-mm-yyyy') from Table-1;
Assuming that all the columns of the starting table can be converted ( implicit conversion) in VARCHAR2, you can do something like the following.
Say you have this table:
SQL> create table table1 (
2 date_field date,
3 varchar_field varchar2(1000),
4 number_field number
5 );
Table created.
SQL> insert into table1 values (sysdate, 'some text', 999);
1 row created.
SQL> commit;
Commit complete.
You can build a dynamis SQL that creates another table and copies the data from one table to another, using implicit type conversion:
SQL> declare
2 stm varchar2(32767);
3 begin
4 select 'create table table2( ' ||
5 listagg(column_name, ' varchar2(4000), ') within group (order by column_name) ||
6 ' varchar2(4000) )'
7 into stm
8 from user_tab_columns
9 where table_name = 'TABLE1';
10 --
11 execute immediate stm;
12 --
13 select 'insert into table2( ' ||
14 listagg(column_name, ', ') within group (order by column_name) ||
15 ' ) select ' ||
16 listagg(column_name, ', ') within group (order by column_name) ||
17 ' from table1'
18 into stm
19 from user_tab_columns
20 where table_name = 'TABLE1';
21 execute immediate stm;
22 end;
23 /
PL/SQL procedure successfully completed.
SQL> select * from table2;
DATE_FIELD NUMBER_FIELD VARCHAR_FIELD
--------------- --------------- ---------------
27-APR-16 some text 999
SQL>

Resources