Using Set Processing Examples

Each of the following topics contains an example of set processing.

Payroll

In this example, suppose the payroll department needs to give a 1000 USD salary increase to everyone whose department made more than 50,000 USD profit. The following pseudocode enables you to compare the row-by-row and set-based approaches.

  • Row-by-Row:

    declare A cursor for select dept_id from department where profit > 50000;
    open A;
    fetch A into p_dept_id
    while sql_status == OK
       update personnel set salary = (salary+1000) where dept_id = p_dept_id;
       fetch A into p_dept_id;
    end while;
    close A;
    free A;
  • Set-Based:

    update personnel set salary = (salary + 1000)
       where exists
          (select ‘X’ from department
             where profit > 50000
             and personnel.dept_id = department.dept_id)

Note:

The set-based example employs a correlated subquery, which is important in set-based processing.

Temporary Tables

One technique for improving database performance is to use temporary tables to hold the results of common subqueries. Effective dating and setID indirection are common types of subqueries that you can replace with joins to temporary tables. With the joins in place, you can access the temporary table instead of doing the subquery multiple times. Not only do most databases prefer joins to subqueries, but if you combine multiple subqueries into a single join as well, the performance benefits can be significant.

In this setID indirection example, you see a join from a transaction table (keyed by BUSINESS_UNIT and ACCOUNTING_DT) to a setup table (keyed by SETID and EFFDT).

If using a single SQL statement, you need to bring in PS_SET_CNTRL_REC to map the business unit to a corresponding setID. Typically, you do this in a subquery. You also need to bring in the setup table a second time in a subquery to get the effective date (MAX(EFFDT) <= ACCOUNTING_DT). If you have a series of similar statements, performance may be negatively affected.

The alternative is to use a temporary table that is the equivalent of the setup table. The temporary table is keyed by BUSINESS_UNIT and ACCOUNTING_DT instead of SETID and EFFDT. You populate it initially by joining in your batch of transactions (presumably also a temporary table) once, as described previously, to get all the business units and accounting dates for this batch. From then on, your transaction and setup temporary tables have common keys, which allow a straight join with no subqueries.

For the example, the original setup table (PS_ITEM_ENTRY_TBL) is keyed by SETID, ENTRY_TYPE and EFFDT.

The denormalized temporary table version (PS_ITEM_ENTRY_TAO) is keyed by PROCESS_INSTANCE, BUSINESS_UNIT, ENTRY_TYPE and ACCOUNTING_DT, and carries the original keys (SETID and EFFDT) as simple attributes for joining to other related setup tables, as in PS_ITEM_LINES_TBL for this example.

If the program references the setup table in only one Insert/Select or Select statement, you would not see increased performance by denormalizing the temporary table. But if several SQL statements are typically executed in a single run, all of which join in the same setup table with similar setID and effective date considerations, then the performance cost of populating the temporary table initially provides long-term advantages.

  • Original setup table version:

    INSERT INTO PS_PG_PENDDST_TAO (...)
    SELECT 
    . . . . . 
     ( (I.ENTRY_AMT_BASE - I.VAT_AMT_BASE) * L.DST_LINE_MULTIPLR * L.DST_LINE_PERCENT /⇒
     100 ),   ( (I.ENTRY_AMT - I.VAT_AMT) * L.DST_LINE_MULTIPLR * L.DST_LINE_PERCENT / 100 ),
    . . . . .
    FROM  PS_PENDING_ITEM I, PS_PG_REQUEST_TAO R, PS_ITEM_LINES_TBL L,
          PS_ITEM_ENTRY_TBL E, PS_SET_CNTRL_REC S, PS_BUS_UNIT_TBL_AR B
    . . . . .WHERE  AND L.ENTRY_REASON = I.ENTRY_REASON AND L.SETID = E.SETID AND L.ENTRY_TYPE = E.ENTRY_TYPE AND L.EFFDT = E.EFFDT. . . . .
     AND E.EFF_STATUS = 'A'
     AND S.RECNAME = 'ITEM_ENTRY_TBL'
     AND S.SETID = E.SETID
     AND S.SETCNTRLVALUE = I.BUSINESS_UNIT
     AND E.ENTRY_TYPE = I.ENTRY_TYPE
     AND E.EFFDT = (SELECT MAX(EFFDT) FROM PS_ITEM_ENTRY_TBL Z
                       WHERE Z.SETID = E.SETID
                         AND Z.ENTRY_TYPE = E.ENTRY_TYPE
                         AND Z.EFF_STATUS = 'A'
                         AND Z.EFFDT <= I.ACCOUNTING_DT )
     AND B.BUSINESS_UNIT = I.BUSINESS_UNIT
    /
  • Denormalized temporary table version:

    INSERT INTO PS_ITEM_ENTRY_TAO
    . . . . .  
    SELECT DISTINCT %BIND(PROCESS_INSTANCE), I.BUSINESS_UNIT, I.ACCOUNTING_DT,
     E.ENTRY_TYPE...
    . . . 
    FROM  PS_PENDING_ITEM I, PS_PG_REQUEST_TAO R, 
          PS_ITEM_ENTRY_TBL E, PS_SET_CNTRL_REC S, PS_BUS_UNIT_TBL_AR B 
    WHERE R.PROCESS_INSTANCE = %BIND(PROCESS_INSTANCE)
     AND R.PGG_GROUP_TYPE = 'B'
     AND I.POSTED_FLAG = 'N'
     AND R.GROUP_BU = I.GROUP_BU
     AND R.GROUP_ID = I.GROUP_ID
     AND E.EFF_STATUS = 'A'
     AND S.RECNAME = 'ITEM_ENTRY_TBL'
     AND S.SETID = E.SETID
     AND S.SETCNTRLVALUE = I.BUSINESS_UNIT
     AND E.ENTRY_TYPE = I.ENTRY_TYPE
     AND E.EFFDT =  ( SELECT MAX(EFFDT) FROM PS_ITEM_ENTRY_TBL Z
                       WHERE Z.SETID = E.SETID
                         AND Z.ENTRY_TYPE = E.ENTRY_TYPE
                         AND Z.EFF_STATUS = 'A'
                         AND Z.EFFDT <= I.ACCOUNTING_DT )
     AND B.BUSINESS_UNIT = I.BUSINESS_UNIT
    /
    INSERT INTO PS_PG_PENDDST_TAO (...)
    SELECT ... 
     ( (I.ENTRY_AMT_BASE - I.VAT_AMT_BASE) * L.DST_LINE_MULTIPLR * L.DST_LINE_PERCENT /⇒
     100 ),
    ( (I.ENTRY_AMT - I.VAT_AMT) * L.DST_LINE_MULTIPLR * L.DST_LINE_PERCENT / 100 ),
    . . . . .
    FROM  PS_PENDING_ITEM I, PS_PG_REQUEST_TAO R, PS_ITEM_LINES_TBL L, 
          PS_ITEM_ENTRY_TAO E
    . . . . .
    WHERE
    . . . . .
     AND L.ENTRY_REASON = I.ENTRY_REASON
     AND L.SETID = E.SETID
     AND L.ENTRY_TYPE = E.ENTRY_TYPE
     AND L.EFFDT = E.EFFDT
    . . . . .
     AND E.BUSINESS_UNIT = I.BUSINESS_UNIT
     AND E.ACCOUNTING_DT = I.ACCOUNTING_DT
     AND E.ENTRY_TYPE = I.ENTRY_TYPE
    /

