Update column value after some validation on it - sql

I have a scenario where i need to update the value of a column or say append !
Below is test table and data
create table test (rsrc_nm varchar2(50), parm varchar2(400));
insert into test values ('HLRCamelProfileBasic','resource_code:7|resource_type:HLR_Camel_Profile|Priority:1|');
insert into test values ('HSSUSERProfileBasic','resource_code:3|resource_type:HSS_User_Profile|Priority:1|');
insert into test values ('HSSUSERProfileBasic','resource_code:3|resource_type:HSS_User_Profile|Priority:2|');
insert into test values ('HSSUSERProfileBasic','resource_code:3|resource_type:HSS_User_Profile|Priority:1|');
insert into test values ('HLRBaseProfileBasic','resource_code:1|resource_type:HLR_Base_Profile|Priority:2|');
insert into test values ('HLRBaseProfileBasic','resource_code:1|resource_type:HLR_Base_Profile|Priority:3|');
Here we have 2 columns of a staging table in which rsrc_nm and parm are given what i have to do is i need to update or append the rsrc_nm column for parm coulmn where the last value i.e. priority is changing from 1 to 2 for same rsrc_nm.
For example
rsrc_ nm -- HSSUSERProfileBasic parm -- resource_code:3|resource_type:HSS_User_Profile|Priority:1|
rsrc_ nm -- HSSUSERProfileBasic parm -- resource_code:3|resource_type:HSS_User_Profile|Priority:2|
Here we have same parm priority but the values are different and i need to insert these values into another table for which i need to separate all PIPE values so while i insert it in the table it gives me error for unique constraint because that table table have 3 columns
RSRC_NM PARM VAL
------------------------------- -------------- ----------------
HSSUSERProfileBasic Priority 1
HLRCamelProfileBasic Priority 1
HLRBaseProfileBasic Priority 2
And on this table we have primary key on first two columns which enforce unique constraint also so i can not insert " HLRCamelProfileBasic " rsrc_nm for parm " Priority " because it fails in uniqueness.
So i find a solution to overcome this if i can append or update the rsrc_nm HLRCamelProfileBasic to " HLRCamelProfileBasic_1 " for " Priority:1 " and HLRCamelProfileBasic_2 for " Priority:2 " and so on for all RSRC_NM in Staging table.
My solution --
declare
cursor c1 is select * from RSRC_SVC_MAPPING where parm_entry like '%Priorit%';
parm_entry_length number;
l_value varchar2(100);
parm_name varchar2(100);
parm_val varchar2(100);
l_c1 c1%rowtype;
l_cnt number := 0;
l_rsrc_nm varchar2(100);
begin
open c1;
loop
fetch c1 into l_c1;
parm_entry_length := length(l_c1.Parm_Entry) - length(replace(l_c1.Parm_Entry,'|',''));
for i in 1 .. parm_entry_length loop
select regexp_substr(l_c1.Parm_Entry,'[^|]+',1,i) into l_value from dual;
select regexp_substr (l_value, '[^:]+', 1, 1) into parm_name from dual;
select regexp_substr (l_value, '[^:]+', 1, 2) into parm_val from dual;
-- dbms_output.put_line(l_value||' '||parm_name||' '||parm_val||' '||l_c1.resourcename);
for r in ( select count(*) cnt, resourcename
-- into l_cnt , l_rsrc_nm
from (select count(*), resourcename, parm_entry from RSRC_SVC_MAPPING where parm_entry like '%Priorit%' group by resourcename, parm_entry) group by resourcename)
loop
if r.cnt > 1
then
dbms_output.put_line(l_value||' '||parm_name||' '||parm_val||' '||l_c1.resourcename);
update RSRC_SVC_MAPPING
set resourcename = r.resourcename||'_'||l_cnt
where resourcename = r.resourcename;
l_cnt := l_cnt +1;
end if;
end loop;
end loop;
exit when c1%notfound;
end loop;
exception
when others then
dbms_output.put_line('ERROR OCCURED '||SQLCODE||' '||sqlerrm);
dbms_output.put_line(dbms_utility.format_error_backtrace());
end;
Data which needs to be like after UPDATE is like
HSSUSERProfileBasic_1 resource_code:3|resource_type:HSS_User_Profile|Priority:1|
HSSUSERProfileBasic_2 resource_code:3|resource_type:HSS_User_Profile|Priority:2|
HLRBaseProfileBasic_2 resource_code:1|resource_type:HLR_Base_Profile|Priority:2|
HLRBaseProfileBasic_3 resource_code:1|resource_type:HLR_Base_Profile|Priority:3|
Here we have changed the value rsrc_nm column and append it with the value of priority i.e. 1 and 2
target table is like
create table target (rsrc_nm varchar2(100), parm varchar2(50), val varchar2(50) constraint tgt_pk primary key (rsrrc_nm, parm));
MY DB is -- Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

