Dynamically unpivot table within Oracle - sql

I have a table with several hundred columns that I need to unpivot. All of the columns that need to be unpivoted start with 'SIM_'. I know how to do this statically (example below), but I'd like a dynamic solution - as the number of columns that need be be unpivot is both long and may change over time.
SELECT
*
FROM
(SELECT
ID,
NAME,
SIM_1,
SIM_2,
SIM_3
FROM
SAMPLE_TABLE
) T UNPIVOT(SIM_RESULT FOR SIM IN (SIM_1, SIM_2, SIM_3))

Maybe you could (re)create a view when there is a need to refresh the list of columns. This way you will have the accurate list of columns and a way to show real data. It does the unpivoting of all columns (like SIM_%) in the time of execution.
/* sample table and data
CREATE TABLE AA_TST
(ID NUMBER(6), SIM_A VARCHAR2(20), SIM_B VARCHAR2(20), SIM_C VARCHAR2(20), SIM_D VARCHAR2(20), SIM_E VARCHAR2(20));
INSERT INTO AA_TST VALUES(1, 'A', 'B', 'C', 'D', 'E');
INSERT INTO AA_TST VALUES(2, 'AA', 'BB', 'CC', 'DD', 'EE');
INSERT INTO AA_TST VALUES(3, 'AAA', 'BBB', 'CCC', 'DDD', 'EEE');
*/
Declare
col_list VarChar2(1000);
myViewSQL VarChar2(1000);
Begin
SELECT LISTAGG(column_name, ', ') WITHIN GROUP (ORDER BY column_name)
INTO col_list
FROM all_tab_columns
WHERE table_name = 'AA_TST' And
column_name LIKE 'SIM_%';
--
myViewSQL := 'SELECT VALUE_NAME, VALUE_OF FROM AA_TST UNPIVOT (VALUE_OF FOR VALUE_NAME IN(' || col_list || '))';
execute immediate 'CREATE or replace VIEW AA_TST_VIEW AS ' || myViewSQL;
End;
/
SELECT * FROM AA_TST_VIEW;
--
-- R e s u l t
--
-- anonymous block completed
-- VALUE_NAME VALUE_OF
-- ---------- --------------------
-- SIM_A A
-- SIM_B B
-- SIM_C C
-- SIM_D D
-- SIM_E E
-- SIM_A AA
-- SIM_B BB
-- SIM_C CC
-- SIM_D DD
-- SIM_E EE
-- SIM_A AAA
-- SIM_B BBB
-- SIM_C CCC
-- SIM_D DDD
-- SIM_E EEE
--
-- 15 rows selected

Related

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

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

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.

Making multiple rows out of single row, SAP HANA Calculation View

I have this below table
TYPE ID VERSION
A WXYZ a#bbb#aaa
A ABCD cc#qq
I want output like this
Type ID VERSION
A WXYZ a
A WXYZ bb
A WXYZ aaa
A ABCD cc
A ABCD qq
It is possible to do using Cursors, I have done it, is it possible to do it without Cursor?
create table v_test (type varchar(5), id varchar(10) , versionn varchar(100));
insert into v_test values ('A', 'WXYZ' , 'a#bbb#aaa');
insert into v_test values ('A', 'ABCD' , 'cc#qq');
select * from v_test
Select * from
( select "ID" "KEY", "ELEMENT_NUMBER" "ORD" ,
SUBSTR_REGEXPR('(?<=^|#)([^#]*)(?=#|$)' IN "V_TEST"."VERSIONN" OCCURRENCE "SERIES"."ELEMENT_NUMBER" GROUP 1) "VERSIONN"
from v_test,
SERIES_GENERATE_INTEGER(1, 1, 10 ) "SERIES" -- replace 10 with your max. number of values in CSV-Field
)
where "VERSIONN" is not null
order by "KEY", "ORD"

Oracle DB SQL Procedure Iterate over table with dynamic table bame

I'm working on an Oracle Stored procedure.
I need to iterate over rows of a table . I can do that using:
FOR eachrow IN table_name
LOOP
END LOOP;
But i need the table_name to be dynamic.
For example the table names are stored in some other table.
So, i can do FOR loop on that table and inside the loop ,i want to iterate through the rows of the new table.
Please suggest how i can achieve that.
Thanks,
Sash
This is not possible with a FOR eachrow IN table_name LOOP, you have to use a ref cursor instead:
First of all some sample data. These tables have some column names in common, in your inner for loop you can only access these columns, this is what I wrote in my comment above. Result type should be the same in the inner loop.
create table froc_a(id number, name varchar2(10), row_added date);
insert into froc_a values (1, '1', sysdate);
insert into froc_a values (2, '2', sysdate - 2);
insert into froc_a values (4, '4', sysdate - 4);
create table froc_b(id number, name2 varchar2(10), row_added date);
insert into froc_b values (1, 'b1', sysdate);
insert into froc_b values (2, 'b2', sysdate - 2);
insert into froc_b values (4, 'b4', sysdate - 4);
create table froc_c(id number, txt varchar2(10), row_added date);
insert into froc_c values (1, 'c1', sysdate);
insert into froc_c values (2, 'c2', sysdate - 2);
insert into froc_c values (4, 'c4', sysdate - 4);
Here is a first approach how to write it:
declare
TYPE curtype IS REF CURSOR;
l_cursor curtype;
l_param_id number;
l_id number;
l_val varchar2(100);
begin
l_param_id := 1;
-- Loop over your table names
for l_rec in (with tabnames(name) as
(select 'froc_a'
from dual
union all
select 'froc_b'
from dual
union all
select 'froc_c'
from dual)
select * from tabnames) loop
dbms_output.put_line(l_rec.name);
-- Open cursor for current table
open l_cursor for 'select id, row_added from ' || l_rec.name || ' where id = :1'
using l_param_id;
-- Loop over rows of current table
loop
fetch l_cursor
into l_id, l_val;
exit when l_cursor%notfound;
dbms_output.put_line(l_id || ', ' || l_val);
end loop;
end loop;
end;
Output:
froc_a
1, 06-APR-16
froc_b
1, 06-APR-16
froc_c
1, 06-APR-16

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>