Writing Dynamic Insert or Update in Oracle - sql

I am working on an application that will be used to populate the address details of for employees. The address structure will be different for every country.
For the address style mapping I have a table in which I have mapped all the styles.
I have the following requirement:
As shown in the image, I have an address mapping table in which
col1 is for style (wrt country),
col2 for Field_name (The field to be displayed in front end) and
col3 for column_field_name (The name of the column in which the field in col2 will be stored in transaction table.)
![Mapping table Desc][1]
**ADDRESS_STYLE FIELD_NAME COLUMN_FIELD_NAME**
1 US_GLB Address Line1 ADDRESS_LINE1
2 US_GLB Address Line2 ADDRESS_LINE2
3 US_GLB Zip Code POSTAL_CODE
4 US_GLB Tax Zip Code ADD_INFORMATION17
5 US_GLB City TOWN_OR_CITY
6 US_GLB State REGION_2
7 US_GLB Country COUNTRY
8 US_GLB Tax Jurisdiction ADD_INFORMATION15
9 US_GLB Tax Jurisdiction Other ADD_INFORMATION16
10 US_GLB Telephone TELEPHONE_NUMBER_1
11 US_GLB Telephone2 TELEPHONE_NUMBER_2
Now I have to write a procedure or function which will take the all the in parameters
and insert those in my transaction table as they are mapped in mapping table.
For Ex-(As shown above)
Field Address Line1 will be stored in ADDRESS_LINE1 of transaction table.
Field State will be stored in REGION_2 of transaction table.

So in this Proof of Concept I have made two assumptions (maybe three, depends how you count them).
The target ADDRESSES table has an ID column, populated by a sequence.
The column mappings are always in the same order as the columns in the table's projection
and that order is guaranteed by a sort column on the mapping table.
The first is just a guess and easy enough to fit to whatever your actual process is.
The second assumption has major ramifications, because if you haven't been disciplined about how the data is entered into the mapping table this implementation won't work reliably. You'll need to replace the loop with eleven separate lookups for each value of FIELD_NAME. Obviously that would be far too tedious for me to code.
create or replace procedure pop_addr_details
(i_addr_style mapping_table.address_style%type
, i_Address_Line1 in varchar2
, i_Address_Line2 in varchar2
, i_Zip_Code in varchar2
, i_Tax_Zip Code in varchar2
, i_City in varchar2
, i_State in varchar2
, i_Country in varchar2
, i_Tax_Jurisdiction in varchar2
, i_Tax_Jurisdiction Other in varchar2
, i_Telephone in varchar2
, i_Telephone2 in varchar2 )
is
stmt varchar2(32767);
begin
stmt := 'insert into adddresses values (address_id';
for map_rec in ( select column_field_name
from mapping_table
where address_style = i_addr_style
order by col_order )
loop
stmt := stmt || ',' || map_rec.column_field_name;
end loop;
stmt := stmt || ') values ( address_seq.next_val, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11)';
execute immedate stmt using i_Address_Line1
, i_Address_Line2
, i_Zip_Code
, i_Tax_Zip Code
, i_City
, i_State
, i_Country
, i_Tax_Jurisdiction
, i_Tax_Jurisdiction Other
, i_Telephone
, i_Telephone2;
end pop_addr_details;
/
There's a whole chapter on dynamic SQL in the PL/SQL documentation. Find out more.

