Development

“Collection iterator pickler fetch”: pipelined vs simple table functions

XTended Oracle SQL - Tue, 2017-12-12 19:08

Alex R recently discovered interesting thing: in SQL pipelined functions work much faster than simple non-pipelined table functions, so if you already have simple non-pipelined table function and want to get its results in sql (select * from table(fff)), it’s much better to create another pipelined function which will get and return its results through PIPE ROW().

A bit more details:

Assume we need to return collection “RESULT” from PL/SQL function into SQL query “select * from table(function_F(…))”.
If we create 2 similar functions: pipelined f_pipe and simple non-pipelined f_non_pipe,

create or replace function f_pipe(n int) return tt_id_value pipelined 
as
  result tt_id_value;
begin
  ...
  for i in 1..n loop
    pipe row (result(i));
  end loop;
end f_pipe;
/
create or replace function f_non_pipe(n int) return tt_id_value 
as
  result tt_id_value;
begin
  ...
  return result;
end f_non_pipe;
/
Full functions definitions
create or replace type to_id_value as object (id int, value int)
/
create or replace type tt_id_value as table of to_id_value
/
create or replace function f_pipe(n int) return tt_id_value pipelined 
as
  result tt_id_value;
  
  procedure gen is
  begin
     result:=tt_id_value();
     result.extend(n);
     for i in 1..n loop
        result(i):=to_id_value(i, 1);
     end loop;
  end;    
begin
  gen();
  for i in 1..n loop
    pipe row (result(i));
  end loop;
end f_pipe;
/
create or replace function f_non_pipe(n int) return tt_id_value 
as
  result tt_id_value;
  
  procedure gen is
  begin
     result:=tt_id_value();
     result.extend(n);
     for i in 1..n loop
        result(i):=to_id_value(i, 1);
     end loop;
  end;    
begin
  gen();
  return result;
end f_non_pipe;
/
create or replace function f_pipe_for_nonpipe(n int) return tt_id_value pipelined 
as
  result tt_id_value;
begin
  result:=f_non_pipe(n);
  for i in 1..result.count loop
    pipe row (result(i));
  end loop;
end;
/
create or replace function f_udf_pipe(n int) return tt_id_value pipelined 
as
  result tt_id_value;
  
  procedure gen is
  begin
     result:=tt_id_value();
     result.extend(n);
     for i in 1..n loop
        result(i):=to_id_value(i, 1);
     end loop;
  end;    
begin
  gen();
  for i in 1..n loop
    pipe row (result(i));
  end loop;
end;
/
create or replace function f_udf_non_pipe(n int) return tt_id_value 
as
  result tt_id_value;
  
  procedure gen is
  begin
     result:=tt_id_value();
     result.extend(n);
     for i in 1..n loop
        result(i):=to_id_value(i, 1);
     end loop;
  end;    
begin
  gen();
  return result;
end;
/

[collapse]
Test queries

set echo on feed only timing on;
--alter session set optimizer_adaptive_plans=false;
--alter session set "_optimizer_use_feedback"=false;

select sum(id * value) s from table(f_pipe(&1));
select sum(id * value) s from table(f_non_pipe(&1));
select sum(id * value) s from table(f_pipe_for_nonpipe(&1));
select sum(id * value) s from table(f_udf_pipe(&1));
select sum(id * value) s from table(f_udf_non_pipe(&1));
with function f_inline_non_pipe(n int) return tt_id_value 
as
  result tt_id_value;
begin
     result:=tt_id_value();
     result.extend(n);
     for i in 1..n loop
        result(i):=to_id_value(i, 1);
     end loop;
     return result;
end;
select sum(id * value) s from table(f_inline_non_pipe(&1));
/
set timing off echo off feed on;

[collapse]

we’ll find that the function with simple “return result” works at least twice slower than pipelined function:

Function 1 000 000 elements 100 000 elements F_PIPE 2.46 0.20 F_NON_PIPE 4.39 0.44 F_PIPE_FOR_NONPIPE 2.61 0.26 F_UDF_PIPE 2.06 0.20 F_UDF_NON_PIPE 4.46 0.44

I was really surprised that even “COLLECTION ITERATOR PICKLER FETCH” with F_PIPE_FOR_NONPIPE that gets result of F_NON_PIPE and returns it through PIPE ROW() works almost twice faster than F_NON_PIPE, so I decided to analyze it using stapflame by Frits Hoogland.

I added “dbms_lock.sleep(1)” into both of these function after collection generation, to compare the difference only between “pipe row” in loop and “return result”:

Modified functions

create or replace function f_pipe(n int) return tt_id_value pipelined 
as
  result tt_id_value;
  
  procedure gen is
  begin
     result:=tt_id_value();
     result.extend(n);
     for i in 1..n loop
        result(i):=to_id_value(i, 1);
     end loop;
  end;    
begin
  gen();
  dbms_lock.sleep(1);
  for i in 1..n loop
    pipe row (result(i));
  end loop;
end f_pipe;
/
create or replace function f_non_pipe(n int) return tt_id_value 
as
  result tt_id_value;
  
  procedure gen is
  begin
     result:=tt_id_value();
     result.extend(n);
     for i in 1..n loop
        result(i):=to_id_value(i, 1);
     end loop;
  end;    
begin
  gen();
  dbms_lock.sleep(1);
  return result;
end f_non_pipe;
/

[collapse]

And stapflame showed that almost all overhead was consumed by the function “kgmpoa_Assign_Out_Arguments”:

I don’t know what this function is doing exactly, but we can see that oracle assign collection a bit later.
From other functions in this stack(pmucpkl, kopp2isize, kopp2colsize, kopp2atsize(attribute?), kopuadt) I suspect that is some type of preprocessiong of return arguments.
What do you think about it?

Full stapflame output:
stapflame_nonpipe
stapflame_pipe

Categories: Development

Using an "On Field Value Changes" Event in Oracle Visual Builder Cloud Service

Shay Shmeltzer - Mon, 2017-12-11 16:51

This entry is based on previous entries from John and Shray that deal with the same topic and provide the same type of solution. John's entry was created before VBCS provided the UI id for components, and Shray's entry is dealing with a more complex scenario that also involve fetching new data. So I figured I'll write my version here - mostly for my own future reference if I'll need to do this again.

The Goal is to show how you can modify the UI shown in a VBCS page in response to data changes in fields. For example how to hide or show a field based on the value of another field.

