a
allAPEX
And out of Chaos came the
Perfect APEX application
Alex Nuijten
a
allAPEX
And out of Chaos came the
Perfect APEX application
Alex Nuijten
MIKI Yoshihito [Link]
a
allAPEX
! @alexnuijten
[Link]
500+ Technical
Experts Helping
Peers Globally
3 Membership Tiers Connect:
• Oracle ACE Director oracle-ace_ww@[Link]
[Link]/OracleACEProgram
• Oracle ACE
• Oracle ACE Associate [Link]/oracleaces
@oracleace
Nominate yourself or someone you know: [Link]
#malagAPEX18
a new yearly ā'pěks conference in the sun
Malaga, Spain 31st May – 1st June, 2018
[Link]
Jake Guild [Link]
“With HTMLDB you can create applications so fast, …”
ODTUG Kaleidsoscope 2005, unknown presenter
“…you won't give the end user time to change their mind.”
ODTUG Kaleidsoscope 2005, unknown presenter
Easy
Beautiful Fast
g i n g
h a n
C
Cool User Friendly
L i f e
Crisp Scalable
Fun
APEXis
Easy
Too
FAIL
F A IL
irst ttempt n earning
KISS
K IS S
eep t imple tupid
KICKME
K IC K
eep t omplicated eeps
ME e mployed
#
it's all Dynamic SQL
New Requirement
We Ne e d
C us t ome r R at ing!
Do it !
alter table demo_customers
add (cust_rating number (1))
/
comment on column demo_customers.cust_rating
is
'How do we rate this customer? Score: 0-9'
/
update demo_customers
set cust_rating = 5
/
alter table demo_customers
modify cust_rating not null
/
alter table demo_customers
add (cust_rating number (1))
/
comment on column demo_customers.cust_rating
is
'How do we rate this customer? Score: 0-9'
/
update demo_customers
set cust_rating = 5
/
alter table demo_customers
modify cust_rating not null
/
alter table demo_customers
add (cust_rating number (1))
/
comment on column demo_customers.cust_rating
is
'How do we rate this customer? Score: 0-9'
/
update demo_customers
set cust_rating = 5
/
alter table demo_customers
modify cust_rating not null
/
alter table demo_customers
add (cust_rating number (1))
/
comment on column demo_customers.cust_rating
is
'How do we rate this customer? Score: 0-9'
/
update demo_customers
set cust_rating = 5
/
alter table demo_customers
modify cust_rating not null
/
$%
$
%
#
it's all Dynamic SQL
What
Would be
the Correct
Solution?
Photo by Sergio Rao on Unsplash
Concatenation Chaos
cust_last_name || ', ' || cust_first_name
cust_first_name || ', ' || cust_last_name
cust_first_name || ' ' || cust_last_name
select
search_title,
apex_util.prepare_url(search_link) search_link,
search_desc,
'Type' as label_01,
type as value_01,
null search_date
from (
select 1 display_seq,
customer_id id,
'Customer' type,
cust_last_name||', '||cust_first_name search_title,
cust_street_address1||' '||cust_street_address2||', '||cust_city||' '||cust_state||' '||
cust_postal_code search_desc,
'f?p='||:APP_ID||':7:'||:APP_SESSION||':::7:P7_CUSTOMER_ID,P7_BRANCH:'||
apex_escape.html(customer_id)||',30' search_link
from demo_customers
where ( instr(upper(cust_first_name),upper(:P30_SEARCH)) > 0
or instr(upper(cust_last_name),upper(:P30_SEARCH)) > 0
or instr(upper(cust_email),upper(:P30_SEARCH)) > 0
or instr(upper(cust_street_address1),upper(:P30_SEARCH)) > 0
or instr(upper(cust_street_address2),upper(:P30_SEARCH)) > 0
or instr(upper(cust_city),upper(:P30_SEARCH)) > 0
or instr(upper(cust_state),upper(:P30_SEARCH)) > 0
or instr(upper(cust_postal_code),upper(:P30_SEARCH)) > 0
or instr(upper(phone_number1),upper(:P30_SEARCH)) > 0
or instr(upper(phone_number2),upper(:P30_SEARCH)) > 0
or :P30_SEARCH is null )
and instr(:P30_OPTIONS,'C') > 0
union all
select 2 display_seq,
product_id id,
'Product' type,
product_name title,
category||' $'||list_price||' '
||( case when length(product_description) > 50 then
substr(product_description,1,50)||'...'
else
product_description
end ) detail,
'f?p='||:APP_ID||':6:'||:APP_SESSION||':::6:P6_PRODUCT_ID,P6_BRANCH:'||
apex_escape.html(product_id)||',30' search_link
from demo_product_info
where ( instr(upper(product_name),upper(:P30_SEARCH)) > 0
or instr(upper(product_description),upper(:P30_SEARCH)) > 0
or instr(upper(category),upper(:P30_SEARCH)) > 0
or instr(upper(list_price),upper(:P30_SEARCH)) > 0
or :P30_SEARCH is null )
and instr(:P30_OPTIONS,'P') > 0
union all
select distinct 3 display_seq,
o.order_id id,
'Order' type,
o.order_timestamp||' $'||o.order_total title,
c.cust_last_name||', '||c.cust_first_name detail,
'f?p='||:APP_ID||':29:'||:APP_SESSION||':SEARCH::29:P29_ORDER_ID,P29_LAST_PAGE:'||
apex_escape.html(o.order_id)||',30' search_link
from demo_orders o,
demo_customers c,
demo_order_items oi,
demo_product_info p
where o.customer_id = c.customer_id
and o.order_id = oi.order_id
and oi.product_id = p.product_id
and ( instr(upper(o.order_total),upper(:P30_SEARCH)) > 0
or instr(upper(o.order_timestamp),upper(:P30_SEARCH)) > 0
or instr(upper(o.order_total),upper(:P30_SEARCH)) > 0
or instr(upper(c.cust_first_name),upper(:P30_SEARCH)) > 0
or instr(upper(c.cust_last_name),upper(:P30_SEARCH)) > 0
or instr(upper(p.product_name),upper(:P30_SEARCH)) > 0
or instr(upper(p.product_description),upper(:P30_SEARCH)) > 0
or instr(upper([Link]),upper(:P30_SEARCH)) > 0
or :P30_SEARCH is null )
and instr(:P30_OPTIONS,'O') > 0
union all
select 4 display_seq,
customer_id id,
'Customer' type,
cust_last_name||', '||cust_first_name title,
cust_street_address1||' '||cust_street_address2||', '||cust_city||' '||cust_state||' '||
cust_postal_code detail,
'f?p='||:APP_ID||':7:'||:APP_SESSION||':::7:P7_CUSTOMER_ID,P7_BRANCH:'||
apex_escape.html(customer_id)||',30' search_link
from demo_customers
where instr(upper(tags),upper(:P30_SEARCH)) > 0
and instr(:P30_OPTIONS,'T') > 0
union all
select 4 display_seq,
product_id id,
'Product' type,
product_name title,
category||' $'||list_price||' '
||( case when length(product_description) > 50 then
substr(product_description,1,50)||'...'
else
product_description
end ) detail,
'f?p='||:APP_ID||':6:'||:APP_SESSION||':::6:P6_PRODUCT_ID,P6_BRANCH:'||
apex_escape.html(product_id)||',30' search_link
from demo_product_info
where instr(upper(tags),upper(:P30_SEARCH)) > 0
and instr(:P30_OPTIONS,'T') > 0
union all
select distinct 4 display_seq,
o.order_id id,
'Order' type,
o.order_timestamp||' $'||o.order_total title,
c.cust_last_name||', '||c.cust_first_name detail,
'f?p='||:APP_ID||':29:'||:APP_SESSION||':SEARCH::29:P29_ORDER_ID:'||apex_escape.html(o.order_id)
search_link
from demo_orders o,
demo_customers c,
demo_order_items oi,
demo_product_info p
where o.customer_id = c.customer_id
and o.order_id = oi.order_id
and oi.product_id = p.product_id
and instr(upper([Link]),upper(:P30_SEARCH)) > 0
and instr(:P30_OPTIONS,'T') > 0
) order by display_seq, search_title
#
Impact Analysis is Tricky
Photo by Sergio Rao on Unsplash
] 10%
] 90%
#SmartDB
Views Packages
& & &
& &
Page Alias
Never Access Tables Directly
One or More Views per Page
Only Calls to Packages
Page Alias
<APP_ALIAS><Page Nr>
Never Access Tables Directly
One or More Views per Page STRUCT1234
Only Calls to Packages
0 999 General Purpose
1000 6999 Main Application
7000 8999 Reporting
9000 Maintenance, Configuration
STRUCT1000
STRUCT1010 STRUCT1020
STRUCT1021 STRUCT1022 STRUCT1023
>= APEX 5
&APP_PAGE_ALIAS.
>= APEX 5
Page Alias
Never Access Tables Directly
One or More Views per Page
Only Calls to Packages
V_STRUCT1010 STRUCT1000 V_STRUCT1020
STRUCT1010 STRUCT1020
V_STRUCT1021
STRUCT1021 STRUCT1022 STRUCT1023
V_STRUCT1021_2
V_STRUCT1010 STRUCT1000 V_STRUCT1020
STRUCT1010 STRUCT1020
V_STRUCT1021
STRUCT1021 STRUCT1022 STRUCT1023
V_STRUCT1021_2
&
( ( ( ( ( ( ( (
( ( ( ( ( ( ( (
( ' ( ( ( ( ( (
( ( ( ( ' ( ( (
( ( ( ( ( ( ( (
( ( ( ' ( ( ( (
( ( ( ( ( ( ( (
( ( ( ( ( ( ( (
( ( (
Reports: Read Only Views
They're Easy
Simple Views
Regular Views They're Harder
Complex Views
They're Easy
Simple Views
Just follow the
With Check
Regular Views
Wizard
Option
Complex Views
Simple Views
Regular Views They're Harder
Complex Views
Replace DML
Processes
Simple Views
View
&
Complex Views
Views Packages
& & &
& &
Complex Views
Views Packages
& & &
& &
Lost Update
Scott
Deptno: 20
10
& Scott
Deptno: 20
10
Scott James
Scott
Deptno: 20
10 Deptno: 10
Up d ate is L o s t ! James
Scott
&
Deptno: 10
20
Scott James
Scott
Deptno: 20
10 Deptno: 10
MD5: AB42 MD5: AB42
( '
Scott
&
Deptno: 20
10 MD5: CF65
AB42
-- build MD5 function for table "DEMO_CUSTOMERS"
function "BUILD_DEMO_CUSTOMERS_MD5" (
"P_CUSTOMER_ID" in number,...
) return varchar2 is
begin
return apex_util.get_hash(apex_t_varchar2(
"P_CUST_FIRST_NAME",
"P_CUST_LAST_NAME",
"P_CUST_STREET_ADDRESS1",
"P_CUST_STREET_ADDRESS2",
"P_CUST_CITY",
"P_CUST_STATE",
"P_CUST_POSTAL_CODE",
"P_CUST_EMAIL",
"P_PHONE_NUMBER1",
"P_PHONE_NUMBER2",
"P_URL",
"P_CREDIT_LIMIT",
"P_TAGS",
"P_CUST_RATING" ));
end "BUILD_DEMO_CUSTOMERS_MD5";
-- build MD5 function for table "DEMO_CUSTOMERS"
function "BUILD_DEMO_CUSTOMERS_MD5" (
"P_CUSTOMER_ID" in number,...
) return varchar2 is
begin
return apex_util.get_hash(apex_t_varchar2(
"P_CUST_FIRST_NAME",
"P_CUST_LAST_NAME",
"P_CUST_STREET_ADDRESS1",
"P_CUST_STREET_ADDRESS2",
"P_CUST_CITY",
"P_CUST_STATE",
"P_CUST_POSTAL_CODE",
"P_CUST_EMAIL",
"P_PHONE_NUMBER1",
"P_PHONE_NUMBER2",
"P_URL",
"P_CREDIT_LIMIT",
"P_TAGS",
"P_CUST_RATING" ));
end "BUILD_DEMO_CUSTOMERS_MD5";
-- update procedure for table "DEMO_CUSTOMERS"
procedure "UPD_DEMO_CUSTOMERS" (
"P_CUSTOMER_ID" in number,...
"P_MD5" in varchar2 default null)
is
"L_MD5" varchar2(32767) := null;
begin
if "P_MD5" is not null then
for c1 in (
select * from "DEMO_CUSTOMERS"
where "CUSTOMER_ID" = "P_CUSTOMER_ID" FOR UPDATE
) loop
"L_MD5" := "BUILD_DEMO_CUSTOMERS_MD5"(
c1."CUSTOMER_ID", ... );
end loop;
end if;
if ("P_MD5" is null) or ("L_MD5" = "P_MD5") then
update "DEMO_CUSTOMERS"
set "CUSTOMER_ID" = "P_CUSTOMER_ID"
...
where "CUSTOMER_ID" = "P_CUSTOMER_ID";
else
raise_application_error (-20001,'Current version of data in database has changed since user
initiated update process. current checksum = "'||"L_MD5"||'", item checksum = "'||"P_MD5"||'".');
end if;
end "UPD_DEMO_CUSTOMERS";
-- update procedure for table "DEMO_CUSTOMERS"
procedure "UPD_DEMO_CUSTOMERS" (
"P_CUSTOMER_ID" in number,...
"P_MD5" in varchar2 default null)
is
"L_MD5" varchar2(32767) := null;
begin
if "P_MD5" is not null then
for c1 in (
select * from "DEMO_CUSTOMERS"
where "CUSTOMER_ID" = "P_CUSTOMER_ID" FOR UPDATE
) loop
"L_MD5" := "BUILD_DEMO_CUSTOMERS_MD5"(
c1."CUSTOMER_ID", ... );
end loop;
end if;
if ("P_MD5" is null) or ("L_MD5" = "P_MD5") then
update "DEMO_CUSTOMERS"
set "CUSTOMER_ID" = "P_CUSTOMER_ID"
...
where "CUSTOMER_ID" = "P_CUSTOMER_ID";
else
raise_application_error (-20001,'Current version of data in database has changed since user
initiated update process. current checksum = "'||"L_MD5"||'", item checksum = "'||"P_MD5"||'".');
end if;
end "UPD_DEMO_CUSTOMERS";
-- update procedure for table "DEMO_CUSTOMERS"
procedure "UPD_DEMO_CUSTOMERS" (
"P_CUSTOMER_ID" in number,...
"P_MD5" in varchar2 default null)
is
"L_MD5" varchar2(32767) := null;
begin
if "P_MD5" is not null then
for c1 in (
select * from "DEMO_CUSTOMERS"
where "CUSTOMER_ID" = "P_CUSTOMER_ID" FOR UPDATE
) loop
"L_MD5" := "BUILD_DEMO_CUSTOMERS_MD5"(
c1."CUSTOMER_ID", ... );
end loop;
end if;
if ("P_MD5" is null) or ("L_MD5" = "P_MD5") then
update "DEMO_CUSTOMERS"
set "CUSTOMER_ID" = "P_CUSTOMER_ID"
...
where "CUSTOMER_ID" = "P_CUSTOMER_ID";
else
raise_application_error (-20001,'Current version of data in database has changed since user
initiated update process. current checksum = "'||"L_MD5"||'", item checksum = "'||"P_MD5"||'".');
end if;
end "UPD_DEMO_CUSTOMERS";
-- update procedure for table "DEMO_CUSTOMERS"
procedure "UPD_DEMO_CUSTOMERS" (
"P_CUSTOMER_ID" in number,...
"P_MD5" in varchar2 default null)
is
"L_MD5" varchar2(32767) := null;
begin
if "P_MD5" is not null then
for c1 in (
select * from "DEMO_CUSTOMERS"
where "CUSTOMER_ID" = "P_CUSTOMER_ID" FOR UPDATE
) loop
"L_MD5" := "BUILD_DEMO_CUSTOMERS_MD5"(
c1."CUSTOMER_ID", ... );
end loop;
end if;
if ("P_MD5" is null) or ("L_MD5" = "P_MD5") then
update "DEMO_CUSTOMERS"
set "CUSTOMER_ID" = "P_CUSTOMER_ID"
...
where "CUSTOMER_ID" = "P_CUSTOMER_ID";
else
raise_application_error (-20001,'Current version of data in database has changed since user
initiated update process. current checksum = "'||"L_MD5"||'", item checksum = "'||"P_MD5"||'".');
end if;
end "UPD_DEMO_CUSTOMERS";
-- update procedure for table "DEMO_CUSTOMERS"
procedure "UPD_DEMO_CUSTOMERS" (
"P_CUSTOMER_ID" in number,...
"P_MD5" in varchar2 default null)
is
"L_MD5" varchar2(32767) := null;
begin
if "P_MD5" is not null then
for c1 in (
select * from "DEMO_CUSTOMERS"
where "CUSTOMER_ID" = "P_CUSTOMER_ID" FOR UPDATE
) loop
"L_MD5" := "BUILD_DEMO_CUSTOMERS_MD5"(
c1."CUSTOMER_ID", ... );
end loop;
end if;
if ("P_MD5" is null) or ("L_MD5" = "P_MD5") then
update "DEMO_CUSTOMERS"
set "CUSTOMER_ID" = "P_CUSTOMER_ID"
...
where "CUSTOMER_ID" = "P_CUSTOMER_ID";
else
raise_application_error (-20001,'Current version of data in database has changed since user
initiated update process. current checksum = "'||"L_MD5"||'", item checksum = "'||"P_MD5"||'".');
end if;
end "UPD_DEMO_CUSTOMERS";
Page Alias
Never Access Tables Directly
One or More Views per Page
Only Calls to Packages
obvious
API: clearly defined methods
of communication between various
software components.
STRUCT1000
STRUCT1010 STRUCT1020
STRUCT_1010_PKG
STRUCT1021 STRUCT1022 STRUCT1023
MD5 DEL
INS UPD
STRUCT_1021_PKG
MD5 Reimburse
Process Create
Validate
STRUCT1021
STRUCT_1021_PKG
MD5 Reimburse
Process Create
Customer API Order API Invoice API
& & & & &
STRUCT1021
STRUCT_1021_PKG
MD5 Reimburse
Process Create
Customer API Order API Invoice API
& & & & &
STRUCT1021 STRUCT2011
STRUCT_1021_PKG STRUCT_2011_PKG
MD5 Reimburse
Process Create
Validate
Customer API Order API Invoice API
& & & & &
of course fully instrumented
Logger
iangbl [Link]
) [Link]
Virtual Column
Photo by Micheile Henderson on Unsplash
Column Based
on Expression
Photo by Micheile Henderson on Unsplash
alter table demo_customers
add customer_name generated always
as (cust_last_name || ', ' || cust_first_name)
alter table demo_customers
add customer_name generated always
as (cust_last_name || ', ' || cust_first_name)
alter table demo_customers
add customer_address generated always as
(cust_street_address1 ||
decode(cust_street_address2, null, null, ', ' ||
cust_street_address2))
alter table demo_customers
add customer_address generated always as
(cust_street_address1 ||
decode(cust_street_address2, null, null, ', ' ||
cust_street_address2))
create or replace view v_cust0002
as
select customer_id,
customer_name,
customer_address,
cust_city,
cust_state,
cust_postal_code,
tags
from demo_customers
create or replace view v_cust0002
as
select customer_id,
customer_name,
customer_address,
cust_city,
cust_state,
cust_postal_code,
tags
from demo_customers
process_customer (p_customer_id in demo_customers.customer_id%type
,p_cust_first_name in demo_customers.cust_first_name%type
,p_cust_last_name in demo_customers.cust_last_name%type
,p_cust_street_address1 in demo_customers.cust_street_address1%type
,p_cust_street_address2 in demo_customers.cust_street_address2%type
,p_cust_city in demo_customers.cust_city%type
,p_cust_state in demo_customers.cust_state%type
,p_cust_postal_code in demo_customers.cust_postal_code%type
,p_tags in demo_customers.tags%type
);
process_customer (p_customer_id in demo_customers.customer_id%type
,p_cust_first_name in demo_customers.cust_first_name%type
,p_cust_last_name in demo_customers.cust_last_name%type
,p_cust_street_address1 in demo_customers.cust_street_address1%type
,p_cust_street_address2 in demo_customers.cust_street_address2%type
,p_cust_city in demo_customers.cust_city%type
,p_cust_state in demo_customers.cust_state%type
,p_cust_postal_code in demo_customers.cust_postal_code%type
,p_tags in demo_customers.tags%type
);
process_customer (p_customer_id in demo_customers.customer_id%type
,p_cust_first_name in demo_customers.cust_first_name%type
,p_cust_last_name in demo_customers.cust_last_name%type
,p_cust_street_address1 in demo_customers.cust_street_address1%type
,p_cust_street_address2 in demo_customers.cust_street_address2%type
,p_cust_city in demo_customers.cust_city%type
,p_cust_state in demo_customers.cust_state%type
,p_cust_postal_code in demo_customers.cust_postal_code%type
,p_tags in demo_customers.tags%type
);
Maybe Hide the
Columns from
Developer
alter table demo_customers
modify cust_first_name
invisible
Photo by Milivoj Kuhar on Unsplash
Maybe Not
Photo by Milivoj Kuhar on Unsplash
[Link]
Steven Lam
Datamodel
SmartDB
APEX
a
allAPEX
?#
Alex Nuijten
+ [Link] , alex@[Link] ! @alexnuijten - [Link]
%
$
Frank Boston Dennis Yang
[Link] [Link]