------------------------------INSERT PROCEDURE-----------------------------
PROCEDURE GHCM_ADDRESS_INSERT_DTLS_PROC(IN_PERSON_ID PLS_INTEGER,
I_ADDR_STYLE VARCHAR2,
IN_ATRRIBUTE1 VARCHAR2,
IN_ATRRIBUTE2 VARCHAR2,
IN_ATRRIBUTE3 VARCHAR2,
IN_ATRRIBUTE4 VARCHAR2,
IN_ATRRIBUTE5 VARCHAR2,
IN_ATRRIBUTE6 VARCHAR2,
IN_ATRRIBUTE7 VARCHAR2,
IN_ATRRIBUTE8 VARCHAR2,
IN_ATRRIBUTE9 VARCHAR2,
IN_ATRRIBUTE10 VARCHAR2,
IN_ATRRIBUTE11 VARCHAR2,
IN_ATRRIBUTE12 VARCHAR2,
IN_ATRRIBUTE13 VARCHAR2,
IN_ATRRIBUTE14 VARCHAR2,
IN_ATRRIBUTE15 VARCHAR2,
IN_ATRRIBUTE16 VARCHAR2,
IN_ATRRIBUTE17 VARCHAR2,
OUT_SUCCESS OUT VARCHAR2)
IS
V_STATEMENT VARCHAR2(1000);
V_SEQ_ID PLS_INTEGER;
BEGIN
V_STATEMENT := 'INSERT INTO PER_ADDRESS_TEST (ADDRESS_ID,STYLE,IN_PERSON_ID';
FOR MAP_REC IN (SELECT COLUMN_FIELD_NAME
FROM GHCM_ADDRESS_STYLE_MAPING_TBL
WHERE STYLE = I_ADDR_STYLE
AND DISPLAY_FLAG = 'Y'
AND ENABLED_FLAG = 'Y'
AND MAPPING_DESC = 'Address Structure'
ORDER BY DISPLAY_ORDER_NO) LOOP
V_STATEMENT := V_STATEMENT || ',' || MAP_REC.COLUMN_FIELD_NAME;
END LOOP;
FOR MAP_REC IN (SELECT DISTINCT (COLUMN_FIELD_NAME)
FROM GHCM_ADDRESS_STYLE_MAPING_TBL
WHERE COLUMN_FIELD_NAME NOT IN
(SELECT COLUMN_FIELD_NAME
FROM GHCM_ADDRESS_STYLE_MAPING_TBL
WHERE STYLE = I_ADDR_STYLE
AND DISPLAY_FLAG = 'Y'
AND ENABLED_FLAG = 'Y'
AND MAPPING_DESC = 'Address Structure')) LOOP
V_STATEMENT := V_STATEMENT || ',' || MAP_REC.COLUMN_FIELD_NAME;
END LOOP;
V_STATEMENT := V_STATEMENT ||
') values ( :1, :2, :3, :4, :5, :6, :7, :8, :9,:10,:11,:12,:13,:14,:15,:16,:17,:18,:19,:20)';
V_SEQ_ID := ADDRESS_SEQ_TEST.NEXTVAL;
EXECUTE IMMEDIATE V_STATEMENT
USING V_SEQ_ID, I_ADDR_STYLE, IN_PERSON_ID, IN_ATRRIBUTE1, IN_ATRRIBUTE2, IN_ATRRIBUTE3, IN_ATRRIBUTE4, IN_ATRRIBUTE5, IN_ATRRIBUTE6, IN_ATRRIBUTE7, IN_ATRRIBUTE8, IN_ATRRIBUTE9, IN_ATRRIBUTE10, IN_ATRRIBUTE11, IN_ATRRIBUTE12, IN_ATRRIBUTE13, IN_ATRRIBUTE14, IN_ATRRIBUTE15, IN_ATRRIBUTE16, IN_ATRRIBUTE17;
COMMIT;
OUT_SUCCESS := 'Y';
END;

Related

Dynamic Tables , Column and values in oracle

I need to provide Table name , Column name and Column values
in an Oracle function dynamically.
I have written a sample code in Oracle function , but it is not dynamic.
Can anyone help to make the below code to be dynamic , so that Table name , Column name and column values can be passed dynamically ?
Select test_emp_name ('Table name' , 'Column name' , 'column_value');
CREATE OR REPLACE FUNCTION test_emp_name (
column_value IN VARCHAR2
) RETURN VARCHAR2 IS
variable_1 VARCHAR2(100);
variable_2 VARCHAR2(100);
variable_3 VARCHAR2(100);
BEGIN
SELECT max(Emp_Name)
INTO variable_1
FROM Employee_Table
where Emp_Name = column_value
variable_2 := SUBSTR( variable_1, 1,10 );
variable_3 := SUBSTR( variable_1, 1,10 );
RETURN
variable_2||variable_3;
EXCEPTION
WHEN no_data_found THEN
RETURN 'nothing found';
END;

ORACLE SQL, If statement to detect datatype of a parameter from a function

