How can i make uniqueness in a sql table with using columns? - sql

in my table there is no unique column , but i need to make uniqueness with using columns , adding a new column is worst option , i need to do this something like concat two or more columns how can i do that ?
i need to make uniqeness in my sql table with using two or more columns , adding a new columns is worst option , i need a solution which is concat two or more columns something like that how can i do that ?

Oracle
There is a way to check for "uniqueness" if you compare distinct values of different field combinations with total number of rows. In below example the score 0 (zero) means uniqueness:
WITH
tbl AS
(
Select 1 "COL_1", 'AXC' "COL_2", 'khgh jklj' "COL_3" From Dual Union All
Select 3 "COL_1", 'AXC' "COL_2", 'khgh jklj' "COL_3" From Dual Union All
Select 3 "COL_1", 'DEF' "COL_2", 'khgh jklj' "COL_3" From Dual Union All
Select 1 "COL_1", 'DEF' "COL_2", 'xxxx yyyy' "COL_3" From Dual
)
Select
Count(*) "TOTAL",
Count(*) - Count(DISTINCT COL_1) "COL_1_SCORE",
Count(*) - Count(DISTINCT COL_2) "COL_2_SCORE",
Count(*) - Count(DISTINCT COL_3) "COL_3_SCORE",
Count(*) - Count(DISTINCT COL_1 || COL_2) "COL_1-2_SCORE",
Count(*) - Count(DISTINCT COL_1 || COL_3) "COL_1-3_SCORE",
Count(*) - Count(DISTINCT COL_2 || COL_3) "COL_2-3_SCORE",
Count(*) - Count(DISTINCT COL_1 || COL_2 || COL_3) "COL_1-2-3_SCORE"
From
tbl
/* R e s u l t:
TOTAL COL_1_SCORE COL_2_SCORE COL_3_SCORE COL_1-2_SCORE COL_1-3_SCORE COL_2-3_SCORE COL_1-2-3_SCORE
---------- ----------- ----------- ----------- ------------- ------------- ------------- ---------------
4 2 2 2 0 1 1 0
*/

You can combine a handful of columns into a unique row id with the following SQL code.
I have done this when making data sets for machine learning in BigQuery.
select distinct
cast(to_hex(SHA1(concat(column1, column2, column3, column4))) as string) as observation_id
from table
Of course you need to check beforehand, that the combination of these columns produces unique rows.

One option is to create a unique index on set of columns you want to enforce uniqueness on.
For example:
create unique index ui1_test on test (id, name, address);
Doing so, users won't be able to insert new rows (or update existing ones) to a combination (of these columns) that already exists in the table as Oracle will raise an error.

