Tuesday, June 12, 2012

A Creative Use of Lookups in EBS 12



Background:
I recently worked on a SOA Middleware project that interfaced our EBS item-related data (including inventory levels) with various third-party websites. Since it was the same inventory being sent to multiple websites, they wanted a way to manage (throttle back) available inventory sent to each site.

Previously, everything was coded using a combination of C#.NET and PL/SQL and any change or adjustment required IT intervention since everything was hard-coded. I was asked to come up with a way to use Standard EBS screens to allow the Users to manage these inventory feeds.


In the following example, I will demonstrate how to use standard lookup functionality within EBS to derive a set of Trading Partner (TP) specific rules to allow the Business Units to manage how inventory is sent at these three levels:
  1. Item Level
  2. Type Level
  3. All Level (this is a generic level)
Here is a sample Lookup screen in EBS:

Sample EBS Lookup Screen Example

Since Code and Meaning values must be unique, I used a "_1_" as ways to increment similar values. You would setup this screen for each TP. This is not always pretty but it works.

Here is how the hierarchy is implemented: 

If an item is listed by SKU, then the SKU_<ordered_item> mapping is chosen first. Based on the actual inventory values obtained from the Description column, it uses the multiplier in the Tag column to recalculate what amount of inventory to send. If no match there, it then checks the item's Type (TYP_<n>_<type>). If no match there, it defaults to GEN_<n> values. All that is required is a "GEN_" setup. 

For Tags less than 1, the amount in the screen is used as a multiplier. For numbers greater than 1, it is an absolute number. 



These entries in the above Lookup screen:


Code     Description    Tag
-----    -----------    ---
GEN_1    <=3             1
GEN_2    >3 and <=30    .5
GEN_3    >30            .99


translate to:

If our current inventory is 3 or less, send 1 item. 
If our current inventory is between 4 and 30, send half. So if 10 – send 5. If 20 – send 10.
If our current inventory is more than 30 – send that number. It will either be the original number or one less depending on the rounding used in the Function below.


Here is the Function that was created:

CREATE OR REPLACE FUNCTION APPS.xxoc_tp_inv_send(p_partner IN VARCHAR2, p_sku IN VARCHAR2, p_type IN VARCHAR2, p_quantity IN NUMBER)
    RETURN NUMBER
IS
    v_result NUMBER;
    p_lookup  VARCHAR2(50);

BEGIN
    p_lookup   := 'OC_' || p_partner || '_INVENTORY_RULES';
     
    SELECT CASE WHEN to_number(tag, '999.99') >= 1 THEN to_number(tag, '999.99') ELSE to_number(tag, '999.99') * p_quantity END
      INTO v_result
      FROM (SELECT *
              FROM (SELECT sku,
                           typ,
                           gen,
                           gt,
                           TO_NUMBER(NVL2(gt, REGEXP_SUBSTR(description, '[0-9]+'), NULL)) rmin,
                           lt,
                           TO_NUMBER(NVL2(
                                         lt,
                                         REGEXP_SUBSTR(
                                             description,
                                             '[0-9]+',
                                             1,
                                             NVL2(gt, 2, 1)
                                         ),
                                         NULL
                                     ))
                               rmax,
                           tag
                      FROM (SELECT meaning,
                   description,
                   tag,
                   CASE WHEN meaning LIKE 'SKU%' THEN SUBSTR (meaning, 5) END sku,
                   CASE WHEN meaning LIKE 'TYP%' THEN SUBSTR (meaning, 7) END typ,
                   CASE WHEN meaning LIKE 'GEN%' THEN 'GEN' END gen,
                   REGEXP_SUBSTR (description, '>=?') gt,
                   REGEXP_SUBSTR (description, '<= ?') lt
              FROM FND_LOOKUP_VALUES FLV
             WHERE LOOKUP_TYPE = p_lookup AND ENABLED_FLAG = 'Y' AND SYSDATE BETWEEN flv.start_date_active AND NVL (flv.end_date_active, SYSDATE + 1)))
             WHERE (sku = p_sku OR typ = p_type OR gen = 'GEN')
               AND (CASE
                        WHEN gt = '>' AND p_quantity > rmin THEN 1
                        WHEN gt = '>=' AND p_quantity >= rmin THEN 1
                        WHEN gt IS NULL THEN 1
                    END = 1
                AND CASE
                        WHEN lt = '<' AND p_quantity < rmax THEN 1
                        WHEN lt = '<=' AND p_quantity <= rmax THEN 1
                        WHEN lt IS NULL THEN 1
                    END = 1)
            ORDER BY CASE WHEN sku = p_sku THEN 1 WHEN typ = p_type THEN 2 ELSE 3 END) x
     WHERE ROWNUM = 1;
    -- v_result := CEIL(v_result);  -- Round the result up. If round down - use FLOOR
    v_result := FLOOR(v_result);  -- Round the result down. If round up - use CEIL
    RETURN v_result;
END;
/





Sample uses – test of the Generic translation:


21:16:10 GOLD1> select xxoc_tp_inv_send('BUY','mysku123','mytype321',3) from dual;
XXOC_TP_INV_SEND('BUY','MYSKU123','MYTYPE321',3)
------------------------------------------------
                                               1