I am trying to make a function that has a variable of any data type passed in. I want to be able to do a if statement to see if it is a varchar2 or a number.
create or replace FUNCTION get_manager(pass in sys.ANYDATA )
RETURN varchar2
IS T1 varchar2(300);
I_var varchar2(300);
I_num number;
F_NAME STRING(300);
L_NAME STRING(300);
BEGIN
if pass%type = I_var%type then --Name
F_NAME := REGEXP_SUBSTR(PASS, '(\S*)(\s)'); --First Word
L_NAME := REGEXP_SUBSTR(PASS, '(\S*)(\s)', 1, 2); --Second Word
select FIRST_NAME || ' ' || LAST_NAME || ' ' || PHONE_NUMBER INTO T1 FROM EMPLOYEES WHERE EMPLOYEES.FIRST_NAME = F_NAME AND EMPLOYEES.LAST_NAME = L_NAME ;
elsif pass%type = I_num%type then --ID Number
select FIRST_NAME || ' ' || LAST_NAME || ' ' || PHONE_NUMBER INTO T1 FROM EMPLOYEES WHERE EMPLOYEES.EMPLOYEE_ID = pass;
end if;
RETURN(T1);
END;
I get these compiler errors:
Error(12,5): PL/SQL: Statement ignored
Error(12,13): PLS-00208: identifier 'TYPE' is not a legal cursor attribute
Any ideas as to what I am doing wrong?
As Amitabh said, the getTypeName function is the most important part. But there are lots of tricky parts involved with the ANY* types.
For example, it would be incredibly difficult to find the type of a local variable. Probably the best bet would be to use PL/SCOPE, but that's a lot of work. Since the variable must be hard-coded to assign to it, the below code also hard-codes the local variable type name for the comparison.
In practice you almost never want to use the ANY types. They are great for solving some hard problems. But you don't want to build a framework around them if you can avoid it. It's usually better to generate lots of simple code, or to generate dynamic SQL.
Function
create or replace FUNCTION get_manager(pass in sys.ANYDATA )
RETURN varchar2
IS T1 varchar2(300);
I_var varchar2(300);
I_num number;
F_NAME varchar2(300);
L_NAME varchar2(300);
V_STATUS pls_integer;
BEGIN
if PASS.getTypeName = 'SYS.VARCHAR2' then --Name
V_STATUS := PASS.GetVarchar2(F_NAME);
V_STATUS := PASS.GetVarchar2(L_NAME);
F_NAME := TRIM(REGEXP_SUBSTR(F_NAME, '(\S*)(\s)')); --First Word
L_NAME := TRIM(REGEXP_SUBSTR(L_NAME, '(\s)(\S*)')); --Second Word
select FIRST_NAME || ' ' || LAST_NAME || ' ' || PHONE_NUMBER INTO T1 FROM EMPLOYEES WHERE EMPLOYEES.FIRST_NAME = F_NAME AND EMPLOYEES.LAST_NAME = L_NAME ;
elsif PASS.getTypeName = 'SYS.NUMBER' then --ID Number
V_STATUS := PASS.GetNumber(I_NUM);
select FIRST_NAME || ' ' || LAST_NAME || ' ' || PHONE_NUMBER INTO T1 FROM EMPLOYEES WHERE EMPLOYEES.EMPLOYEE_ID = I_NUM;
end if;
RETURN(T1);
END;
/
Sample Schema
--drop table employees;
create table employees(employee_id number, first_name varchar2(300), last_name varchar2(300), phone_number varchar2(300));
insert into employees values(1, 'JOHN', 'SMITH', '867-5309');
commit;
Results
select get_manager(anydata.ConvertVarchar2('JOHN SMITH')) employee_data from dual;
EMPLOYEE_DATA
-------------
JOHN SMITH 867-5309
select get_manager(anydata.ConvertNumber(1)) employee_data from dual;
EMPLOYEE_DATA
-------------
JOHN SMITH 867-5309
I think you need to use GETTYPENAME function here to get the data type.
https://docs.oracle.com/cd/E18283_01/appdev.112/e16760/t_anydat.htm#i1000030
Thanks,
Amitabh

While I call the procedure, it gives me error: ORA-06553: PLS-306: wrong number or types of arguments in call to phone_info