Platform Issues

Set processing does not behave the same on every database platform. On some platforms, set processing can encounter performance breakdowns. Some platforms do not optimize update statements that include subqueries.

For example, environments that are accustomed to updates with subqueries get all the qualifying department IDs from the Department table and then, using an index designed by an application developer, update the Personnel table. Other platforms read through every employee row in the Personnel table and query the Department table for each row.

On platforms where these types of updates are a problem, try adding some selectivity to the outer query. In the following example, examine the SQL in the Before section and then notice how it is modified in the After section to run smoothly on all platforms. You can use this approach to work around platforms that have difficulty with updates that include subqueries.

Note:

In general, set processing capabilities vary by database platform. The performance characteristics of each database platform differ with more complex SQL and set processing constructs. Some database platforms allow additional set processing constructs that enable you to process even more data in a set-based manner. If performance needs improvement, you must tailor or tune the SQL for your environment. You should be familiar with the capabilities and limitations of your database platform and be able to recognize, through tracing and performance results, the types of modifications you need to incorporate with the basic set processing constructs described.

  • Basic version:

    UPDATE PS_REQ_LINE
    SET SOURCE_STATUS = 'I'
    WHERE
    EXISTS
    (SELECT 'X' FROM PS_PO_ITM_STG STG
    WHERE
    STG.PROCESS_INSTANCE =%BIND(PROCESS_INSTANCE)  AND
    STG.PROCESS_INSTANCE =PS_REQ_LINE.PROCESS_INSTANCE AND
    STG.STAGE_STATUS = 'I'  AND
    STG.BUSINESS_UNIT = PS_REQ_LINE.BUSINESS_UNIT AND
    STG.REQ_ID = PS_REQ_LINE.REQ_ID AND
    STG.REQ_LINE_NBR = PS_REQ_LINE.LINE_NBR)
  • Optimized for platform compatibility:

    UPDATE PS_REQ_LINE
    SET SOURCE_STATUS = 'I'
    WHERE
    PROCESS_INSTANCE = %BIND(PROCESS_INSTANCE) AND
     EXISTS
    (SELECT 'X' FROM PS_PO_ITM_STG STG
    WHERE
    STG.PROCESS_INSTANCE =%BIND(PROCESS_INSTANCE)  AND
    STG.PROCESS_INSTANCE =PS_REQ_LINE.PROCESS_INSTANCE AND
    STG.STAGE_STATUS = 'I'  AND
    STG.BUSINESS_UNIT = PS_REQ_LINE.BUSINESS_UNIT AND
    STG.REQ_ID = PS_REQ_LINE.REQ_ID AND
    STG.REQ_LINE_NBR = PS_REQ_LINE.LINE_NBR)

Note:

This example assumes that the transaction table (PS_REQ_LINE) has a PROCESS_INSTANCE column to lock rows that are in process. This is another example of designing your database with batch performance and set processing in mind.

This modification enables the system to limit its scan through PS_REQ_LINE to only those rows that the program is currently processing. At the same time, it enables a more set-friendly environment to first scan the smaller staging table and then update the larger outer table.