To do this, you need to hook into the HTML lifecycle of your VBCS page and subscribe to events in the UI. Then you code the changes you want to happen. Your gateway into manipulating/extending the HTML lifecycle in VBCS is the custom component available in the VBCS component palette. It provides a way to add your own HTML+JavaScript into an existing page.

The video below shows you the process (along with a couple of small mistakes along the route):

The basic steps to follow:

Find out the IDs of the business object field whose value changes you want to listen to. You'll also need to know the IDs of the UI component you want to manipulate - this is shown as the last piece of info in the property inspector when you click on a component. 

Once you have those you'll add a custom component into your page, and look up the observable that relates to the business object used in the page. This can be picked up from the "Generated Page Model (read-only)" section of the custom component and it will look something like : EmpEntityDetailArchetype

Next you are going to add a listener to your custom component model. Add it after the lines 

//the page view model this.pageViewModel = params.root;

your code would look similar to this:

this._listener = this.pageViewModel.Observables.EmpEntityDetailArchetype.item.ref2Job.currentIDSingle.subscribe(function (value) { if (value === "2") { $("#pair-currency-32717").show(); } else { $("#pair-currency-32717").hide(); } }); CustomComponentViewModel.prototype.dispose = function () { this._listener.dispose(); };

Where you will replace the following:

  • EmpEntityDetailArchetype  should be replaced with the observable for your page model.
  • ref2Job  should be replaced with the id of the column in the business object whose value you are monitoring.
  • pair-currency-32717 should be replaced with the id of the UI component you want to modify. (in our case show/hide the component).

You can of course do more than just show/hide a field with this approach.

Categories: Development

SQL*Plus tips #8: How to read the output of dbms_output without “serveroutput on”

XTended Oracle SQL - Sat, 2017-12-09 16:49

When “serveroutput” is enabled, SQL*Plus executes “BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;” after each command.
That’s why I don’t like when it is always enabled: it adds extra calls and round-trips and it is inconvenient when I want to get a plan of the last executed query:

SQL> set serverout on;
SQL> select * from dual;

D
-
X

SQL> select * from table(dbms_xplan.display_cursor('','','allstats last'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------
SQL_ID  9babjv8yq8ru3, child number 0

BEGIN DBMS_OUTPUT.GET_LINES(:LINES, :NUMLINES); END;

NOTE: cannot fetch plan for SQL_ID: 9babjv8yq8ru3, CHILD_NUMBER: 0
      Please verify value of SQL_ID and CHILD_NUMBER;
      It could also be that the plan is no longer in cursor cache (check v$sql_plan)

So usually I switch “serveroutput” on only if needed, but sometimes I can forget to enable it. In such cases I use very simple script that reads the output using dbms_output.get_lines and prints it using refcursor:
https://github.com/xtender/xt_scripts/blob/master/output_print.sql

When you set “serveroutput on“, SQL*Plus also executes “dbms_output.enable” and if you set “serverout off” it executes “dbms_output.disable”, that’s why my glogin.sql contains “call dbms_output.enable(1e6);” and you need to execute it after each “set serverout off” if you want to use this script.

Categories: Development

Bug with integer literals in PL/SQL

XTended Oracle SQL - Fri, 2017-12-08 15:04

This interesting question was posted on our russian forum yesterday:

We have a huge PL/SQL package and this simple function returns wrong result when it’s located at the end of package body:

create or replace package body PKGXXX as
  ...
  function ffff return number is
  nRes number;
  begin        
    nRes :=  268435456;
    return nRes;
  end;
end;
/

But it works fine in any of the following cases:
* replace 268435456 with power(2, 28), or
* replace 268435456 with small literal like 268, or
* move this function to the beginning of package body

The one of the interesting findings was that the returned value is equal to the one of literals in another function.
We can reproduce this bug even with an anonymous pl/sql block. The following test case uses 32768 integer literals from 1000001 to 1032768 and prints 5 other integers:

declare n number;
begin
  n:=1000001; -- this part
  n:=1000002; -- creates
  n:=1000003; -- 32768 
   ...        -- integer
  n:=1032768; -- literals
    dbms_output.put_line('100000='||100000); -- it should print: 100000=100000
    dbms_output.put_line('32766 ='||32766);
    dbms_output.put_line('32767 ='||32767);    
    dbms_output.put_line('32768 ='||32768);
    dbms_output.put_line('32769 ='||32769);
end;
Test code
declare
   c clob:='declare n number;begin'||chr(10);
   f varchar2(100):='n:=%s;'||chr(10);
   v varchar2(32767);
   n number:=32768;
begin
   for i in 1..n loop
      v:=v||utl_lms.format_message(f,to_char(1e7+i));
      if length(v)>30000 then
         c:=c||v;
         v:='';
      end if;
   end loop;
   v:=v||q'[
    dbms_output.put_line('100000='||100000);
    dbms_output.put_line('32766 ='||32766);
    dbms_output.put_line('32767 ='||32767);    
    dbms_output.put_line('32768 ='||32768);
    dbms_output.put_line('32769 ='||32769);
   end;
   ]';
   c:=c||v;
   execute immediate c;
end;
/

[collapse]
It produces the following output:

100000=10000001
32766 =32766
32767 =32767
32768 =10000002
32769 =10000003

This test case well demonstrates wrong results:
* instead of 100000 we get 10000001, which is the value from first line after “begin”, ie 1st integer literal in the code,
* for 32766 and 32767 oracle returns right values
* instead of 32768 (==32767+1) it returns 10000002, which is the integer from 2nd line, ie 2nd integer literal in the code,
* instead of 32769 (==32767+2) it returns 10000003, which is the integer from 3rd line, ie 3rd integer literal in the code
After several tests I can make a conclusion:

  • It doesn’t matter what plsql_optimize_level or plsql_code_type you set, was debug enabled or not, the behaviour is the same.
  • It seems that this is a kind of PL/SQL optimization: during parsing, oracle leaves integer literal in place if its value is in range -32768..32767 (16bit signed int), but if its value is out of this range, oracle adds this value into array of integers’ constants and replaces the value with the index of this element in this array. But because of index value overflow in cases when a count of such integer literals becomes larger than 32768, instead of Nth element of this array, oracle returns Mth element, where M is mod(N,32767).

So we can describe this behaviour using first test case:

declare n number;
begin
  n:=1000001; -- this part
  n:=1000002; -- creates
  n:=1000003; -- 32768 
   ...        -- integer
  n:=1032768; -- literals
    dbms_output.put_line('100000='||100000); -- it should print 100000, ie 32768th element of array, but prints 10000001
                                             -- where 10000001 is the 1st element of array (1==mod(32768,32767))
    dbms_output.put_line('32766 ='||32766);  -- these 2 lines print right values,
    dbms_output.put_line('32767 ='||32767);  -- because their values are in the range of -32768..32767
    dbms_output.put_line('32768 ='||32768);  -- this line contains 32769th element and prints 2nd element of array (2==mod(32769,32767))
    dbms_output.put_line('32769 ='||32769);  -- this line contains 32770th element and prints 3nd element of array (3==mod(32770,32767))
end;

The following query can help you to find objects which can potentially have this problem:

select
  s.owner,s.name,s.type
 ,sum(regexp_count(text,'(\W|^)3\d{4,}([^.0-9]|$)')) nums_count -- this regexp counts integer literals >= 30000
from dba_source s 
where 
    owner='&owner'
and type in ('FUNCTION','PROCEDURE','PACKAGE','PACKAGE BODY')
group by s.owner,s.name,s.type
having sum(regexp_count(text,'(\W|^)3\d{4,}([^.0-9]|$)'))>32767 -- filter only objects which have >=32767 integer literal

Workaround:
You may noticed that I wrote about INTEGER literals only, so the easiest workaround is to make them FLOAT – just add “.” to the end of each literal:

declare n number;
begin
  n:=1000001.;
  n:=1000002.;
  n:=1000003.;
   ...       
  n:=1032768.;
    dbms_output.put_line('100000='||100000.);
    dbms_output.put_line('32766 ='||32766.);
    dbms_output.put_line('32767 ='||32767.);    
    dbms_output.put_line('32768 ='||32768.);
    dbms_output.put_line('32769 ='||32769.);
end;
Fixed test cases

declare
   c clob:='declare n number;begin'||chr(10);
   f varchar2(100):='n:=%s.;'||chr(10); -- I've added here "."
   v varchar2(32767);
   n number:=32768;
begin
   for i in 1..n loop
      v:=v||utl_lms.format_message(f,to_char(1e7+i));
      if length(v)>30000 then
         c:=c||v;
         v:='';
      end if;
   end loop;
   v:=v||q'[
    dbms_output.put_line('100000='||100000.); -- .
    dbms_output.put_line('32766 ='||32766.);
    dbms_output.put_line('32767 ='||32767.);    
    dbms_output.put_line('32768 ='||32768.);
    dbms_output.put_line('32769 ='||32769.);
   end;
   ]';
   c:=c||v;
   execute immediate c;