Here is my procedure code:
CREATE OR REPLACE PROCEDURE phone_info (
numar IN order_detail_data.phone_number%TYPE,
process_s OUT VARCHAR2,
type_e OUT VARCHAR2,
status_s OUT VARCHAR2,
acceptor_r OUT VARCHAR2,
donor_r OUT VARCHAR2,
porting_g OUT VARCHAR2,
Idate_e OUT VARCHAR2,
Fdate_e OUT VARCHAR2,
ancom_d OUT VARCHAR2,
status_f OUT VARCHAR2,
error_r OUT VARCHAR2,
creation_n OUT VARCHAR2)
IS
BEGIN
SELECT od.process_type,
NVL (od.subscription_type_fd, od.process_type),
c."STATUS",
od.recipient_id,
od.donor_id,
oda.porting_id,
NVL (od.initial_date, TO_DATE ('31-12-9999', 'DD-MM-YYYY')),
NVL (od.final_date, TO_DATE ('31-12-9999', 'DD-MM-YYYY')),
oi.REG_PORTING_ID,
s.sub_status,
NVL2 (oj.error_description, oj.error_description, s.sub_status),
oda.sys_creation_date
INTO process_s,
type_e,
status_s,
acceptor_r,
donor_r,
porting_g,
Idate_e,
Fdate_e,
ancom_d,
status_f,
error_r,
creation_n
FROM order_data od
LEFT JOIN order_detail_data oda ON od.porting_id = oda.porting_id
LEFT JOIN order_sub_statuses s ON s.id = oda.sub_status_id
LEFT JOIN order_reject_details oj ON oda.porting_id = oj.porting_id
LEFT JOIN order_id oi ON oda.porting_id = oi.porting_id
LEFT JOIN order_bpm_processes bx ON oda.porting_id = bx.porting_id
LEFT JOIN order_detail_statuses c ON c.id = oda.status_id
WHERE oda.phone_number = numar;
-- Afisare dbms_output.put_line('######### DONE #########');
-- dbms_output.put_line('Process: ' || process_s);
-- dbms_output.put_line('TYPE: ' || type_e);
-- dbms_output.put_line('Status: ' || status_s);
-- dbms_output.put_line('Acceptor: ' || acceptor_r);
-- dbms_output.put_line('Donor: ' || donor_r);
-- dbms_output.put_line('Porting: ' || porting_g);
-- dbms_output.put_line('Idate: ' || Idate_e);
-- dbms_output.put_line('Fdate: ' || Fdate_e);
-- dbms_output.put_line('Ancom_ID: ' || ancom_d);
-- dbms_output.put_line('Status_flow: ' || status_f);
-- dbms_output.put_line('Error: ' || error_r);
-- dbms_output.put_line('Creation: ' || creation_n);
-- dbms_output.put_line('######### FINISHED #########');
END;
you have many outputs in your procedure , you can store those data in a table then retrieve them.
Anyway as for your errors :
are you passing the first parameter with varchar or quotes. it should be numbers.
also I noticed parameter Idate_e is varchar however its seems you are passing date values. try as the below
DECLARE
phonenumb NUMBER;
process_s varchar2,
type_e varchar2,
status_s varchar2,
acceptor_r varchar2,
donor_r varchar2,
porting_g varchar2,
Idate_e date,
Fdate_e date,
ancom_d varchar2,
status_f varchar2,
error_r varchar2,
creation_n varchar2
begin
phone_info (1123,process_s,process_s,type_e,status_s,acceptor_r,donor_r,porting_g,Idate_e,Fdate_e,ancom_d,status_f,error_r,creation_n);
end;
/

Inserting data into a table from multiple tables