I just implemented the following to make customer_id a unique column by combining a random number, part of a SYSTIMESTAMP and a sequence NUMBER. To make it difficult to decipher and shorter in length I ran in through a function. I also store the origin (seed) that was used to create the unique column.
As for the first_name and last_name I use so it makes sense in the example as there are names associated with customers.
CREATE OR REPLACE PACKAGE mf_names IS
FUNCTION random_first_name(
gender IN VARCHAR2 DEFAULT NULL,
percentage_mf IN NUMBER DEFAULT 50
) RETURN VARCHAR2;
FUNCTION random_last_name RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY mf_names IS
first_names_male SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Tom', 'Andrew', 'Paul', 'Peter', 'Keith', 'Mark', 'Solomon', 'Joseph', 'John', 'Melvin', 'Harry', 'Barry', 'Larry', 'Gary', 'Jeffrey', 'karl'
);
first_names_female SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Alice', 'Anna', 'Barbara', 'Carol', 'Debra', 'Madison', 'Faith', 'Cheryl', 'Beth', 'Kathy', 'Jill', 'Grayce', 'Lynn', 'Roz', 'Deena', 'Laura', 'Sophia'
);
last_names SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST(
'Cooper', 'Dimeo', 'Spatafore', 'Jones', 'Coralnick', 'Torchiano', 'Fazio', 'Behrens', 'Lebowitz','Stern', 'Malden', 'Kramer','Stein', 'Tessio', 'Weinreb', 'Dillon', 'Rucker', 'Silverberg', 'Aarron', 'Kern', 'Saladino', 'Rice', 'Sanford', 'Orr', 'Roth'
);
FUNCTION random_first_name(
gender IN VARCHAR2 DEFAULT NULL,
percentage_mf IN NUMBER DEFAULT 50
) RETURN VARCHAR2
IS
BEGIN
IF UPPER(gender) LIKE 'M%' THEN
RETURN first_names_male(FLOOR(DBMS_RANDOM.VALUE(1, first_names_male.COUNT + 1)));
ELSIF UPPER(gender) LIKE 'F%' THEN
RETURN first_names_female(FLOOR(DBMS_RANDOM.VALUE(1, first_names_female.COUNT + 1)));
ELSIF DBMS_RANDOM.VALUE(0, 100) < percentage_mf THEN
RETURN random_first_name('M');
ELSE
RETURN random_first_name('F');
END IF;
END;
FUNCTION random_last_name RETURN VARCHAR2
IS
BEGIN
RETURN last_names(FLOOR(DBMS_RANDOM.VALUE(1, last_names.COUNT + 1)));
END;
END;
/
CREATE TABLE CUSTOMERS (
customer_id VARCHAR2 (20),
seed NUMBER,
first_name VARCHAR2 (20),
last_name VARCHAR2 (20));
create sequence customer_seq start with 1000000 minvalue 1000000 maxvalue 9999999 cycle;
create or replace function base34(p_num number) return varchar2 is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := p_num;
l_str varchar2(38);
begin
loop
l_str := substr(l_dig,mod(l_num,34)+1,1) || l_str ;
l_num := trunc(l_num/34);
exit when l_num = 0;
end loop;
return l_str;
end;
/
create or replace function dec34(p_str varchar2) return number is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := 0;
begin
for i in 1 .. length(p_str) loop
l_num := l_num * 34 + instr(l_dig,upper(substr(p_str,i,1)))-1;
end loop;
return l_num;
end;
/
create or replace trigger customer_trg
before update on customers for each row
begin
if ( updating('customer_id') )
then
raise_application_error(-20000,'Cant Update customer_id');
end if;
if ( updating('seed') )
then
raise_application_error(-20001,'Cant Update seed');
end if;
end;
/
DECLARE
seed NUMBER;
begin
for i in 1 .. 10 loop
seed := (to_number(trunc(dbms_random.value(1000,9999))|| to_char(systimestamp,'FFSS')||customer_seq.nextval));
INSERT into customers(
customer_id,
seed,
first_name,
last_name
) VALUES (
base34(seed),
seed,
mf_names.random_first_name(),
mf_names.random_last_name()
);
end loop;
end;
/
SELECT * from customers
CUSTOMER_ID SEED FIRST_NAME LAST_NAME
BJGEYVDR5HSBEL8 3754328637000441000120 Cheryl Stern
B8FPDSMR0WQSTNZ 4886346472000441000121 Roz Dillon
00KGDZARBUZTCB6 5711346658000441000122 Solomon Kern
KBMP0WU4VQ76T5 1058346784000441000123 Grayce Lebowitz
0EXWKL06SC2E3R6 6080346899000441000124 karl Jones
BM9XSDNX7CWNNZC 4128347038000441000125 Alice Sanford
01SKXFTE18JTT5M 5981347154000441000126 Cheryl Saladino
B53A6Y19CF7B54F 4164347334000441000127 John Stein
B3XMHNKT9LDLSDA 3726347470000441000128 Joseph Lebowitz
B7FT0265MYGJUTP 4643347602000441000129 Debra Rice
select to_char(seed) from customers where rownum = 1
union all
select base34(seed) from customers where rownum = 1
union all
select to_char(dec34(base34(seed))) from customers where rownum = 1;
TO_CHAR(SEED)
3754328637000441000120
BJGEYVDR5HSBEL8
3754328637000441000120
select base34(seed),
dump(base34(seed)) from customers where rownum = 1
BASE34(SEED) DUMP(BASE34(SEED))
BJGEYVDR5HSBEL8 Typ=1 Len=15: 66,74,71,69,89,86,68,82,53,72,83,66,69,76,56

Related

Procedure to insert data if not exist pl sql