end;
/

[collapse]
Categories: Development

Reverse engineer existing Oracle tables to Quick SQL

Dimitri Gielis - Fri, 2017-12-08 13:21
If you didn't hear about Oracle Quick SQL, it's time to read about it as it's something you have without knowing (it's a packaged app in Oracle APEX) and I believe you should start using :)

Quick SQL enables you to rapidly design and prototype data models using a markdown-like shorthand syntax that expands to standards-based Oracle SQL. You can easily create master detail relationships, check constraints, and even generate sample data.
In my blog post Create the Oracle database objects I go over the history how I created database objects and why I think Quick SQL is great and why I use it.

I guess most people typically use Quick SQL at the start of a new project, as it's the quickest way to create your data model and Oracle database objects. That is my primary use case too, but I started to use Quick SQL even on projects where database objects already exist.

In the project I'm currently involved in, the datamodel was generated by another tool, but as we iterate through the project, tables change, columns get renamed and added, row version were requested, triggers need to be made Oracle APEX aware...

Now we could do those changes manually, but I thought it made much more sense to create the data model in Quick SQL and use the features that come with Quick SQL. By clicking a checkbox we can include a Row version, Quick SQL generates the triggers automatically in an APEX aware form, we can generate as much sample data as we want by adding /insert and we can use all the other features that come with Quick SQL. For example when you want to include a history table in the future it's just another checkbox to click.


It's also easy to check-in the Quick SQL script into source control, together with the generated DDL.
If changes need to be done, we can adapt in Quick SQL and generate the DDL again and we see the changes immediately. It would be nice if Quick SQL could generate the ALTER statements too, but that's not the case yet. But it's easy enough to see the changes that were done by comparing the scripts in source control.

If you also want to reverse engineer an existing model into Quick SQL, here's a script that gives you a head start generating the markdown style format.


I tried the script on the Quick SQL data model itself - the result you see below:


Hopefully you see the benefit of using Quick SQL in existing projects too and the script helps you get there. Also Quick SQL gets frequent updates - in the upcoming release (17.3.4), which is already online, you can add a Security Group ID to every table (to make your app multi-tenant) and you can rename the audit columns to your own naming conventions.
Categories: Development

Oracle issues after upgrade to 12.2

XTended Oracle SQL - Thu, 2017-11-23 16:18

Sometimes it’s really hard even to create reproducible test case to send it to oracle support, especially in case of intermittent errors.
In such cases, I think it would be really great to have access to similar service requests or bugs of other oracle clients.
So while my poll about knowledge sharing is still active, I want to share a couple of bugs we have faced after upgrade to 12.2 (and one bug from Eric van Roon). I’m going to remove the bugs from this list when they become “public” or “fixed”.
If you want to add own findings into this list, you can add them into comments. To make this process easier, you can provide just symptomps, short description and the link to own post with details – I’ll add it just as a link.

th.c_symptomps { min-width:100px; max-width:100px; } th.c_description { min-width:200px } .c_links { min-width:150px; max-width:220px; } .c_links ul { margin: 0 0 5px 0 !important; -webkit-padding-start: 5px; } .c_links ul li { margin-left: 0px; -webkit-padding-start: 0px; } td.c_symptomps { font-size:12px;} td.c_description { font-size:12px;} td.c_links { font-size:10px;} .c_body td { vertical-align: text-top; } div.hints_wrapper { border-style: solid; border-width: 1px; padding: 2px; overflow: scroll !important; } div.hints_content { width: 1175px; min-width:1175px; padding: 2px; }