How can I insert data into a table from multiple tables where all the tables have a common prefix as their table name
This is the structure of the table and error table generated by the package (DBMS_ERRLOG.CREATE_ERROR_LOG) :
Name Type
----------------------------------------------------------------------------
ACCOUNT_ID VARCHAR2(20)
EFFECTIVE_DTM DATE
ACCOUNT_STATUS VARCHAR2(200)
STATUS_REASON_TXT VARCHAR2(255)
ISVALID NUMBER(1)
Name Type
----------------------------------------------------------------------------
ORA_ERR_NUMBER NUMBER
ORA_ERR_MESG$ VARCHAR2(2000)
ORA_ERR_ROWID$ ROWID
ORA_ERR_OPTYP$ VARCHAR2(2)
ORA_ERR_TAG$ VARCHAR2(2000)
ACCOUNT_ID VARCHAR2(4000)
EFFECTIVE_DTM VARCHAR2(4000)
ACCOUNT_STATUS VARCHAR2(4000)
STATUS_REASON_TXT VARCHAR2(4000)
ISVALID VARCHAR2(4000)
For starting you can select from all your source tables in union/union all queries if you know all the table names. Otherwise you can use the pl/sql block to use dynamic query to do this task for you.
But it would be easier to state if you provide your table structure and few records and some effort done by you as commented by peers in comment section.
Edit
After getting further clarity on the requirement I would suggest to keep all the other columns (basically entries from the base tables) into one single column - we can use CLOB for this purpose.
you can try something like below -
*** considering err log table as -
create table err_log_tab (
ORA_ERR_NUMBER NUMBER,
ORA_ERR_MESG$ VARCHAR2(2000),
ORA_ERR_ROWID$ ROWID,
ORA_ERR_OPTYP$ VARCHAR2(2),
ORA_ERR_TAG$ VARCHAR2(2000),
table_name varchar2(50),
row_data clob)
Below block can be executed to do so -
DECLARE
l_sql VARCHAR2(4000);
l_col_cnt NUMBER := 0;
BEGIN
FOR l_err_tabs IN (SELECT table_name
FROM user_tab_cols
WHERE table_name LIKE 'ERR$_%'
AND column_name IN ('ORA_ERR_NUMBER',
'ORA_ERR_MESG$',
'ORA_ERR_ROWID$',
'ORA_ERR_OPTYP$',
'ORA_ERR_TAG$')
GROUP BY table_name
HAVING COUNT(*) = 5)
LOOP
l_sql := 'insert into err_log_tab select ORA_ERR_NUMBER,ORA_ERR_MESG$,ORA_ERR_ROWID$,ORA_ERR_OPTYP$,ORA_ERR_TAG$,''' ||
substr(l_err_tabs.table_name,
6) || ''' table_name , to_clob(' || chr(10);
FOR l_cols IN (SELECT column_name
FROM user_tab_cols
WHERE table_name = l_err_tabs.table_name
AND column_id > 5
ORDER BY column_id)
LOOP
l_sql := l_sql || CASE
WHEN l_col_cnt = 1 THEN
'||'',''||'
ELSE
''
END || '''"''||' || l_cols.column_name || '||''"''';
l_col_cnt := 1;
END LOOP;
l_sql := l_sql || ') row_data from ' || l_err_tabs.table_name;
--dbms_output.put_line (l_sql);
EXECUTE IMMEDIATE l_sql;
END LOOP;
END;
/
Please correct me if my understanding with question is going in wrong direction.

Inserting row values into another table's column

I'm trying to implement an undo and logging feature for my project.
When a user deletes a row from a table with the DELETE_ROW procedure i select all values from that row and insert it into my row_history table by serializing row values as xml with LOG_DELETED_ROW procedure, then i delete row from its original table.
Serializing with built-in functions of Oracle was easy but i couldn't find a way to deserialize the rowdata and insert it back to own table.
Is there any way to store that deleted row into another table and restore it when needed?
Delete Procedure:
create or replace procedure DELETE_ROW(tableName varchar2, userId varchar2, columnName varchar2, columnValue number) is
begin
log_deleted_row(tableName, userId, columnName, columnValue);
execute immediate 'delete from ' || tableName || ' where ' || columnName || ' = ' || columnValue;
end DELETE_ROW;
Logging Procedure:
create or replace procedure LOG_DELETED_ROW(tableName varchar2, userId varchar2, columnName varchar2, columnValue number) is
tableId number;
begin
SELECT ID into tableId FROM TABLES WHERE NAME = tableName;
execute immediate
'INSERT INTO ROW_HISTORY(TABLE_ID,ROW_ID,ROW_DATA)
SELECT
'|| tableId ||',
'|| columnValue ||',
to_clob(
DBMS_XMLGEN.getxmltype(
''SELECT * FROM ' || tableName || ' where ' || columnName || ' = ' || columnValue || '''
)
)FROM DUAL';
end LOG_DELETED_ROW;
Row History Table:
create table ROW_HISTORY
(
ID NUMBER not null,
TABLE_ID NUMBER not null,
ROW_ID NUMBER not null,
ROW_DATA CLOB not null
)
DBMS_XMLSAVE seems to be the thing you need.Here is a procedure which should do what you need to do.
CREATE OR REPLACE PROCEDURE insert_xml_data(p_table IN VARCHAR2, xml_data IN CLOB) IS
t_context DBMS_XMLSAVE.CTXTYPE;
t_rows NUMBER;
BEGIN
t_context := DBMS_XMLSAVE.NEWCONTEXT(p_table);
t_rows := DBMS_XMLSAVE.INSERTXML(t_context,xml_data);
DBMS_XMLSAVE.CLOSECONTEXT(t_context);
END;
/
I believe you could use DBMS_SQL package here - it will allow you to reconstruct insert statement knowing table name and columns.
Another, more complicated, way would be to insantiate LCR$_ROW_RECORD object and then run its EXECUTE member - it will perform actual insert.