I have a table with these columns (id,first_name,birth). I want to create a procedure that insert a new customer only if the id inserted doesn't exist in the table. If it already exist, then don't insert it. This is my code so far, but I got an error 'line 3 sql statement ignored'. Any idea? I need to use procedure and pl sql in oracle. Thanks!
CREATE OR REPLACE PROCEDURE add_emp(v_id IN int,
v_name IN varchar2,
v_bday IN date) IS
BEGIN
INSERT INTO Employees
(Id, First_name, Birth)
SELECT *
FROM (SELECT v_id, v_name, v_bday) AS tmp
WHERE NOT EXISTS (SELECT Id FROM Employees WHERE Id = v_id);
END;
/
DECLARE
m_id int := 3;
m_name varchar2 := 'John';
m_bday date := '16-Dec-1990';
BEGIN
add_cust(m_id, m_name, m_bday);
END;
/
Your procedure has some syntax issue which is fixed in following code:
CREATE OR REPLACE PROCEDURE ADD_EMP (
V_ID IN INT,
V_NAME IN VARCHAR2,
V_BDAY IN DATE
) IS
BEGIN
INSERT INTO EMPLOYEES (
ID,
FIRST_NAME,
BIRTH
)
SELECT V_ID,
V_NAME,
V_BDAY
FROM DUAL -- FROM clause was missing
WHERE NOT EXISTS (
SELECT ID
FROM EMPLOYEES
WHERE ID = V_ID
);
END;
/
Also, Your calling PL/SQL block has some issues which are corrected in the following code:
DECLARE
M_ID INT := 3;
M_NAME VARCHAR2(10) := 'John'; -- varchar2 must be declared with size
M_BDAY DATE := DATE '1990-12-16'; -- added date literal to convert string to date
BEGIN
ADD_CUST(M_ID, M_NAME, M_BDAY);
END;
/
In Oracle SELECT does not work without a FROM clause (other DBMS products are different). So you need to provide a table; you can use DUAL, which is a dummy table provided by Oracle which is guaranteed to return one row.
INSERT INTO Employees(Id,First_name,Birth)
SELECT v_id, v_name, v_bday
from dual
WHERE NOT EXISTS (
SELECT Id FROM Employees WHERE Id = v_id
);
Your INSERT statement would work with a slight change
CREATE OR REPLACE PROCEDURE add_emp(v_id Employees.Id%type,
v_name Employees.First_name%type,
v_bday Employees.Birth%type) IS
BEGIN
INSERT INTO Employees
(Id, First_name, Birth)
SELECT v_id, v_name, v_bday
FROM dual
WHERE NOT EXISTS (SELECT * FROM Employees WHERE Id = v_id);
END;
/
You can replace INSERT statement with MERGE as an alternative DML such as
MERGE INTO Employees e1
USING
(SELECT v_id AS id, v_name AS First_name, v_bday AS birth
FROM dual) e2
ON ( e1.id = e2.id )
WHEN MATCHED THEN UPDATE SET e1.First_name = e2.First_name,
e1.Birth = e2.Birth -- If you need to change for the matching case
WHEN NOT MATCHED THEN INSERT( e1.id, e1.First_name, e1.birth )
VALUES( e2.id, e2.First_name, e2.birth );

How to store fetched bulk values into single variable and insert all values it on one variable using pl/sql