21:17:12 GOLD1> select xxoc_tp_inv_send('BUY','mysku123','mytype321',6) from dual;
XXOC_TP_INV_SEND('BUY','MYSKU123','MYTYPE321',6)
------------------------------------------------
                                               3
21:17:51 GOLD1> select xxoc_tp_inv_send('BUY','mysku123','mytype321',32) from dual;
XXOC_TP_INV_SEND('BUY','MYSKU123','MYTYPE321',32)
-------------------------------------------------
                                               31

Test of the Type translation:

21:18:08 GOLD1> select xxoc_tp_inv_send('BUY','mysku123','OC_KIT_AB_OPENBOX',10) from dual;
XXOC_TP_INV_SEND('BUY','MYSKU123','OC_KIT_AB_OPENBOX',10)
---------------------------------------------------------
                                                        5

21:22:54 GOLD1> select xxoc_tp_inv_send('BUY','mysku123','OC_KIT_AB_OPENBOX',40) from dual;
XXOC_TP_INV_SEND('BUY','MYSKU123','OC_KIT_AB_OPENBOX',40)
---------------------------------------------------------
                                                       10
 
Test of the SKU translation:

21:41:23 GOLD1> select xxoc_tp_inv_send('BUY','074101011265','mytype321',100) from dual;
XXOC_TP_INV_SEND('BUY','074101011265','MYTYPE321',100)
------------------------------------------------------
                                                     0
 
21:41:50 GOLD1> select xxoc_tp_inv_send('BUY','074101011265','mytype321',20000) from dual;
XXOC_TP_INV_SEND('BUY','074101011265','MYTYPE321',20000)
--------------------------------------------------------
                                                   19800

 
In the above example, since we went over the SKU Rule's amount of 10,000, then the GEN Rule processed using a multiplier of .99 then rounded down (FLOOR).

Thursday, June 7, 2012

AutoExtend Unlimited

I used to create uniform 2GB Data Files without AutoExtend. Reason being, I liked to know what was creating rows in my database and the "set it and forget it" attitude was OK for Test Systems. But if you don't keep on top of things, you might find yourself with a Table with 175m rows of unneeded data.

Let's talk about AutoExtend - Unlimited with a simple command:

alter database datafile 'c:\oradata\mysid\XYZ.dbf' autoextend on next 10m maxsize unlimited;

Turns out, Data files are not exactly unlimited in size (even though you may check the box or specify it like in the SQL above), so the term "Unlimited" refers to the maximun size your datafile is allowed to reach, and this depends on the Oracle Block Size. Oracle 10g does have a maximun Data File limit of 128GB by the way.

To find your real maximum file size, multiply block size by 4194303 (2 ^22). This is the actual maximum size:

Maximum datafile size = db_block_size * maximum number of blocks (which is 4194303)

A datafile cannot be oversized, otherwise it could get corrupted. Let's say if your database is 8k blocks (which most are) - that means that one file can not exceed approximately 34GB (34,359,730,176 bytes) without having database corruption.

Setting your Data Files to AUTOEXTEND and UNLIMITED growth could give you a false sense of well being as your Tablespace reaches a hard ceiling. Periodically check your Data Files for once approaching this hard-limit.

Tuesday, June 5, 2012

How to change your db_block_size - NOT

Was reading this white paper from Virident today:

Accelerating Oracle Databases and Reducing Storage Complexity and Costs. Virident FlashMAX SCM as Primary Storage.

Offered at: http://www.bitpipe.com/data/document.do?res_id=1337280483_183&src=5053040&asrc=EM_BRU_17570135&uid=10956921  (as of 6/5/2012)

They are making a point that switching from 8k block size to 4k would reap performance benefits:

"When Oracle data is stored on FlashMAX devices, reducing Oracle database block size from the default value of 8192 bytes (8KB) to 4096 bytes (4KB) can provide substantial performance benefits in many applications. With HDDs, reading/writing 4KB takes essentially the same amount of time as 8KB as most of the time is spent on moving heads. In contrast, FlashMAX can perform 2x the amount of IOPS with 4KB block size compared to 8KB block size, or the same amount of IOPS at lower latencies."

They offered the steps on how to change your block size:

"You can set this parameter in several different ways:
  1. By adding it to initORACLE_SID.ora file (or changing if the parameter already exists)
  2. By setting the parameter in the SPFILE:
    1. SQL>alter system set db_block_size=4096 scope=spfile;
    2. SQL>shutdown immediate
    3. SQL>startup
  3. By setting it on Initialization Parameters -> Sizing tab of the DBCA"

Last I checked, you could only change the default block size at DB creation. Sure you can have multiple block sizes supported at the Tablespace level (must also specify cache sizes) but it's not as simple as changing a init parameter and bouncing your instance – otherwise, you will see:

ORA-00209: control file blocksize mismatch, check alert log for more info

Hard to take this white paper seriously or did I miss something?


Vendor response received 6/5/2012:

Hello Anthony,

You are bringing up a valid point. The block size change needs to be done before creating the DB . We will fix the whitepaper to clarify this.
 

Thanks a lot you for your feedback!

-Artem