how to create record type using dynamic sql in oracle? - oracle

I have a table named a with column x.
create table A (x varchar2(4000));
insert into A values ('select p,q,r from o');
commit;
Now I want to create a record type dynamically on the basis of above sql i.e.
TYPE rc IS RECORD ( p o.p%type,
q o.q%type,
r o.r%type);
Please let me know how to create above record on runtime.
If its not possible, please suggest any work around.
Thank you.

if you just want to use a record of this table in your code you don't need to create a type, you can declare a rowtype variable as bellow
CREATE TABLE xx_emp (emp_id NUMBER, emp_name VARCHAR2(100));
DECLARE
l_emp_type xx_emp%ROWTYPE;
BEGIN
l_emp_type.emp_id := 10;
l_emp_type.emp_name := 'JOE';
END;

As you commented on a previous answer, I will assume that with "dinamically" you want to create it based on a query that may contain joins,
you can use cursor as bellow
DECLARE
-- here you declare a cursor named c_cursor_name
CURSOR c_cursor_name IS SELECT a.emp_name
, b.dept_name
FROM xx_emp a
, xx_Dept b
WHERE a.dept_id = b.dept_id;
-- here you can declare your record based on your cursor
r_recors_type c_cursor_name%ROWTYPE;
BEGIN
-- now you can fill your cursor as you want
r_recors_type.emp_name := 'JOE';
r_recors_type.dept_name := 'Marketing';
END;

Related

Adding a prefix N for a parameter in Oracle stored procedure

How can I add a prefix N for a parameter in stored procedure?
For example: in an insert/update/select statement we can do it like
insert into table xxx values (N'value')
Alter table set value = N'value1'
etc..
I want to add prefix to resolve the inverted question mark(¿) issue in the Oracle table column of type nvarchar2 for some characters like - (hyphen), TM (trade mark symbol) etc..
If I'm understanding what you're saying, you would just concatenate the string onto the value:
insert into table xxx values ('N'||value_1, value_2, value_3);
If your prefix isn't a fixed string, you could use a parameter:
prefix_1 := 'N';
insert into table xxx values (prefix_1||value_1, value_2, value_3);
Please try this, we can declare a local variable inside procedure to achieve it.
Not sure about how and if possible to include it in the IN parameter.
CREATE TABLE so_nvarchar_test (col1 NVARCHAR2(100)) ;
CREATE OR REPLACE PROCEDURE validate_nvarchar2(p_parameter NVARCHAR2)
IS
validated_param NVARCHAR2(100) := N''||p_parameter||'';
BEGIN
INSERT INTO so_nvarchar_test(col1) VALUES (validated_param);
END;
/
DECLARE
p_parameter NVARCHAR2(100) := 'Länsförsäkringar?';
BEGIN
INSERT INTO so_nvarchar_test(col1) VALUES (p_parameter);
END;
/

Create insert record dynamically by changing pk of existing record for passed in table