Note
Somebody Tell me how to store Multiples rows value for a single column store into a single variable and behalf of that variable returned values store in table on one single click how to do such scenario using pl/sql.
Like Such Query Return single column with Multi rows i want to store it into some var and insert all values on single click process.It will generate error image of error has attached
DECLARE
l_respondent_id VARCHAR(100);
BEGIN
FOR c IN (
SELECT
s.gr_number
FROM
student s
LEFT JOIN class_time ct ON ct.class_id = s.class_id
AND instr(s.class_time, ct.class_time) > 0
WHERE
upper(TRIM(ct.class_id)) = upper(TRIM(:app_user))
AND s.gr_number IS NOT NULL
AND is_active_flg = 'Y'
) LOOP
l_respondent_id := c.gr_number;
BEGIN
SELECT
s.gr_number
INTO l_respondent_id
FROM
student s
LEFT JOIN class_time ct ON ct.class_id = s.class_id
AND instr(s.class_time, ct.class_time) > 0
WHERE
upper(TRIM(ct.class_id)) = upper(TRIM(:app_user))
AND s.gr_number IS NOT NULL
AND is_active_flg = 'Y'
AND s.gr_number = c.gr_number;
EXCEPTION
WHEN no_data_found THEN
l_respondent_id := NULL;
END;
IF l_respondent_id IS NULL THEN
INSERT INTO student_class_attend ( gr_number ) VALUES ( c.gr_number ) RETURNING gr_number INTO l_respondent_id;
END IF;
END LOOP;
END;
You can probably use a nested table for this, together with a BULK COLLECT and a FORALL. Simplified example:
Tables and data
create table students( id primary key, fname, lname )
as
select 1, 'fname_1', 'lname_1' from dual union all
select 2, 'fname_2', 'lname_2' from dual union all
select 3, 'fname_3', 'lname_3' from dual union all
select 4, 'fname_4', 'lname_4' from dual union all
select 5, 'fname_5', 'lname_5' from dual union all
select 6, 'fname_6', 'lname_6' from dual ;
-- empty
create table attendance( studentid number references students, when_ date ) ;
Anonymous block
declare
-- This will allow you to store several studentIDs:
type studentid_t is table of students.id%type index by pls_integer ;
-- The current attendance list. Notice the TYPE.
attendancelist studentid_t ;
begin
-- Your query (including all the necessary joins and conditions) here.
-- In this case, we will pick up 3 of the 6 IDs.
select id bulk collect into attendancelist
from students
where mod( id, 2 ) = 0 ;
-- Write a _single_ DML statement after FORALL (which is not a loop - see docs)
forall i in attendancelist.first .. attendancelist.last
insert into attendance( studentid, when_ )
values( attendancelist( i ) , sysdate ) ; -- use whatever you need here
end ;
/
Result (after executing the anonymous block)
SQL> select * from attendance ;
STUDENTID WHEN_
---------- ---------
2 07-JUN-20
4 07-JUN-20
6 07-JUN-20
DBfiddle here.

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

Find out what are all the columns having NULL values in the table for a given row in Oracle

For Example if the table is like below
ID FIRST_NAME LAST_NAME START_DAT END_DATE SALARY CITY DESCRIPTION
---- ---------- ---------- --------- --------- ---------- -------------------
01 Jason Martin 25-JUL-96 NULL 1234.56 NULL Programmer
02 Alison Mathews 21-MAR-76 21-FEB-86 6661.78 Vancouver Tester
For the record ID "1", END_DATE and CITY are having null values. How to get these values in a single query?
You can use case to figure out what has a NULL value and concatenate the results together:
select ((case when id is null then 'id;' end) ||
(case when first_name is null then 'firstname;' end) ||
. . .
(case when description is null then 'description;' end)
) as NullColumns
from table;
Note: Oracle treats NULL values as empty strings for the concatenation operator.
You can use this function:
create or replace function list_null_cols(i_table in varchar2,
i_filter in varchar2 default '') return varchar2 is
v_sql varchar2(32000);
cursor cols is
select column_name cn from user_tab_cols
where table_name = upper(i_table) and nullable='Y';
ret varchar2(32000);
begin
for c in cols loop
v_sql := v_sql || 'max(nvl2('||c.cn||',null,'''||c.cn||',''))||';
end loop;
if length(v_sql) = 0 then
return 'no columns found';
end if;
v_sql := 'select '||rtrim(v_sql, '|| ') ||' from '||i_table||' '||i_filter;
begin
execute immediate v_sql into ret;
exception when others then
return 'error: '||sqlerrm;
end;
return rtrim(ret, ',');
end;
First parameter is table name, second, optional is where... filter. If you omit second parameter all rows in table will be analyzed, like in examples:
create table test_table (ID varchar2(2), FIRST_NAME number, LAST_NAME number,
START_DATE number, END_DATE number, SALARY number, CITY number, DESCRIPTION number);
insert into test_table values ('01', 1, 1, 1, null, 1, 1, 1);
insert into test_table values ('02', 2, 2, 2, 2, 2, null, 2);
select list_null_cols('test_table') list from dual;
LIST
-------------------
END_DATE,CITY
select list_null_cols('test_table', ' where id=''01''') list from dual;
LIST
-------------------
END_DATE
select list_null_cols('test_table', ' where salary=2') list from dual;
LIST
-------------------
CITY

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>