Symptomps Description Links Intermittent ORA-01483 After upgrade to 12.2 we started getting “ORA-01483: invalid length for DATE or NUMBER bind variable” in different applications using different oracle drivers.
Interestingly, that after reconnect, Oracle processes the same statement with same bind variables successfully.
Looking into errorstack dump (alter system set events ‘1483 trace name errorstack level 3, lifetime 5’;) we have found that oracle mixed up all values.
The only similar bug we found in MOS was “OCI Application Fails With ORA-01483/ORA-01461 When Inserting VARCHAR2 Field From 12.2 Database Using Database Link To Lower Database Version. (Doc ID 2309285.1)”, but it shows different symptomps.
Nevertheless, we have tried second workaround from this doc and it helped us.

Workaround:
set “_qkslvc_extended_bind_sz” to 0 and bounce the database. Doc ID 2309285.1 Periodically never ending SQL_ID f1xfww55nj0xp with SYNC(on commit) and SMALL_R_ROW enabled It seems that sometimes ctxsys.syncrn(:idxownid, :idxoname, :idxid, :ixpid, :rtabnm, :flg, :smallr)
falls into infinite loop when you have ctx domain text indexes with SYNC(on commit) and SMALL_R_ROW option enabled

Workaround:
Recreate indexes as TRANSACTIONAL Near real-time indexes with SYNC(every…) option. ORA-07445: exception encountered: core dump [keswxCurNbRows()+61] with CURSOR() and DBMS_XMLGEN.getXML() Workaround:
use the following parameters:

  • “_optimizer_use_feedback”=false
  • “_optimizer_gather_feedback”=false
  • “_optimizer_dsdir_usage_control”=0
  • “_iut_enable”=false
“Non-public” Bug 26696342 ORA-01722 on quering ctx_preference_values After upgrade to Oracle 12.2.0.1 simple query “select * from ctx_preference_values” returns error ORA-01722: invalid number

details

If we look into the source of this view ctx_preference_values:

create or replace view ctxsys.ctx_preference_values as 
select /*+ ORDERED INDEX(dr$preference_value) */ 
u.name prv_owner 
,pre_name prv_preference 
,oat_name prv_attribute 
,decode(oat_datatype, 'B', decode(prv_value, 1, 'YES', 'NO'), 
nvl(oal_label, prv_value)) prv_value 
from 
sys."_BASE_USER" u 
,dr$preference 
,dr$preference_value 
,dr$object_attribute 
,dr$object_attribute_lov 
where prv_value = nvl(oal_value, prv_value) 
and oat_id = oal_oat_id (+) 
and oat_id = prv_oat_id 
and prv_pre_id = pre_id 
and pre_owner# = u.user#; 

and check PRV_VALUE from ctxsys.dr$preference_value:

select pre_name, prv_pre_id,prv_value 
from ctxsys.dr$preference 
, ctxsys.dr$preference_value 
, ctxsys.dr$object_attribute 
where prv_pre_id = pre_id 
and oat_id = prv_oat_id 
and oat_datatype='B' 
and not regexp_like(prv_value,'^\d*$') 
/ 
PRE_NAME                          PRV_PRE_ID PRV_VALUE
--------------------------------- ---------- ----------
CTXSYS.JSONREST_GERMAN_LEXER            1098 YES
CTXSYS.JSONREST_GERMAN_DIN_LEXER        1104 YES

we can find that the root cause of the problem is the line with decode(prv_value, 1, ‘YES’, ‘NO’)
and values ‘YES’ for preferences ‘CTXSYS.JSONREST_GERMAN_LEXER’, ‘CTXSYS.JSONREST_GERMAN_DIN_LEXER’.

Oracle tries to compare ‘YES’ with 1 in decode() and raises ORA-01722.

[collapse]

Workaround:

To add predicates to avoid these 2 preferences:

select * from ctx_preference_values
where prv_preference not in (‘CTXSYS.JSONREST_GERMAN_LEXER’,’CTXSYS.JSONREST_GERMAN_DIN_LEXER’); Wrong results with DETERMINISTIC functions in subquery factoring clause Description from Eric van Roon
Workaround:
alter session set “_plsql_cache_enable”=false; Examples

Categories: Development

Scaling Oracle using NVMe flash

Gerger Consulting - Tue, 2017-11-21 07:37
Attend the free webinar by storage expert Venky Nagapudi and learn how to improve the performance of your Oracle Database using new storage technologies such as NVMe flash. 

About the Webinar
Growth in users and data put an ever-increasing strain on transactional and analytics platforms. With many options available to scale platforms, what are the considerations and what are others choosing? Vexata’s VP of Product Management, Venky Nagapudi covers how the latest in storage side technologies, like NVMe flash, can deliver both vast improvements in performance as well as drive down costs and complexity of platforms. He will also cover key use cases where storage-side solutions delivered amazing results for Vexata’s customers.
In this webinar, you will:
  • Hear real-world performance scaling use cases.
  • Review the pros & cons of common scaling options.
  • See specific results of choosing a storage-side solution.


About the Presenter


Venky Nagapudi has 20 years experience in engineering and product management in the storage, networking and computer industries. Venky led product management at EMC and Applied Microsystems. Venky also held engineering leadership roles at Intel, Brocade and holds 10 patents with an MBA from Haas business school at UC Berkeley, an MSEE from North Carolina State University, and a BSEE from IIT Madras.

Sign up now.

Categories: Development

Triggers and Redo: changes on 12.2

XTended Oracle SQL - Sun, 2017-11-19 09:47

In one of the previous posts I showed How even empty trigger increases redo generation, but running the test from that post, I have found that this behaviour a bit changed on 12.2:
In my old test case, values of column A were equal to values of B, and on previous oracle versions including 12.1.0.2 we can see that even though “update … set B=A” doesn’t change really “B”, even empty trigger greatly increases redo generation.
But on 12.2.0.1 in case of equal values, the trigger doesn’t increase redo, so we can see small optimization here, though in case of different values, the trigger still increases reado generation greatly.

same_dumpredo.sql
set feed on;
drop table xt_curr1 purge;
drop table xt_curr2 purge;
-- simple table:
create table xt_curr1 as select '2' a, '2' b from dual connect by level<=1000;
-- same table but with empty trigger:
create table xt_curr2 as select '2' a, '2' b from dual connect by level<=1000;
create or replace trigger tr_xt_curr2 before update on xt_curr2 for each row
begin
  null;
end;
/
-- objectID and SCN:
col obj1 new_val obj1;
col obj2 new_val obj2;
col scn  new_val scn;
select 
  (select o.OBJECT_ID from user_objects o where o.object_name='XT_CURR1') obj1
 ,(select o.OBJECT_ID from user_objects o where o.object_name='XT_CURR2') obj2
 ,d.CURRENT_SCN scn