Related

Copy data for another table using Collections Types index-by table (associated with a table)

I don't know how to create a function with collections type. I am familiar with SQL, but I do not know that particular function. Here is what I tried:
Create or Replace Function COPY_EMPLOYEES_WITH_RT(
Begin
insert into jjj_employees ( select * from employees)
I want to create a function COPY EMPLOYEES_WITH_RT and copy data from the table EMPLOYEES to jjj_EMPLOYEES using Collections Types index-by table (associated with a table).
CREATE OR REPLACE FUNCTION COPY_EMPLOYEES_WITH_RT(O_ERROR_MESSAGE OUT VARCHAR)
--DECLARE type
TYPE EMP_RECORD IS TABLE OF employees%ROWTYPE;
l_emp EMP_RECORD;
-- define cursor
CURSOR c_employees IS
SELECT *
FROM employees;
--
BEGIN
--
open c_employees;
loop
fetch c_employees
bulk collect into l_emp limit 1000;
exit when l_emp.count = 0;
-- Process contents of collection here.
Insert into jrf_employees values l_emp;
END LOOP;
CLOSE c_employees;
EXCEPTION
--
WHEN OTHERS THEN
O_error_message := SQLERRM;
END;
/
it's like something like that, i just didn't know, what is wrong
If you are using object-relational tables and particularly want to use PL/SQL associative arrays (index-by table types) then you can create your tables as:
CREATE TYPE employee_t IS OBJECT(
id NUMBER(10,0),
first_name VARCHAR2(20),
last_name VARCHAR2(20)
);
CREATE TABLE employees OF employee_t (
id PRIMARY KEY
);
CREATE TABLE jjj_employees OF employee_t (
id PRIMARY KEY
);
With the sample data:
INSERT INTO employees
SELECT 1, 'One', 'Uno' FROM DUAL UNION ALL
SELECT 2, 'Two', 'Dos' FROM DUAL UNION ALL
SELECT 3, 'Three', 'Tres' FROM DUAL;
And the function as:
CREATE FUNCTION COPY_EMPLOYEES_WITH_RT
RETURN NUMBER
IS
TYPE employee_a IS TABLE OF employee_t INDEX BY PLS_INTEGER;
emps employee_a;
i PLS_INTEGER;
BEGIN
FOR r IN ( SELECT VALUE(e) AS employee FROM employees e )
LOOP
emps( r.employee.id ) := r.employee;
END LOOP;
i := emps.FIRST;
WHILE i IS NOT NULL LOOP
INSERT INTO jjj_employees VALUES ( emps(i) );
i := emps.NEXT(i);
END LOOP;
RETURN 1;
END;
/
Then you can run the function using:
BEGIN
DBMS_OUTPUT.PUT_LINE( COPY_EMPLOYEES_WITH_RT() );
END;
/
And the table is copied as:
SELECT * FROM jjj_employees;
Outputs:
ID | FIRST_NAME | LAST_NAME
-: | :--------- | :--------
1 | One | Uno
2 | Two | Dos
3 | Three | Tres
db<>fiddle here
Update
Since you appear to want to use a collection and not an associative array:
CREATE OR REPLACE FUNCTION COPY_EMPLOYEES_WITH_RT
RETURN VARCHAR2
IS
--DECLARE type
TYPE EMP_RECORD IS TABLE OF employees%ROWTYPE;
l_emp EMP_RECORD;
-- DECLARE cursor
CURSOR c_employees IS
SELECT *
FROM employees;
BEGIN
OPEN c_employees;
LOOP
FETCH c_employees
BULK COLLECT INTO l_emp LIMIT 1000;
EXIT WHEN l_emp.COUNT = 0;
-- Process contents of collection here.
FORALL i IN 1 .. l_emp.COUNT
INSERT INTO jjj_employees VALUES l_emp(i);
END LOOP;
CLOSE c_employees;
RETURN NULL;
EXCEPTION
WHEN OTHERS THEN
RETURN SQLERRM;
END;
/
db<>fiddle

Is there any possibility of creating view or table based on the output of a dynamically opened ref cursor?

I have learnt how to return a dynamically opened ref cursor. Now based on the output, I want to create a table or a view.
Input table:
create table sales
(s_sale_name varchar2(20),
s_date_sal date,
s_qty number(10)
);
Records
insert into sales values ('Norb','10-MAR-2019',10000);
insert into sales values ('Bert','10-MAR-2019',5000);
insert into sales values ('Alba','10-MAR-2019',4000);
insert into sales values ('Rob','10-MAR-2019',200000);
insert into sales values ('Norb','11-MAR-2019',5000);
insert into sales values ('Bert','11-MAR-2019',13000);
insert into sales values ('Rob','11-MAR-2019',80000);
insert into sales values ('Norb','12-MAR-2019',1000);
insert into sales values ('Bert','12-MAR-2019',4000);
insert into sales values ('Rob','12-MAR-2019',40000);
insert into sales values ('Alba','12-MAR-2019',2000);
Query output
sales_name 10-MAR-2019 11-MAR-2019 12-MAR-2019
Norb 10000 5000 1000
Bert 5000 13000 4000
Alba 4000 0 2000
Rob 200000 80000 40000
Now the result should be saved in a table or a view. I have learnt how to return a dynamically opened ref cursor so far.
------Here is the procedure I used -----------
create or replace package p_sales_pkg
as
type rc is ref cursor;
procedure get_query( p_cursor in out rc, p_start date, p_end date );
end;
/
create or replace package body p_sales_pkg
as
procedure get_query( p_cursor in out rc, p_start date, p_end date )
is
l_query long := 'select s_name ';
begin
for i in 1 .. trunc(p_end)-trunc(p_start)+1
loop
l_query := l_query || ', sum( decode( trunc(s_date), ' ||
'to_date( ''' || to_char(p_start+i-1,'yyyymmdd') ||
''', ''yyyymmdd'' ), s_qty, 0 )) "' ||
to_char(p_start+i-1) || '"';
end loop;
l_query := l_query || ' from sales group by s_name';
open p_cursor for l_query;
end;
end;
/
set autoprint on
var x refcursor
exec nw_demo_pkg.get_query( :x, '10-MAR-19', '13-MAR-19' );
This is really a very nice and challenging question. I disagree on the #APC point on SELECT part of a CREATE TABLE ... AS SELECT statement. Well we definitely can't do that. What i believe is to every problem in Oracle, there exists a solution.
You requirement can be achieved using a NESTED TABLE. See below:
Set Up:
create table sales
(s_sale_name varchar2(20),
s_date_sal date,
s_qty number(10)
);
/
insert into sales values ('Norb','10-MAR-2019',10000);
insert into sales values ('Bert','10-MAR-2019',5000);
insert into sales values ('Alba','10-MAR-2019',4000);
insert into sales values ('Rob','10-MAR-2019',200000);
insert into sales values ('Norb','11-MAR-2019',5000);
insert into sales values ('Bert','11-MAR-2019',13000);
insert into sales values ('Rob','11-MAR-2019',80000);
insert into sales values ('Norb','12-MAR-2019',1000);
insert into sales values ('Bert','12-MAR-2019',4000);
insert into sales values ('Rob','12-MAR-2019',40000);
insert into sales values ('Alba','12-MAR-2019',2000);
---Created an Object of Sales table to hold intermediate result
create or replace type sales_obj is OBJECT
(obj_sale_name varchar2(20),
obj_date_sal date,
obj_qty number(10)
);
/
-- Table of Sales Object.
create or replace type vtest1Tab is table of sales_obj;
/
Anonymous Block to Create table ccc:
DECLARE
VAR VTEST1TAB ;
vsql varchar2(500);
BEGIN
vsql := 'create table ccc(col1) NESTED TABLE COL1 STORE AS TAB1
as
Select cast(multiset(Select * from SALES) as VTEST1TAB )
from dual
';
Execute immediate vsql ;
END;
Output:
SQL> Select p.*
from ccc c,
table(c.COL1) p ;
In this link , The reply by "Zlatko Sirotic" covers exactly how to identify columns of the cursor and print them.
Look for "dyn_fetch", as the package is generic enough, it can work with any query for printing data. You can use the same approach to insert the data into a table that is created dynamically.

Create sequence with sql result in it

I have a sequence like this
begin
if :new."ID" is null then
select to_number(sys_guid(),'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') into :new.id from dual;
end if;
is there a way to set the ID to be the next upcoming ID in the table?
For example:
if my current last row ID is 5
I want the new.id to be 6, so when the INSERT executes then it would have ID of 6
IDENTITY
column is now available on Oracle 12c:
create table t1 (
id NUMBER GENERATED ALWAYS as IDENTITY(START with 1 INCREMENT by 1),
info VARCHAR2(10)
);

Create procedure for inserting the records, if exception, procedure needs start from exception line

I need to write the procedure for inserting the records in to multiple tables, for example I have 3 table,
CREATE TABLE SOURCE
(
SORT_CODE NUMBER,
FLAG CHAR(1)
);
INSERT INTO SOURCE VALUES(605096,5);
INSERT INTO SOURCE VALUES(605097,5);
INSERT INTO SOURCE VALUES(605098,5);
INSERT INTO SOURCE VALUES(605099,5);
INSERT INTO SOURCE VALUES(605100,5);
INSERT INTO SOURCE VALUES(605101,6);
INSERT INTO SOURCE VALUES(605102,6);
INSERT INTO SOURCE VALUES(605103,6);
INSERT INTO SOURCE VALUES(605104,6);
INSERT INTO SOURCE VALUES(605105,6);
SQL> SELECT * FROM SOURCE;
SORT_CODE F
---------- -
605096 5
605097 5
605098 5
605099 5
605100 5
605101 6
605102 6
605103 6
605104 6
605105 6
10 rows selected.
CREATE TABLE TARGET
(
SORT_CODE NUMBER,
TARGET_SORT_CODE NUMBER
);
Table created.
INSERT 5 VALUES
INSERT INTO TARGET VALUES(605101,189873);
INSERT INTO TARGET VALUES(605102,189874);
INSERT INTO TARGET VALUES(605103,189875);
INSERT INTO TARGET VALUES(605104,189876);
INSERT INTO TARGET VALUES(605105,'');
SELECT * FROM TARGET;
SORT_CODE TARGET_SORT_CODE
---------- ----------------
605101 189873
605102 189874
605103 189875
605104 189876
605105
CREATE TABLE NEWID
(
SORT_CODE NUMBER,
ID_SCODE NUMBER
);
Table created.
INSERT 2 VALUES
INSERT INTO TARGET VALUES(605103,189875);
INSERT INTO TARGET VALUES(605104,189876);
SELECT * FROM NEWID;
SORT_CODE ID_SCODE
---------- ----------------
605103 189875
605104 189876
Creating intermediate tables with existing table's structure.
CREATE TABLE SOURCE_TEMP AS (SELECT * FROM SOURCE WHERE 1=2);
CREATE TABLE TARGET_TEMP AS (SELECT * FROM TARGET WHERE 1=2);
CREATE TABLE NEWID_TEMP AS (SELECT * FROM NEWID WHERE 1=2);
My Procedure for inserting the records
CREATE OR REPLACE PROCEDURE insert_sql
is
BEGIN
DELETE FROM SOURCE_TEMP;
INSERT INTO SOURCE_TEMP SELECT * FROM SOURCE; --insert query 1
DELETE FROM TARGET_TEMP;
INSERT INTO TARGET_TEMP SELECT * FROM TARGET; --insert query 2
--due to some network issue or table error this procedure GOT EXEPCTION here and above insert query 2(TARGET_TEMP) and below --insert query 3(NEWID_TEMP) is not inserted the values or not executed procedure is came out from this line.
DELETE FROM NEWID_TEMP;
INSERT INTO NEWID_TEMP SELECT * FROM NEWID; --insert query 3
EXCEPTION
WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('ERROR');
END;
Point 1: The above procedure is executed only one insert query 1 SOURCE_TEMP is got the values.
Point 1: TARGET_TEMP and NEWID_TEMP is not inserted the values or not execute.
My question: can I re-execute this procedure with starting point of '--insert query 2' line?
because I am inserting the 100 tables records in new tables, if 50 tables are inserted the values during this time if I am getting any error in the proc execution, remaining 50 tables needs to insert the values, for I don't wish to delete the previous 50 tables inserted the values it will be the time consuming activity. Any save point or boolean concepts is there for this type of issue in ORACLE (which is available in java and unix). if yes how to use this function?
CREATE OR REPLACE PROCEDURE insert_sql
is
v_new_rec_count int:=0;
BEGIN
select count(*)
into v_new_rec_count
from (
select * FROM SOURCE
minus
select * FROM SOURCE_TEMP
) ;
If v_new_rec_count >0 then
INSERT INTO SOURCE_TEMP SELECT * FROM SOURCE; --insert query 1
Commit;--permanently save the records in table and it wont be rolledback after - -- any subsequent failure.
v_new_rec_count :=0;
end if;
select count(*)
into v_new_rec_count
from (
select * FROM TARGET
minus
select * FROM TARGET_TEMP
) ;
If v_new_rec_count >0 then
INSERT INTO TARGET_TEMP SELECT * FROM TARGET; --insert query 2
Commit;--permanently save the records in table and it wont be rolledback after - -- any subsequent failure.
v_new_rec_count :=0;
end if;
select count(*)
into v_new_rec_count
from (
select * FROM NEWID
minus
select * FROM NEWID_TEMP
) ;
If v_new_rec_count >0 then
INSERT INTO NEWID_TEMP SELECT * FROM NEWID; --insert query 2
Commit;--permanently save the records in table and it wont be rolledback after - -- any subsequent failure.
v_new_rec_count :=0;
end if;
EXCEPTION
WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('ERROR');
END;
CREATE OR REPLACE PROCEDURE insert_sql
IS
x int;
FLG FLAG.FLAG%type;
BEGIN
dbms_output.enable;
SELECT FLAG INTO FLG FROM FLAG;
if FLG='PASS01' then
DBMS_OUTPUT.PUT_LINE('PASS01');
DELETE FROM SOURCE_TEMP;
DBMS_OUTPUT.PUT_LINE('SOURCE_TEMP-DELETED');
INSERT INTO SOURCE_TEMP SELECT * FROM SOURCE;
DBMS_OUTPUT.PUT_LINE('SOURCE_TEMP-INSERTED');
UPDATE FLAG SET FLAG='PASS02';
DBMS_OUTPUT.PUT_LINE('PASS02-updated');
SELECT FLAG INTO FLG FROM FLAG;
DBMS_OUTPUT.PUT_LINE('PASS02-SELECT');
COMMIT;
end if;
if FLG='PASS02' then
DBMS_OUTPUT.PUT_LINE('PASS02');
DELETE FROM TARGET_TEMP;
DBMS_OUTPUT.PUT_LINE('TARGET_TEMP-DELETED');
INSERT INTO TARGET_TEMP SELECT * FROM TARGET;
DBMS_OUTPUT.PUT_LINE('TARGET_TEMP-INSERTEDD');
UPDATE FLAG SET FLAG='PASS03';
DBMS_OUTPUT.PUT_LINE('PASS03-updated');
SELECT FLAG INTO FLG FROM FLAG;
DBMS_OUTPUT.PUT_LINE('PASS03-FLG');
COMMIT;
end if;
--x :=1/0;
if FLG='PASS03' then
DBMS_OUTPUT.PUT_LINE('PASS03');
DELETE FROM NEWID_TEMP;
DBMS_OUTPUT.PUT_LINE('NEWID_TEMP-DELETED');
INSERT INTO NEWID_TEMP SELECT * FROM NEWID;
DBMS_OUTPUT.PUT_LINE('NEWID_TEMP-INSERTEDD');
UPDATE FLAG SET FLAG='PASS01';
DBMS_OUTPUT.PUT_LINE('PASS01-updated');
SELECT FLAG INTO FLG FROM FLAG;
DBMS_OUTPUT.PUT_LINE('PASS01-FLG');
COMMIT;
end if;
EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('NO_DATA_FOUND!');
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Error Message: !'|| SQLERRM ||' Sql Code: ' || SQLCODE);
END;
CREATE OR REPLACE PROCEDURE insert_sql
is
v_new_rec_count int:=0;
CURSOR cur_src
IS
SELECT * FROM SOURCE ;
TYPE cur_src_typ IS TABLE OF cur_src%ROWTYPE;
cur_src_tbl cur_src_typ;
CURSOR cur_newid
IS
SELECT * FROM newid ;
TYPE cur_newid_typ IS TABLE OF cur_src%ROWTYPE;
cur_newid_tbl cur_newid_typ;
CURSOR cur_target
IS
SELECT * FROM target ;
TYPE cur_target_typ IS TABLE OF cur_src%ROWTYPE;
cur_target_tbl cur_target_typ;
BEGIN
cur_src_tbl :=cur_src_tbl();
cur_newid_tbl :=cur_newid_tbl();
cur_target_tbl :=cur_target_tbl();
OPEN cur_src;
LOOP
FETCH cur_src BULK COLLECT INTO cur_src_tbl LIMIT 500;
EXIT WHEN cur_src_tbl.count =0;
FOR i IN cur_src LOOP
BEGIN
INSERT INTO SOURCE_TEMP VALUES(cur_src(i).sort_code,cur_src(i).flag);
COMMIT;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
CLOSE cur_src;
OPEN cur_target;
LOOP
FETCH cur_target BULK COLLECT INTO cur_target_tbl LIMIT 500;
EXIT WHEN cur_target.count =0;
FOR i IN cur_target_tbl LOOP
BEGIN
INSERT INTO target_temp VALUES(cur_target_tbl(i).sort_code,cur_target_tbl(i).target_sort_code);
COMMIT;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
CLOSE cur_newid;
OPEN cur_newid;
LOOP
FETCH cur_newid BULK COLLECT INTO cur_newid_tbl LIMIT 500;
EXIT WHEN cur_newid_tbl.count =0;
FOR i IN cur_newid_tbl LOOP
BEGIN
INSERT INTO newid_TEMP VALUES(cur_newid_tbl(i).sort_code,cur_newid_tbl(i).flag);
COMMIT;
EXCEPTION
WHEN OTHERS THEN NULL;
END;
CLOSE cur_newid;
END;
Thanks to All ur support
I created the Proc, Finally I got PLSQL-PROCEDURE to execute my concepts.
create TABLE FLAG
(
FLAG VARCHAR2(6)
);
INSERT INTO FLAG VALUES('PASS01');

Creating a [materialised]view from generic data in Oracle/Mysql

I have a generic datamodel with 3 tables
CREATE TABLE Properties
(
propertyId int(11) NOT NULL AUTO_INCREMENT,
name varchar(80) NOT NULL
)
CREATE TABLE Customers
(
customerId int(11) NOT NULL AUTO_INCREMENT,
customerName varchar(80) NOT NULL
)
CREATE TABLE PropertyValues
(
propertyId int(11) NOT NULL,
customerId int(11) NOT NULL,
value varchar(80) NOT NULL
)
INSERT INTO Properties VALUES (1, 'Age');
INSERT INTO Properties VALUES (2, 'Weight');
INSERT INTO Customers VALUES (1, 'Bob');
INSERT INTO Customers VALUES (2, 'Tom');
INSERT INTO PropertyValues VALUES (1, 1, '34');
INSERT INTO PropertyValues VALUES (2, 1, '80KG');
INSERT INTO PropertyValues VALUES (1, 2, '24');
INSERT INTO PropertyValues VALUES (2, 2, '53KG');
What I would like to do is create a view that has as columns all the ROWS in Properties and has as rows the entries in Customers. The column values are populated from PropertyValues.
e.g.
customerId Age Weight
1 34 80KG
2 24 53KG
I'm thinking I need a stored procedure to do this and perhaps a materialised view (the entries in the table "Properties" change rarely).
Any tips?
It's easy enough to generate a view with dynamic SQL:
create or replace procedure gen_view
as
cols_stmt varchar2(32767);
from_stmt varchar2(32767);
subq_name varchar2(30);
begin
for r in ( select * from properties
order by propertyid )
loop
subq_name := 'pv_'||trim(to_char(r.propertyid));
cols_stmt := cols_stmt || ', '|| subq_name ||'.value as '||r.name;
from_stmt := from_stmt || ' left join ( select value, customerid from propertyvalues where propertyid = '
||trim(to_char(r.propertyid))||') '||subq_name
||' on '||subq_name||'.customerid = customers.customerid';
end loop;
execute immediate 'create or replace view eav_view as select customers.customerid, customers.customername'
|| cols_stmt
|| ' from customers '
|| from_stmt;
end gen_view;
/
Here's it working:
SQL> exec gen_view
PL/SQL procedure successfully completed.
SQL> select * from eav_view
2 /
CUSTOMERID
----------
CUSTOMERNAME
--------------------------------------------------------------------------------
AGE
--------------------------------------------------------------------------------
WEIGHT
--------------------------------------------------------------------------------
1
Bob
34
80KG
2
Tom
24
53KG
SQL>
Let's create a new property and insert values for it for some of the customers...
SQL> insert into properties values (3, 'FavouriteIceCream')
2 /
1 row created.
SQL> insert into propertyvalues values (3, 1, 'Cherry Garcia')
2 /
1 row created.
SQL> exec gen_view
PL/SQL procedure successfully completed.
SQL> select * from eav_view
2 /
CUSTOMERID
----------
CUSTOMERNAME
--------------------------------------------------------------------------------
AGE
--------------------------------------------------------------------------------
WEIGHT
--------------------------------------------------------------------------------
FAVOURITEICECREAM
--------------------------------------------------------------------------------
1
Bob
34
80KG
Cherry Garcia
2
Tom
24
53KG
SQL>
"I'm thinking I need a stored
procedure to do this and perhaps a
materialised view (the entries in the
table "Properties" change rarely)."
The problem is, Properties are going to change, and I'm guessing you will have no oversight of when that happens. So you are going to find it very hard to apply the changes to a materialized view. This matters because changing the projection of a materialized view necessitates dropping it. So it's quite difficult to do this without an interruption to service. A similar consideration applies to the regular view , but the outage is almost zero.
If you do want to convert the view statement into a materialized view note that Oracle doesn't seem to like the ANSI-92 syntax when it comes to materialized views (it hurls ORA-12054). I'm not sure why that should be, but the problem went away when I changed to the older joining technique, which is annoying because the outer join syntax is clunkier.
A solution without the need to re-create database objects would be to use the dynamic SQL in a function which returns a Ref Cursor, which maps to a JDBC ResultSet:
create or replace function get_eav_view
return sys_refcursor
as
cols_stmt varchar2(32767);
from_stmt varchar2(32767);
subq_name varchar2(30);
return_value sys_refcursor;
begin
for r in ( select * from properties
order by propertyid )
loop
subq_name := 'pv_'||trim(to_char(r.propertyid));
cols_stmt := cols_stmt || ','|| subq_name ||'.value as '||r.name;
from_stmt := from_stmt || ' left join ( select value, customerid from propertyvalues where propertyid = '
||trim(to_char(r.propertyid))||') '||subq_name
||' on '||subq_name||'.customerid = customers.customerid';
end loop;
open return_value for
'select customers.customerid, customers.customername'
|| cols_stmt
|| ' from customers '
|| from_stmt;
return return_value;
end get_eav_view;
/
This will always return the latest projection:
SQL> var rc refcursor
SQL> exec :rc := get_eav_view
PL/SQL procedure successfully completed.
SQL> print rc
CUSTOMERID
----------
CUSTOMERNAME
--------------------------------------------------------------------------------
AGE
--------------------------------------------------------------------------------
WEIGHT
--------------------------------------------------------------------------------
FAVOURITEICECREAM
--------------------------------------------------------------------------------
1
Bob
34
80KG
Cherry Garcia
2
Tom
24
53KG
SQL>
Now, if we add a new property it gets picked up immediately:
SQL> insert into properties values (4, 'StarSign')
2 /
1 row created.
SQL> insert into propertyvalues values (4, 2, 'Aries')
2 /
1 row created.
SQL> exec :rc := get_eav_view
PL/SQL procedure successfully completed.
SQL> print rc
CUSTOMERID
----------
CUSTOMERNAME
--------------------------------------------------------------------------------
AGE
--------------------------------------------------------------------------------
WEIGHT
--------------------------------------------------------------------------------
FAVOURITEICECREAM
--------------------------------------------------------------------------------
STARSIGN
--------------------------------------------------------------------------------
1
Bob
34
80KG
Cherry Garcia
2
Tom
24
53KG
Aries
SQL>