I want to pass a table name and schema into a procedure, and have it generate insert, update and delete statements for the particular table. This is part of an automated testing solution (in a development environment) in which I need to test some change data capture. I want to make this dynamic as it is going to be need to be done for lots of different tables over a long period of time, and I need to call it via a REST request through ORDS, so don't want to have to make an endpoint for every table.
Update and delete are fairly easy, however I am struggling with the insert statement. Some of the tables being passed in have hundreds of columns with various constraints, fks etc. so I think it makes sense to just manipulate an existing record by changing only the primary key. I need to be able to modify the primary key to a new value known to me beforehand (e.g. '-1').
Ideally I would create a dynamic rowtype, and select into where rownum = 1, then loop round the primary keys found from all_constraints, and update the rowtype.pk with my new value, before inserting this into the table. Essentially the same as this but without knowing the table in advance.
e.g. rough idea
PROCEDURE manipulate_records(p_owner in varchar2, p_table in varchar2)
IS
cursor c_pk is
select column_name
from all_cons_columns
where owner = p_owner
and constraint_name in (select constraint_name
from all_constraints
where table_name = p_table
and constraint_type = 'P');
l_row tbl_passed_in%ROWTYPE --(I know this isn't possible but ideally)
BEGIN
-- dynamic sql or refcursor to collect a record
select * into tbl_passed_in from tablename where rownum = 1;
-- now loop through pks and reassign their values to my known value
for i in c_pk loop
...if matches then reassign;
...
end loop;
-- now insert the record into the table passed in
END manipulate_records;
I have searched around but haven't found any examples which fit this exact use case, where an unknown column needs to be modified and insert into a table.
Depending on how complex your procedure is, you might be able to store it as a template in a CLOB. Then pull it in, replace table and owner, then compile it.
DECLARE
prc_Template VARCHAR2(4000);
vc_Owner VARCHAR2(0008);
vc_Table VARCHAR2(0008);
BEGIN
vc_Table := 'DUAL';
vc_Owner := 'SYS';
-- Pull code into prc_Template from CLOB, but this demonstrates the concept
prc_Template := 'CREATE OR REPLACE PROCEDURE xyz AS r_Dual <Owner>.<Table>%ROWTYPE; BEGIN NULL; END;';
prc_Template := REPLACE(prc_Template,'<Owner>',vc_Owner);
prc_Template := REPLACE(prc_Template,'<Table>',vc_Table);
-- Create the procedure
EXECUTE IMMEDIATE prc_Template;
END;
Then you have the appropriate ROWTYPE available:
CREATE OR REPLACE PROCEDURE xyz AS r_Dual SYS.DUAL%ROWTYPE; BEGIN NULL; END;
But you can't create the procedure and run it in the same code block.

Is there an easy to to iterate over all :NEW values from an Oracle database trigger execution?

I am attempting to write a generic trigger that will provide all of the :NEW values for the row inserted. Ultimately I want to turn them into XML and insert the XML string into a binary field on another table.
There are a variable number of columns in each table - many times over 100 fields and over 100 tables in all, so individual mapping to XML per table is extremely time consuming.
Is there a way to reference the :NEW pseudorecord as a collection of column values - or perhaps a way to pass the whole :NEW record to a Stored Procedure that could pass it to a Java function (hosted on the database) that might make the individual values iterable?
I've found an example here:
https://docs.oracle.com/database/121/LNPLS/triggers.htm
Create history table and trigger:
CREATE TABLE tbl_history ( d DATE, old_obj t, new_obj t)
/
CREATE OR REPLACE TRIGGER Tbl_Trg
AFTER UPDATE ON tbl
FOR EACH ROW
BEGIN
INSERT INTO tbl_history (d, old_obj, new_obj)
VALUES (SYSDATE, :OLD.OBJECT_VALUE, :NEW.OBJECT_VALUE);
END Tbl_Trg;
/
This seems to imply there is some sort of way it is storing all of the values as a variable, but this appears to put them directly back into a database table. I want to get the 'text' values of the column values listed.
You can create a stored procedure to create your trigger
for table tbl like
create table tbl (id number, value varchar2(10));
and an history table like
create table tbl_history (d date,id number, value varchar2(10));
you can create your trigger like this
create or replace procedure CREATE_TRIGGER IS
trig_str VARCHAR2(32767);
col_str VARCHAR2(32767) := '(d';
values_str VARCHAR2(32767) := '(sysdate';
begin
trig_str := 'CREATE OR REPLACE TRIGGER Tbl_Trg AFTER UPDATE ON tbl FOR EACH ROW'||chr(10)||
'BEGIN'||chr(10)||chr(9)||'INSERT INTO tbl_history ';
for col in (
SELECT column_name FROM all_tab_columns where table_name = 'TBL'
) loop
col_str := col_str||','||col.column_name;
values_str := values_str||','||':OLD.'||col.column_name;
end loop;
col_str := substr(col_str,1,length(col_str)-1)||')';
values_str := substr(values_str,1,length(values_str)-1)||')';
trig_str := trig_str||col_str||' VALUES '||values_str||';'||chr(10)||'END;';
execute immediate trig_str;
END;
/
With an history table with old and new values it's a bit more complicated but same idea

How to use %ROWTYPE when inserting into Oracle table with identity column?

I have an Oracle 12c database with a table containing an identity column:
CREATE TABLE foo (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
bar NUMBER
)
Now I want to insert into the table using PL/SQL. Since in practice the table has many columns, I use %ROWTYPE:
DECLARE
x foo%ROWTYPE;
BEGIN
x.bar := 3;
INSERT INTO foo VALUES x;
END;
However, it give me this error:
ORA-32795: cannot insert into a generated always identity column
ORA-06512: at line 5
Since it is very good for code readability and maintainability, I do not want to stop using %ROWTYPE. Since I under no circumstances want to allow anything but the automatically generated ID's I do not want to lift the GENERATED ALWAYS restriction.
This article suggests that the only way to be able to use %ROWTYPE is to switch to GENERATED BY DEFAULT ON NULL. Is there no other way to fix this?
The only thing I can think of, since you're on 12c is to make the identity column INVISIBLE, like the code below.
The problem is that it makes getting a %ROWTYPE with the id a little more difficult, but it's doable.
Of course, it may also confuse other people using your table to not see a primary key!
I don't think I'd do this, but it is an answer to your question, for what that's worth.
DROP TABLE t;
CREATE TABLE t ( id number invisible generated always as identity,
val varchar2(30));
insert into t (val) values ('A');
DECLARE
record_without_id t%rowtype;
CURSOR c_with_id IS SELECT t.id, t.* FROM t;
record_with_id c_with_id%rowtype;
BEGIN
record_without_id.val := 'C';
INSERT INTO t VALUES record_without_id;
-- If you want ID, you must select it explicitly
SELECT id, t.* INTO record_with_id FROM t WHERE rownum = 1;
DBMS_OUTPUT.PUT_LINE(record_with_id.id || ', ' || record_with_id.val);
END;
/
SELECT id, val FROM t;
You can create a view and insert there:
CREATE OR REPLACE VIEW V_FOO AS
SELECT BAR -- all columns apart from virtual columns
FROM foo;
DECLARE
x V_FOO%ROWTYPE;
BEGIN
x.bar := 3;
INSERT INTO V_FOO VALUES x;
END;
I think that a mix of former answers - using view with invisible identity column - is a optimal way to accomplish this task:
create table foo (id number generated always as identity primary key, memo varchar2 (32))
;
create or replace view fooview (id invisible, memo) as select * from foo
;
<<my>> declare
r fooview%rowtype;
id number;
begin
r.memo := 'first row';
insert into fooview values r
returning id into my.id
;
dbms_output.put_line ('inserted '||sql%rowcount||' row(s) id='||id);
end;
/
inserted 1 row(s) id=1

How to use a nested table in a cursor

CREATE PROCEDURE( p_cur OUT a_cur)
IS
type rec is record( a varchar2(2), b number, c number);
type tab is table of rec;
tab1 tab:=tab();
begin
tab1.extend;
tab1(tab1.last).a:='as';
tab1(tab1.last).b:=2;
tab1(tab1.last).c:=3;
tab1.extend;
tab1(tab1.last).a:='jj';
tab1(tab1.last).b:=2;
tab1(tab1.last).c:=3;
--??---
end;
I have created a nested table here tab1 ,but my issue is that i want to use this nested table in a cursor and want to return whole records using this nested table ,limitation is that i dont want to use any temporary table .
I am using RDBMS as ORACLE
If you want to use a collection as if it were a table then you'll nedd to look a the TABLE() function:
There is an example here: http://www.dobosz.at/oracle/select-from-plsql-table/
And another good resource here: http://www.databasejournal.com/features/oracle/article.php/2222781/Returning-Rows-Through-a-Table-Function-in-Oracle.htm
You'll need to declare the collection type in the database before then populating it in your procedure and then selecting from it.
I answered a question using this method here: Can a table variable be used in a select statement where clause?
Take a look as it should help you with what you are trying to achieve.
Hope it helps...
EDIT: In response to your question this code should do what you want it to. I haven't tested it but it should be very close to what you need and you can debug it if needed.
-- Create the relevent Object
CREATE TYPE data_obj_type AS OBJECT (
a VARCHAR2(2),
b NUMBER,
c NUMBER
);
Type Created
-- Create the collection to hold the objects
CREATE TYPE table_obj_type IS TABLE OF data_obj_type;
Type Created
CREATE OR REPLACE
PROCEDURE cursor_values(
p_cur OUT sys_refcursor
)
IS
-- Create a variable and initialise it
tab1 table_obj_type := table_obj_type();
BEGIN
-- Populate the tab1 collection
tab1.extend;
tab1(tab1.last) := data_obj_type('as', 2, 3);
tab1.extend;
tab1(tab1.last) := data_obj_type('jj', 2, 3);
--
-- Open ref_cursor for output
OPEN p_cur FOR
SELECT a,
b,
c
FROM TABLE(CAST(tab1 AS table_obj_type));
END cursor_values;
N.B.: This is code amended from this page:
http://www.akadia.com/services/ora_return_result_set.html

Resources