from v$database d
/
-- logfile1:
alter system switch logfile;
col member new_val logfile;
SELECT member
FROM v$logfile
WHERE 
     is_recovery_dest_file='NO'
 and group#=(SELECT group# FROM v$log WHERE status = 'CURRENT')
 and rownum=1;
-- update1:
set autot trace stat;
update xt_curr1 set b=a;
set autot off;
commit;
-- dump logfile1:
alter session set tracefile_identifier='log1_same';
ALTER SYSTEM DUMP LOGFILE '&logfile' SCN MIN &scn OBJNO &obj1;

-- logfile2:
alter system switch logfile;
col member new_val logfile;
SELECT member
FROM v$logfile
WHERE 
     is_recovery_dest_file='NO'
 and group#=(SELECT group# FROM v$log WHERE status = 'CURRENT')
 and rownum=1;
-- update2:
set autot trace stat;
update xt_curr2 set b=a;
set autot off;
commit;
-- dump logfile2:
alter session set tracefile_identifier='log2_same';
ALTER SYSTEM DUMP LOGFILE '&logfile' OBJNO &obj2;
alter session set tracefile_identifier='off';
disc;

[collapse]

diff_dumpredo.sql

set feed on;
drop table xt_curr1 purge;
drop table xt_curr2 purge;
-- simple table:
create table xt_curr1 as select '1' a, '2' b from dual connect by level<=1000;
-- same table but with empty trigger:
create table xt_curr2 as select '1' a, '2' b from dual connect by level<=1000;
create or replace trigger tr_xt_curr2 before update on xt_curr2 for each row
begin
  null;
end;
/
-- objectID and SCN:
col obj1 new_val obj1;
col obj2 new_val obj2;
col scn  new_val scn;
select 
  (select o.OBJECT_ID from user_objects o where o.object_name='XT_CURR1') obj1
 ,(select o.OBJECT_ID from user_objects o where o.object_name='XT_CURR2') obj2
 ,d.CURRENT_SCN scn
from v$database d
/
-- logfile1:
alter system switch logfile;
col member new_val logfile;
SELECT member
FROM v$logfile
WHERE 
     is_recovery_dest_file='NO'
 and group#=(SELECT group# FROM v$log WHERE status = 'CURRENT')
 and rownum=1;
-- update1:
set autot trace stat;
update xt_curr1 set b=a;
set autot off;
commit;
-- dump logfile1:
alter session set tracefile_identifier='log1_diff';
ALTER SYSTEM DUMP LOGFILE '&logfile' SCN MIN &scn OBJNO &obj1;

-- logfile2:
alter system switch logfile;
col member new_val logfile;
SELECT member
FROM v$logfile
WHERE 
     is_recovery_dest_file='NO'
 and group#=(SELECT group# FROM v$log WHERE status = 'CURRENT')
 and rownum=1;
-- update2:
set autot trace stat;
update xt_curr2 set b=a;
set autot off;
commit;
-- dump logfile2:
alter session set tracefile_identifier='log2_diff';
ALTER SYSTEM DUMP LOGFILE '&logfile' OBJNO &obj2;
alter session set tracefile_identifier='off';
disc;

[collapse]

Equal values:
12.1.0.2:

12.2.0.1:

Different values:
12.1.0.2:

12.2.0.1:

We can easily find that trigger disables batched “Array update”:

Categories: Development

Date Calculations and Queries with Oracle Visual Builder Cloud Service

Shay Shmeltzer - Fri, 2017-11-17 12:10

It's very easy to define a field in a custom object in Oracle Visual Builder Cloud Service to store a date, but when it comes to doing calculations and queries based on this date you'll find that you need to resort to a little bit of JavaScript calculations.

Here are a couple of useful things to know if you are trying to do that.

Calculating Age (or time passed from a date in years)

Let's assume you are storing information about employees and one of the pieces of information you have is their date of birth - the Birthday field in the image below.

How do you show their actual age in years on a page?

You can define a calculated field in your business object - and have VBCS use the "calculate value with formula" as the source for this field.

Your formula would be something like:

(new Date() -new Date($birthdate) )/ (60*60*24*1000*365)

You are calculating the difference between today's date and the birthday field and since the answer is in milliseconds you convert it to years by dividing by the number of milliseconds in a year.

Note that as you type in your formula the dialog shows you the results of the formula below the formula field - quite useful to verify that you are doing it right.

Now your page can show the age of your employees:

Filtering Based on Date

What if you wanted to limit the records shown in the table above to only show employees of a specific age?

The tricky part is that you'll need to do the calculation against the birthday field and not against the age field. The age field is not actually stored anywhere - rather it is calculated on the fly.

Let's take the table shown above and assume we want to limit it to show employees who are younger than 9 years. To do that we'll add a query condition to our table to check that the birthday is larger than the date of (today - 9 years).

The calculation of the date 9 years ago will be with a formula like this:

new Date($current_date-9*365*24*60*60*1000)

Now your table only shows older employees.

Want to have a more dynamic way to define the query criteria - you can adopt the approach I showed in the blog about Creating Custom Search/Query Pages with Visual Builder along with the techniques shown here.

One last note - since not every year has 365 days - the calculation for milliseconds conversion is not completely accurate - but it is quite close.

Categories: Development

Easy(lazy) way to check which programs have properly configured FetchSize

XTended Oracle SQL - Wed, 2017-11-15 15:37
select 
   s.module
  ,ceil(max(s.rows_processed/s.fetches)) rows_per_fetch
from v$sql s
where 
    s.rows_processed>100
and s.executions    >1
and s.fetches       >1
and s.module is not null
and s.command_type  = 3    -- SELECTs only
and s.program_id    = 0    -- do not account recursive queries from stored procs
and s.parsing_schema_id!=0 -- <> SYS
group by s.module
order by rows_per_fetch desc nulls last
/
Categories: Development

Conditional Navigation based on Queries in Oracle Visual Builder Cloud Service

Shay Shmeltzer - Wed, 2017-11-15 13:21

A couple of threads on the Oracle Visual Builder Cloud Service forum asked about writing code in buttons in VBCS that compares values entered in a page to data in business objects and perform conditional navigation based on the values. In a past blog I showed the code needed for querying VBCS objects from the UI, but another sample never hurts, so here is another demo...

For this demo I'm going to show how to do it in a login flow - assuming you have a business object that keeps usernames and passwords, and you want to develop a page where a user types a user/pass combination and you need to verify that this is indeed a valid combination that exist in the business object.

