Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Oracle Database 12c Application Development
1. Oracle Database 12c Application Development
SQL and PL/SQL New Features
-Saurabh K. Gupta
Author of "Oracle Advanced PL/SQL Developer Professional Guide"
2. Table of Contents
SQL New features.................................................................................................................................3
a) Oracle 12c Temporal Support ................................................................................................3
b) Querying a table using SQL Row-Limiting Clause................................................................4
c) Generate Identity Columns in SQL.........................................................................................6
d) SQL Pattern Matching............................................................................................................8
PL/SQL New features.........................................................................................................................10
a) Using Boolean, Record and Collection type as Parameters in Subprograms.......................10
b) The ACCESSIBLE BY Clause.............................................................................................11
c) PL/SQL functions run faster in SQL.....................................................................................12
3. SQL New features
a) Oracle 12c Temporal Support
1. Connect to the SCOTT user in the NONCDB database
[oracle@localhost ~]$ . oraenv
ORACLE_SID = [cdb1] ? noncdb
The Oracle base remains unchanged with value /u01/app/oracle
[oracle@localhost ~]$ sqlplus scott/tiger
2. Create a table with a valid time dimension
CREATE TABLE my_emp(
empno NUMBER,
last_name VARCHAR2(30),
start_time date,
end_time date,
PERIOD FOR user_valid_time (start_time, end_time))
/
3. Populate the table MY_EMP with the test data
INSERT INTO my_emp VALUES (100, 'Ames', to_date('01-JAN-2010','DD-MON-YYYY'),
to_date('30-JUN-2011','DD-MON-YYYY'))
/
INSERT INTO my_emp VALUES (101, 'Burton', to_date('01-JAN-2011','DD-MON-YYYY'),
to_date('30-JUN-2011','DD-MON-YYYY'))
/
INSERT INTO my_emp VALUES (102, 'Chen', to_date('01-JAN-2012','DD-MON-YYYY'),
null)
/
4. Display all records from MY_EMP table
SELECT * FROM my_emp;
EMPNO
---------100
101
102
LAST_NAME
-----------------------------Ames
Burton
Chen
START_TIM
--------01-JAN-10
01-JAN-11
01-JAN-12
END_TIME
--------30-JUN-11
30-JUN-11
5. Run the query to which are valid during 01st June, 2010
SELECT *
FROM my_emp
AS OF PERIOD FOR user_valid_time to_date('01-JUN-2010','DD-MON-YYYY')
/
EMPNO LAST_NAME
START_TIM END_TIME
---------- ------------------- --------- --------100 Ames
01-JAN-10 30-JUN-11
4. 6. Run the query to which are valid between 01st June, 2010 and 01st June, 2011
SELECT *
from my_emp versions
PERIOD FOR user_valid_time BETWEEN
to_date('01-JUN-2010','DD-MON-YYYY') and to_date('01-JUN-2011','DD-MON-YYYY')
/
EMPNO
---------100
101
LAST_NAME
START_TIM END_TIME
------------------------------ --------- --------Ames
01-JAN-10 30-JUN-11
Burton
01-JAN-11 30-JUN-11
7. Use DBMS_FLASHBACK_ARCHIVE package to set the visibility of records. As
SYSDBA, grant EXECUTE privilege on the package to user SCOTT.
7a. Set the visibility to CURRENT and query the records in the MY_EMP table
EXEC dbms_flashback_archive.enable_at_valid_time('CURRENT');
PL/SQL procedure successfully completed.
SELECT * FROM my_emp;
EMPNO LAST_NAME
START_TIM END_TIME
---------- ------------------------------ --------- --------102 Chen
01-JAN-12
7a. Set the visibility to ALL and query the records in the MY_EMP table
EXEC dbms_flashback_archive.enable_at_valid_time('ALL');
PL/SQL procedure successfully completed.
SELECT * FROM my_emp;
EMPNO
---------100
101
102
LAST_NAME
-----------------------------Ames
Burton
Chen
START_TIM
--------01-JAN-10
01-JAN-11
01-JAN-12
END_TIME
--------30-JUN-11
30-JUN-11
b) Querying a table using SQL Row-Limiting Clause
1. Connect to the SCOTT user in the CDB1 database
[oracle@localhost ~]$ . oraenv
ORACLE_SID = [noncdb] ? cdb1
The Oracle base remains unchanged with value /u01/app/oracle
[oracle@localhost ~]$ sqlplus scott/tiger@pdb1
2. Create a test table EMP_TEST for demonstration
CREATE TABLE emp_test
(empno VARCHAR2(30),
deptno NUMBER,
sal NUMBER,
hiredate DATE);
5. 3. Populate the table EMP_TEST with the test data
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
into emp_test values ('Emp1',10,1200,'01-JAN-1985')
into emp_test values ('Emp2',20,1500,'01-APR-1989')
into emp_test values ('Emp3',10,1830,'01-JUN-1993')
into emp_test values ('Emp4',10,1367,'01-DEC-1975')
into emp_test values ('Emp5',20,1344,'01-MAY-1984')
into emp_test values ('Emp6',30,1643,'01-FEB-1989')
into emp_test values ('Emp7',10,1621,'01-JUL-1988')
into emp_test values ('Emp8',30,1764,'01-AUG-1995')
into emp_test values ('Emp8',20,3245,'01-SEP-1986')
into emp_test values ('Emp9',10,3214,'01-JAN-1988')
into emp_test values ('Emp10',20,1245,'01-FEB-1989')
into emp_test values ('Emp11',10,6533,'01-MAR-1990')
into emp_test values ('Emp12',30,1324,'01-NOV-1991')
into emp_test values ('Emp13',20,6342,'01-JAN-1997')
into emp_test values ('Emp14',20,7223,'01-OCT-1983')
into emp_test values ('Emp15',30,2355,'01-NOV-1985')
4. Select the employee details of the first 5 employees ordered by their salaries.
SELECT *
FROM emp_test
ORDER BY sal DESC
FETCH FIRST 5 ROWS ONLY
/
EMPNO
DEPTNO
AL HIREDATE
--------------- ---------- ------- --------Emp14
20
7223 01-OCT-83
Emp11
10
6533 01-MAR-90
Emp16
30
6532 01-MAY-92
Emp13
20
6342 01-JAN-97
Emp20
10
4563 01-AUG-88
6. 5. Select the employee details of the top 25% employees ordered by their salaries.
SELECT *
FROM emp_test
ORDER BY sal DESC
FETCH FIRST 25 PERCENT ROW ONLY
/
EMPNO
DEPTNO
AL HIREDATE
--------------- ---------- ------- --------Emp14
20
7223 01-OCT-83
Emp11
10
6533 01-MAR-90
Emp16
30
6532 01-MAY-92
Emp13
20
6342 01-JAN-97
Emp20
10
4563 01-AUG-88
6. Select the employee details of the next 2 employees ordered by their salaries after the
top-5 employees.
SELECT *
FROM emp_test
ORDER BY SAL DESC
OFFSET 5 ROWS FETCH NEXT 2 ROWS ONLY
/
EMPNO
DEPTNO
SAL HIREDATE
--------------- ---------- ------- --------Emp19
20
3456 01-DEC-87
Emp8
20
3245 01-SEP-86
c) Generate Identity Columns in SQL
1. Connect to the SCOTT user in the CDB1 database
[oracle@localhost ~]$ . oraenv
ORACLE_SID = [noncdb] ? cdb1
The Oracle base remains unchanged with value /u01/app/oracle
[oracle@localhost ~]$ sqlplus scott/tiger@pdb1
2. Create the test table T_GEN_IDTY and include an column ID which generates as
identity
CREATE TABLE t_gen_idty
(id
NUMBER GENERATED AS IDENTITY,
name VARCHAR2(20))
/
3. View the identity column properties in USER_TAB_COLS and
USER_TAB_IDENTITY_COLS dictionary views.
Observe that the DATA_DEFAULT column in USER_TAB_COLS shows a sequence has
been implicitly created by Oracle to supply values to the identity column.
Observe that the IDENTITY_OPTIONS column in USER_TAB_IDENTITY_COLS shows
the sequence characteristics i.e. START WITH, INCREMENT BY, MAX VALUE, MIN
7. VALUE, CYCLE FLAG, CACHE SIZE and ORDER FLAG.
col column_name format a10
col data_default format a35
SELECT column_name,data_default,user_generated,default_on_null,identity_column
FROM user_tab_cols
WHERE table_name='T_GEN_IDTY'
/
COLUMN_NAM DATA_DEFAULT
---------- ----------------------------------ID
"SCOTT"."ISEQ$$_92720".nextval
NAME
USE DEF IDE
--- --- --YES NO YES
YES NO NO
SELECT table_name,column_name, generation_type,identity_options
FROM user_tab_identity_cols
WHERE table_name = 'T_GEN_IDTY'
/
4. Populate the table T_GEN_IDTY with the test data
insert
/
insert
/
insert
/
insert
/
insert
/
insert
/
into t_gen_idty (name) values ('Allen')
into t_gen_idty (name) values ('Matthew')
into t_gen_idty (name) values ('Peter')
into t_gen_idty (name) values ('John')
into t_gen_idty (name) values ('King')
into t_gen_idty (name) values ('Freddy')
5. Select the ID and NAME columns from the table
SELECT id,name
FROM t_gen_idty
/
ID
-------1
2
3
4
5
6
NAME
-------------------Allen
Matthew
Peter
John
King
Freddy
6 rows selected.
6. Manually, try to insert values in ID column. Oracle raises ORA-32795 to restrict manual
inserts on the Identity Columns.
INSERT INTO t_gen_idty VALUES (7,'Steyn');
insert into t_gen_idty values (7,'Steyn')
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column
8. d) SQL Pattern Matching
1. Connect to the SCOTT user in the CDB1 database
[oracle@localhost ~]$ . oraenv
ORACLE_SID = [noncdb] ? cdb1
The Oracle base remains unchanged with value /u01/app/oracle
[oracle@localhost ~]$ sqlplus scott/tiger@pdb1
2. Create a test table for demonstration
CREATE TABLE ticker
(product VARCHAR2(10),
tstamp DATE,
price
NUMBER)
/
3. Populate the table TICKER with the test data
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INSERT
/
INTO ticker VALUES('ACME', '01-Apr-11', 12)
INTO ticker VALUES('ACME', '02-Apr-11', 17)
INTO ticker VALUES('ACME', '03-Apr-11', 19)
INTO ticker VALUES('ACME', '04-Apr-11', 21)
INTO ticker VALUES('ACME', '05-Apr-11', 25)
INTO ticker VALUES('ACME', '06-Apr-11', 12)
INTO ticker VALUES('ACME', '07-Apr-11', 15)
INTO ticker VALUES('ACME', '08-Apr-11', 20)
INTO ticker VALUES('ACME', '09-Apr-11', 24)
INTO ticker VALUES('ACME', '10-Apr-11', 25)
INTO ticker VALUES('ACME', '11-Apr-11', 19)
INTO ticker VALUES('ACME', '12-Apr-11', 15)
INTO ticker VALUES('ACME', '13-Apr-11', 25)
INTO ticker VALUES('ACME', '14-Apr-11', 25)
INTO ticker VALUES('ACME', '15-Apr-11', 14)
INTO ticker VALUES('ACME', '16-Apr-11', 12)
INTO ticker VALUES('ACME', '17-Apr-11', 14)
INTO ticker VALUES('ACME', '18-Apr-11', 24)
INTO ticker VALUES('ACME', '19-Apr-11', 23)
INTO ticker VALUES('ACME', '20-Apr-11', 22)
9. 4. Pattern matching: Look for double bottom patterns ('W' shape)
SELECT product, first_x,first_y,last_w,last_z
FROM Ticker MATCH_RECOGNIZE (
PARTITION BY product
ORDER BY tstamp
MEASURES
first(X.tstamp) as first_x,
first(Y.tstamp) as first_y,
last(Y.tstamp) as last_y,
first(W.tstamp) as last_w,
last(Z.tstamp) as last_z
ONE ROW PER MATCH
PATTERN (X+ Y+ W+ Z+)
DEFINE
X AS (price < PREV(price)),
Y AS (price > PREV(price)),
W AS (price < PREV(price)),
Z AS (price > PREV(price)AND Z.tstamp - FIRST(x.tstamp) <= 7 ))
/
PRODUCT
FIRST_X
FIRST_Y
LAST_W LAST_Z
---------- --------- --------- --------- --------ACME
06-APR-11 07-APR-11 11-APR-11 13-APR-11
5. Pattern matching: Look for V-shape patterns
SELECT product, start_tstamp,bottom_tstamp, end_tstamp
FROM Ticker MATCH_RECOGNIZE (
PARTITION BY product
ORDER BY tstamp
MEASURES STRT.tstamp AS start_tstamp,
LAST(DOWN.tstamp) AS bottom_tstamp,
LAST(UP.tstamp) AS end_tstamp
ONE ROW PER MATCH
AFTER MATCH SKIP TO LAST UP
PATTERN (STRT DOWN+ UP+)
DEFINE
DOWN AS DOWN.price < PREV(DOWN.price),
UP AS UP.price > PREV(UP.price)
) MR
ORDER BY MR.product, MR.start_tstamp
/
PRODUCT
START_TST BOTTOM_TS END_TSTAM
---------- --------- --------- --------ACME
05-APR-11 06-APR-11 10-APR-11
ACME
10-APR-11 12-APR-11 13-APR-11
ACME
14-APR-11 16-APR-11 18-APR-11
10. PL/SQL New features
a) Using Boolean, Record and Collection type as Parameters in
Subprograms
Connect to the SCOTT user in the CDB1 database
[oracle@localhost ~]$ . oraenv
ORACLE_SID = [noncdb] ? cdb1
The Oracle base remains unchanged with value /u01/app/oracle
[oracle@localhost ~]$ sqlplus scott/tiger@pdb1
i) For BOOLEAN datatype
1. Create a procedure with BOOLEAN type parameter.
CREATE OR REPLACE PROCEDURE p_demo_boolean_bind (p_var IN boolean) IS
BEGIN
IF p_var THEN
DBMS_OUTPUT.PUT_LINE('I am true');
ELSE
DBMS_OUTPUT.PUT_LINE('I am false');
END IF;
END;
/
2. Invoke the procedure in an anonymous block using EXECUTE IMMEDIATE to pass the
boolean argument as a bind variable.
set serveroutput on
DECLARE
l_var BOOLEAN := true;
BEGIN
EXECUTE IMMEDIATE 'begin p_demo_boolean_bind(:1); end;' using l_var;
END;
/
ii) For a collection type variable
1. Create a package with a local nested table collection and a member procedure which
uses the local collection type as the input argument.
CREATE OR REPLACE PACKAGE pkg_collection_bind IS
TYPE t is table of number;
PROCEDURE p_coll_bind (p_var t);
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_collection_bind IS
PROCEDURE p_coll_bind (p_var t) is
BEGIN
FOR i in 1..p_var.count
LOOP
DBMS_OUTPUT.PUT_LINE('Element '||i||' is '||p_var(i));
END LOOP;
END;
END;
/
11. 2. Invoke the packaged subprogram in an anonymous block using EXECUTE IMMEDIATE
to pass the collection argument as a bind variable.
set serveroutput on
DECLARE
l_var pkg_collection_bind.t := pkg_collection_bind.t (10,20,30,40,50,60,70);
BEGIN
EXECUTE IMMEDIATE 'begin pkg_collection_bind.p_coll_bind(:1); end;' USING
l_var;
END;
/
b) The ACCESSIBLE BY Clause
1. Create a procedure with an ACCESSIBLE BY clause. Specify the "white list" of trusted
subprograms in the clause who can invoke this procedure. However, it is not mandatory
that the list of subprograms specified must exist in the schema.
CREATE OR REPLACE PROCEDURE p_demo_accessible
AUTHID CURRENT_USER
ACCESSIBLE BY (package coll_pkg, procedure p_white_list, function f_white_list)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Testing ACCESSIBLE BY clause in Oracle 12c');
END;
/
2. Create the "white list" procedure which invokes the above procedure. Invoke the
procedure P_WHITE_LIST to verify the access to P_DEMO_ACCESSIBLE procedure.
CREATE OR REPLACE PROCEDURE p_white_list is
BEGIN
DBMS_OUTPUT.PUT_LINE('Invoking P_DEMO_ACCESSIBLE..');
P_DEMO_ACCESSIBLE;
END;
/
SET SERVEROUTPUT ON
BEGIN
p_white_list;
END;
/
Invoking P_DEMO_ACCESSIBLE..
Testing ACCESSIBLE BY clause in oracle 12c
PL/SQL procedure successfully completed.
3. Try invoking the procedure P_DEMO_ACCESSIBLE in an anonymous block.
begin
P_DEMO_ACCESSIBLE;
end;
/
BEGIN P_DEMO_ACCESSIBLE; END;
*
12. ERROR at line 1:
ORA-06550: line 1, column 7:
PLS-00904: insufficient privilege to access object P_DEMO_ACCESSIBLE;
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
c) PL/SQL functions run faster in SQL
1. Create a test table T
CREATE TABLE
(
PK integer
n1 integer
n2 integer
n3 integer
constraint
)
/
t
not null,
not null,
not null,
not null,
t_PK primary key(PK)
2. Populate the table T by generating the random data using the below PL/SQL program
DECLARE
commit_count constant pls_integer := 100000;
nof_Rows constant pls_integer := 20*commit_count;
Zero constant integer not null := 0;
THS
constant integer not null := 1000;
MIL
constant integer not null := THS*THS;
BIL
constant integer not null := MIL*THS;
TIL
constant integer not null := BIL*THS;
M1
constant integer not null := 2*THS;
M2
constant integer not null := 2*BIL;
Hi
constant integer not null := 2*TIL;
BEGIN
DBMS_Random.Seed(To_Char(Sysdate, 'MM-DD-YYYY HH24:MI:SS'));
for j in 1..Nof_Rows loop
declare
n1 integer not null := DBMS_Random.Value(Zero, M1);
n2 integer not null := DBMS_Random.Value(M1, M2);
n3 integer not null := DBMS_Random.Value(M2, Hi);
begin
insert into t(PK, n1, n2, n3) values(j, n1, n2, n3);
end;
if Mod(j, commit_count) = 0 then
commit;
end if;
end loop;
commit;
END;
/
3. Gather the table stats for the table T
begin
DBMS_Stats.Gather_Table_Stats('SCOTT', 'T');
end;
/
13. 4. Create the PL/SQL function logic to pretty print an integer as a multiple of appropriate
unit of "Thousand", "Million","Billion" or "Trillion". We shall do this activity in different
fashion to measure the compare the performance. Record the timing at each stage to do
the comparison
a) Using a conventional pre 12c standalone function to set the base line
CREATE OR REPLACE FUNCTION F_ShowVal_pre12c(n IN integer) return varchar2 is
THS
constant integer not null := 1000;
MIL
constant integer not null := THS*THS;
BIL
constant integer not null := MIL*THS;
TIL
constant integer not null := BIL*THS;
BEGIN
RETURN
CASE
WHEN n
<= 999 then To_Char(n, '999999')||' units'
WHEN n/THS <= 999 then To_Char(n/THS, '999999')||' Thousand'
WHEN n/MIL <= 999 then To_Char(n/MIL, '999999')||' Million'
WHEN n/BIL <= 999 then To_Char(n/BIL, '999999')||' Billion'
ELSE
To_Char(n/TIL, '999999')||' Trillion'
END;
END F_ShowVal_pre12c;
/
SET TIMING ON
SELECT F_ShowVal_pre12c(n1) n1, F_ShowVal_pre12c(n2) n2, F_ShowVal_pre12c(n3) n3
FROM t
/
b) Using Pure SQL
SET TIMING ON
SELECT PK,
case
case
n1
n1/1000
n1/1000000
n1/1000000000
<= 999 then To_Char(n1, '999999')||' units'
<= 999 then To_Char(n1/1000, '999999')||' Thousand'
<= 999 then To_Char(n1/1000000, '999999')||' Million'
<= 999 then To_Char(n1/1000000000, '999999')||' Billion'
To_Char(n1/1000000000000, '999999')||' Trillion'
when
when
when
when
Else
end,
case
when
when
when
when
Else
n2
n2/1000
n2/1000000
n2/1000000000
<= 999 then To_Char(n2, '999999')||' units'
<= 999 then To_Char(n2/1000, '999999')||' Thousand'
<=999 then To_Char(n2/1000000, '999999')||' Million'
<=999 then To_Char(n2/1000000000, '999999')||' Billion'
To_Char(n2/1000000000000, '999999')||' Trillion'
when
when
when
when
Else
n3
n3/1000
n3/1000000
n3/1000000000
<= 999 then To_Char(n3, '999999')||' units'
<= 999 then To_Char(n3/1000, '999999')||' Thousand'
<= 999 then To_Char(n3/1000000, '999999')||' Million'
<= 999 then To_Char(n3/1000000000, '999999')||' Billion'
To_Char(n3/1000000000000, '999999')||' Trillion'
end,
end
FROM t
/
c) Declaring the PL/SQL function in the subquery's WITH clause
SET TIMING ON
WITH
function ShowVal(n IN integer) return varchar2 is
THS
constant integer not null := 1000;
MIL
constant integer not null := THS*THS;
BIL
constant integer not null := MIL*THS;
TIL
constant integer not null := BIL*THS;
14. Begin
return
case
when n
when n/THS
when n/MIL
when n/BIL
Else
end;
end ShowVal;
SELECT showVal(n1) n1,
FROM t
/
<= THS-1 then To_Char(n, '999999')||' units'
<= THS-1 then To_Char(n/THS, '999999')||' Thousand'
<= THS-1 then To_Char(n/MIL, '999999')||' Million'
<= THS-1 then To_Char(n/BIL, '999999')||' Billion'
To_Char(n/TIL, '999999')||' Trillion'
showVal(n2) n2, showVal(n3) n3
d) Declaring the PL/SQL function using PRAGMA UDF
CREATE OR REPLACE FUNCTION F_ShowVal(n IN integer) return varchar2 is
PRAGMA UDF;
THS
constant integer not null := 1000;
MIL
constant integer not null := THS*THS;
BIL
constant integer not null := MIL*THS;
TIL
constant integer not null := BIL*THS;
BEGIN
RETURN
CASE
WHEN n
<= 999 then To_Char(n, '999999')||' units'
WHEN n/THS <= 999 then To_Char(n/THS, '999999')||' Thousand'
WHEN n/MIL <= 999 then To_Char(n/MIL, '999999')||' Million'
WHEN n/BIL <= 999 then To_Char(n/BIL, '999999')||' Billion'
ELSE
To_Char(n/TIL, '999999')||' Trillion'
END;
END F_ShowVal;
/
SET TIMING ON
SELECT F_ShowVal(n1) n1, F_ShowVal(n2) n2, F_ShowVal(n3) n3
FROM t
/
Record your observations from the steps (a), (b), (c) and (d) in the below matrix. Here is
the performance comparison from the above scenarios Method
Timing recorded
Performance gains
(a) Pre 12c Standalone function 565 sec (Baseline)
1X
(b) Pure SQL
210 sec
2.7X
(c) Using WITH clause
315 sec
1.8X
(d) Using PRAGMA UDF
380 sec
1.5 X