(In reality, if you want to do user authentication in VBCS - you should use the built in security frameworks and not code it this way. I'm just using this as an example.)

Here is a quick video of the working app - with pointers to the components detailed below.

The first thing you'll do is create the business object that hosts the user/pass combination - note that in the video since "user" is a reserved word - the ID for the field is actually "user_" - which is what we'll use in our code later on.

 

Next you'll want to create a new page where people can insert a user/pass combination - to do that create a new page of type "Create" - this page will require you to associate it with a business object, so create a new business object. We won't actually keep data in this new business object. In the video and the code - this business object is called "query".

Now design your page and add the user and pass fields - creating parallel fields in the query business object (quser and qpass in the video). You can then remove the "Save" button that won't be use, and instead add a "validate" button.

For this new button we'll define a new custom action that will contain custom JavaScript code. Custom code should return either a success state - using resolve(); - or failure - using reject();

Based on the success or failure you can define the next action in the flow - in our case we are showing either a success or error message:

success flow

Now lets look at the custom JavaScript code:

require(['operation/js/api/Conditions', 'operation/js/api/Operator'], function (Conditions, Operator) { var eo = Abcs.Entities().findById('Users'); var passid = eo.getProperty('pass'); var userid = eo.getProperty('user_'); var condition = Conditions.AND( Conditions.SIMPLE(passid, Operator.EQUALS,$QueryEntityDetailArchetypeRecord.getValue('qpass') ), Conditions.SIMPLE(userid, Operator.EQUALS, $QueryEntityDetailArchetypeRecord.getValue('quser')) ); var operation = Abcs.Operations().read( { entity : eo, condition : condition }); operation.perform().then(function (operationResult) { if (operationResult.isSuccess()) { operationResult.getData().forEach(function (oneRecord) { resolve("ok"); }); } reject("none"); } ). catch (function (operationResult) { if (operationResult.isFailure()) { // Insert code you want to perform if fetching of records failed alert('didnt worked'); reject("error"); } }); });

Explaining the code:

  • Lines 2-4 - getting the pointers to the business object and the fields in it using their field id.
  • Lines 5-8 - defining a condition with AND - referencing the values of the fields on the page
  • Lins 9-11 - defining the operation to read data with the condition from the business object
  • Line 12 - executing the read operation
  • Line 14-18 - checking if a record has been returned and if it has then we are ok to return success - there was a user/pass combination matching the condition.
  • Line 19 - otherwise we return with a failure.

One recommendation, while coding JavaScript - use a good code editor that will help highlight open/close brackets matches - it would save you a lot of time.

For more on the VBCS JavaScript API that you can use for accessing business components see the doc.

Categories: Development

Meet me in Australia and New Zealand at the OTN Days 2017

Dimitri Gielis - Tue, 2017-11-14 08:32
Tonight I'll start my trip from Belgium to Australia and New Zealand. Although we have a company in New Zealand and Australia, which Lino is managing, I've never been there myself. It has always been my dream to visit the other side of the earth, so I look forward to it :)

I'll present on how I build Oracle APEX apps today (and in the future) and how to make them available for others (cloud and others).

My schedule of the OTN Days 2017 (APAC Tour) looks like this:
If you are in one of those places, I would love to meet you and hear how you use Oracle APEX.
And I'm always up for showing you a live demo of APEX Office Print, you'll see our upcoming AOP 3.2 version as first! Just grab me by my arm and ask :)

In Perth there will also be a Q&A slot - so any Oracle APEX question can be asked there.
Categories: Development

Karamozov

Greg Pavlik - Fri, 2017-11-10 12:02
"Brothers, have no fear of men's sin. Love a man even in his sin, for that is the semblance of Divine Love and is the highest love on earth. Love all God's creation, the whole and every grain of sand in it. Love every leaf, every ray of God's light. Love the animals, love the plants, love everything. If you love everything, you will perceive the divine mystery in things. Once you perceive it, you will begin to comprehend it better every day. And you will come at last to love the whole world with an all-embracing love. Love the animals: God has given them the rudiments of thought and joy untroubled. Do not trouble it, don't harass them, don't deprive them of their happiness, don't work against God's intent. Man, do not pride yourself on superiority to the animals; they are without sin, and you, with your greatness, defile the earth by your appearance on it, and leave the traces of your foulness after you -- alas, it is true of almost every one of us! Love children especially, for they too are sinless like the angels; they live to soften and purify our hearts and, as it were, to guide us. Woe to him who offends a child! Father Anfim taught me to love children. The kind, silent man used often on our wanderings to spend the farthings given us on sweets and cakes for the children. He could not pass by a child without emotion. That's the nature of the man.

At some thoughts one stands perplexed, especially at the sight of men's sin, and wonders whether one should use force or humble love. Always decide to use humble love. If you resolve on that once for all, you may subdue the whole world. Loving humility is marvellously strong, the strongest of all things, and there is nothing else like it....

Brothers, love is a teacher; but one must know how to acquire it, for it is hard to acquire, it is dearly bought, it is won slowly by long labour. For we must love not only occasionally, for a moment, but for ever. Everyone can love occasionally, even the wicked can.

My brother asked the birds to forgive him; that sounds senseless, but it is right; for all is like an ocean, all is flowing and blending; a touch in one place sets up movement at the other end of the earth. It may be senseless to beg forgiveness of the birds, but birds would be happier at your side -- a little happier, anyway -- and children and all animals, if you were nobler than you are now. It's all like an ocean, I tell you. Then you would pray to the birds too, consumed by an all-embracing love, in a sort of transport, and pray that they too will forgive you your sin. Treasure this ecstasy, however senseless it may seem to men."

Introduction to Oracle Developer Cloud Service Issue Tracking REST Interfaces

Shay Shmeltzer - Mon, 2017-11-06 13:38

The task tracking system in Oracle Developer Cloud Service (DevCS) helps your team manage your development priorities and process. DevCS offers a simple web interface for working with the system. However, in some cases you might want to build your own interfaces to interact with the issues. For example, you might want to build a system for end-users to report bugs in your app and you don't want to give them direct access to the DevCS web insterface. In the August 17 update of DevCS  we introduced a set of REST services that will let you build a custom interface that will interact with our issues repository.

The official documentation for the DevCS REST services is here.

I wanted to share some tips to help you get this going in your project. The results are in this short video demo, and the details are below.

Figuring Out The End Points

The documentation gives you the basic end-points you should be calling, but it took me a little bit of time to figure out the full URL to the end point. Turns out the URL is composed in the following way:

https://server/org-id/rest/org-id+project-id/issues/v2/issues

The first parts (server/org-id) are quite easy to get - just copy it from the URL of your project when you look at it in your browser.

The org-id+project-id part is something you can get by looking at the details of your maven repository URL - see the image below - what you are looking for is the part before the /maven/ at the end:

Note that in some projects this will also include a numeric value appended to the project name. Something like developer-oracletemplates_db-oss-devops_20266.

In the video sample below the result URL for the REST that returns the list of issues currently in the system ended up being:

https://myserver/developer-oracletemplates/rest/developer-oracletemplates_adf1221/issues/v2/issues

Creating New Issues

One of the useful services is the /issues/v2/issues/create-form service. It returns a json file that you can edit to specify information about a new task that you want to create.

Note that the file start with : {"createIssue":{"links":.... Before you use the file to insert a new issue, you'll need to remove the  {"createIssue": at the start and the corresponding } at the end of the file. Only then can you use it to submit the POST operation to create an issue.

In the video I used the following command to create the issue in the DevCS:

curl -X POST -u shay@oracle.com https://myserver/developer-oracletemplates/rest/developer-oracletemplates_adf1221/issues/v2/issues/ -d@issue.json -H 'Content-type:application/json'

(the -d allows you to specify the name of the file with the new issue, and the -H specifies the content format).

Now that you have access to the information you can create new systems on top of it using your favorite development tool. At the end of the video you can see a simple issue system I built with Oracle Visual Builder Cloud Service - more on that in a future blog entry.

 

Categories: Development

Exporting and Importing Data from Visual Builder Cloud Service - with REST Calls

Shay Shmeltzer - Thu, 2017-11-02 16:37

Visual Builder Cloud Service (VBCS) makes it very easy to create custom objects to store your data. A frequent request we get is for a way to load and export data from these business objects. As John blogged, we added a feature to support doing this through the command line - John's blog shows you the basic options for the command line.

I recently needed to do this for a customer, and thought I'll share some tips that helped me get the functionality working properly - in case others need some help skipping bumps in the road.

Here is a demo showing both import and export and how to get them to work.

Exporting Data

Export is quite simple - you use a GET operation on a REST service, the command line for calling this using curl will look like this:

curl -u user:password https://yourserver/design/ExpImp/1.0/resources/datamgr/export > exp.zip

The result is a streaming of a zip file, so I just added a > exp.zip file to the command's end. The zip file will contain CSV files for each object in your application.

Don't forget to replace the bold things with your values for username and password, your VBCS server name and the name of the app you are using (ExpImp in my case).

Importing Data

Having the exported CSV file makes it easy to build a CSV file for upload - in the demo I just replaced and added values in that file. Next you'll use a similar curl command to call a POST method. It will look like this:

curl -X POST -u user:password https://yourserver/design/ExpImp/1.0/resources/datamgr/import/Employee?filename=Employee.csv -H "Origin:https://yourserver" -H "Content-Type:text/csv" -T Employee.csv -v

A few things to note.

You need to specify which object you want to import into (Employee after the /import/ in the command above), and you also need to provide a filename parameter that tell VBCS which file to import.

In the current release you need to work around a CORS security limitation - this is why we are adding a header (with the -H option) that indicate that we are sending this from the same server as the one we are running on. In an upcoming version this won't be needed.

We use the -T option to attach the csv file to our call.

Note that you should enable the "Enable basic authentication for business object REST APIs" security option for the application (Under Application Settings->Security). 

Using Import in Production Apps

In the samples above we imported and exported into an application that is still being developed - this is why we used the /design/ in our REST path.

If you want to execute things on an application that you published then replace the /design/ with /deployment/ 

One special note about live applications, before you import data into them you'll need to lock them. You can do this from the home page of VBCS and the drop down menu on the application.

 

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 11.0px Menlo; color: #000000; background-color: #ffffff} span.s1 {font-variant-ligatures: no-common-ligatures}
Categories: Development

Using Flyway to Manage Oracle DB Versions in the Cloud

Shay Shmeltzer - Thu, 2017-10-19 13:11

This is another entry in my series about managing database scripts/schema as part of agile development. In the past I showed how to use simple SQL and Liquibase to manage schema creation/population scripts, and today I'll show you how to use Flyway.

Flyway log

Flyway is a free open source solution for managing "database migrations" - or basically helping you keep multiple database in synch by tracking and applying changes to the schema structure and data.

Flyway uses simple SQL scripts - which means you can use DB specific syntax - and tracks their execution in the database through a table it maintains. It is very easy to get started with and only has 6 commands that you need to be familiar with.

The main command is "migrate" which will check your database status, and then run all the newer scripts that have yet to be run on that instance.

Flyway uses a directory structure that contains a sql folder where you'll host all your SQL scripts. It uses a naming convention (that can be adjusted) where you start the file name with a Version number (V1, V1.1, V2.1) and then two "_" followed by a description - so something like V1__Create_Emp_Table - will show up as "Create Emp Table" when you issue the "info" command to find out what is the status of a database and which scripts have already run. By the way, the info command will also show you which new scripts are pending to be run on a specific database instance.

In the video below I show how to configure and use Flyway, and how to integrate it into an automatic DevOps process leveraging Oracle Developer Cloud Service. (including task tracking, Git version management of the source, and build execution of the scripts).

Flyway can integrate with various build framework (ant, maven, gradle etc), but since many DB folks are not familiar with those, I chose to use simple command lines in my demo to invoke Flyway. On my laptop and local MySQL DB I just used the Flyway command line utility. However Flyway is not installed by default in the DevCS servers, so I did a little trick:

Flyway is a Java program, so into my DevCS Git repository I uploaded the Flyway directory along with needed jars for flyway and the JDBC driver. Then I looked at the script for invoking the command line and found out the Java command they used and copied it into a regular shell command in my build:

java -cp lib/flyway-commandline-4.2.0.jar:lib/flyway-core-4.2.0.jar org.flywaydb.commandline.Main info -user=fw -password=$Password -url=jdbc:oracle:thin:@ipaddress:1521/servicename

The $Password refers to a build parameter which is encrypted.

The directory structure and files in my Git are shown in this image:

directory structure

 

Categories: Development

Your DBA Career in the Age of Oracle Cloud

Gerger Consulting - Wed, 2017-10-18 11:48
Attend the free webinar by Oracle ACE Director Craig Shallahamer and learn how the Oracle Cloud and the Oracle 18c autonomous database changes your role as an Oracle DBA.
About the Webinar
The cloud is a change that all Oracle DBAs must face. The cloud is here stay, and that means Oracle DBAs need to adapt or get out of the game. It makes no difference if you are a new Oracle DBA or retiring in five years, before us is one of the most significant changes you will ever face.

In this webinar, you'll learn what has happened, what is happening, what you can expect, and what you can do today to ensure you are positioned to thrive in a cloud world full of surprising and exciting opportunities.

Register at this link.
Categories: Development

Introduction to Liquibase and Managing Your Database Source Code

Shay Shmeltzer - Mon, 2017-10-16 10:35

In previous posts I showed how you can manage SQL scripts lifecycle with the help of Oracle Developer Cloud Service (DevCS) as part of an overall Oracle DB DevOps solution. I wanted to add one more utility that might act as an alternative or addition to the SQL script managing - Liquibase.

Liquibase logo

Liquibase is an open source solution for managing revisions of your databse schema scripts. It works across various types of databases, and supports various file formats for defining the DB structure. The feature that is probably most attractive in Liquibase is its ability to roll changes back and forward from a specific point - saving you from the need to know what was the last change/script you ran on a specific DB instance.

Liquibase uses scripts - referred to as "changesets" - to manage the changes you do to your DB. The changesets files can be in various formats including XML, JSON, YAML, and SQL. In the examples below I'm using the XML format.

As you continue to change an enhance your DB structure through the development lifecycle you'll add more changesets. A master file lists all the changeset files (or the directories where they are). In parallel Liquibase tracks in your database which changesets have already run. 

When you issue a liquibase update command, liquibase looks at the current state of your DB, and identifies which changes have already happened. Then it run the rest of the changes - getting you to the latest revision of the structure you are defining.

By integrating Liquibase into your overall code version management system and continuous integration platform you can synch up your database versions with your app version. In my case this would of course mean integration with Oracle Developer Cloud Service (DevCS) - which you get for free with the Oracle Database Cloud Service. In the video below I show a flow that covers:

  • Tracking my DBA tasks in the issue system
  • Modifying a local MySQL DB with Liquibase (doing forward and backward rolls)
  • Adding a change set defining a new table
  • Committing to Git
  • Automatic build implementing the changes in Oracle Database Cloud Service
  • Automatic testing with UT/PLSQL

Here is a quick 10 minute demo:

For those who want to try and replicate this, here are some resources:

A changeset that creates a "department" table with three columns:

A changeset that creates PL/SQL function, package and procedure. Note that in line 3 the dbms="oracle" means this script will only run when we are connected to an Oracle DB:

create or replace function betwnstr( a_string varchar2, a_start_pos integer, a_end_pos integer ) return varchar2 is begin return substr( a_string, a_start_pos, a_end_pos - a_start_pos+1 ); end; create or replace package test_betwnstr as -- %suite(Between string function) -- %test(Returns substring from start position to end position) procedure basic_usage; end; create or replace package body test_betwnstr as procedure basic_usage is begin ut.expect( betwnstr( '1234567', 2, 5 ) ).to_equal('2345'); end; end; A changeset that adds a record to a table. Line 8 has the rollback tag that defines how to do a rollback for this insert: delete from department where id=20

 

A few tips about my DevCS project and build setup.

1. For the sake of simplicity, I loaded the liquibase and JDBC jar files into my git repository - this makes it easy for my build steps to find the files and execute them. I'm guessing you could also use Maven to host those.

2. I use a password parameter for my build so I don't need to hardcode the password adding a bit of security to my build. Reference teh parameter in your build with a $ sign - $password

3. Want to learn more about test automation with ut/PLSQL - check out this blog entry.

 

 

Categories: Development

APEX Office Print 3.1 released - support for Docker

Dimitri Gielis - Mon, 2017-10-16 04:18
Last week we release APEX Office Print (AOP) 3.1, our best release ever :)

AOP was already the easiest and most fully integrated printing and exporting solution for Oracle APEX, but with every new release we allow you to customise the way you use AOP a bit more and add more advanced functionalities.

As more and more bigger companies are using AOP, we focussed in this release more on enterprise features, for example, native HTTPS support, end-to-end and customisable debugging, a new queuing system for large amounts of prints and overall performance enhancements and general improvements.
You can read more about this release in our release history.

One other addition I want to highlight is the ability to run AOP in a Docker configuration.
The Docker image is available for our Gold and Enterprise license.

Docker is the world’s leading software container platform. If this concept is new for you, you can read more at What is Docker?



In the previous days Martin Giffy D'Souza blogged about How to Setup Oracle DB 12.2 Docker Container and Docker Oracle and APEX and Roel Hartman talked about Dockerize your APEX development environment. Those are some excellent posts how to get started with Docker in an Oracle Database and APEX context.

The most important reason for us to make an APEX Office Print docker image available was to ease the installation of multiple AOP instances even more and give the possibility to scale AOP in an enterprise way.

Here's a video how you are up and running with our AOP docker image in less than a minute:



You also find the detailed steps in the AOP documentation.

Juergen Schuster and Martin Giffy D'Souza did a podcast with me end of August, where I talk a bit about AOP and our development too.

If you are not yet on APEX Office Print 3.1, go and download the latest version, even when you are not enterprise, it's worthwhile the upgrade. We updated our AOP Sample Application with some new examples too.


Happy printing and exporting from Oracle APEX with AOP :)

Categories: Development

Talking about APEX Reporting and AOP @ Montreal Oracle Dev Day 2017

Dimitri Gielis - Wed, 2017-10-11 01:00
For those in Montreal and the surrounding area I encourage you to come out to the Montreal Oracle Dev Day on October 25th (8:30-4:30 at Centre for Sustainable Development).

Here’s a summary agenda of the presentations with the full agenda here:
Aside from the presentations you will have plenty of opportunity to network and share your Oracle development experiences. All speakers will be available all day so feel free to bring your APEX questions!

You can register now online.

As I'm not that much in this part of the world it would be great to meet in person. I would love to hear your thoughts on APEX Office Print (AOP) too.  If you have any questions, feedback or just want to talk how to use AOP in your environment, don't hesitate to come up to me. I'm more than happy to talk to you :)

Categories: Development

Pages

Subscribe to Oracle FAQ aggregator - Development