Why Procedural Code Matters
Callidus Commissions relies on PL/SQL for complex calculations: commission aggregation, tier assignments, payout processing. These are business logic engines, written as packages with interdependent procedures. Moving them to HANA requires understanding not just syntax, but the architectural shift from row-oriented to set-based operations.
Structure Comparison: Blocks and Declarations
| Aspect | Oracle Database PL/SQL | SAP HANA Database SQL Script |
|---|---|---|
| Block structure | DECLARE ... BEGIN ... EXCEPTION ... END; | BEGIN ... END; (no DECLARE section) |
| Variable declaration | In DECLARE block before BEGIN | DECLARE inside BEGIN...END |
| Variable type | %TYPE, %ROWTYPE attributes | Must specify inline (no attributes) |
| Local procedures | Nested procedures (PL/SQL blocks) | Not supported — use separate procedures |
| Package | Package spec + body (grouping, globals) | Not supported — use individual procedures |
Block Structure Example
DECLARE v_total NUMBER(10,2); v_count INTEGER; v_start_date DATE; BEGIN v_start_date := TRUNC(SYSDATE); SELECT SUM(amount), COUNT(*) INTO v_total, v_count FROM cs_commissions WHERE created_date >= v_start_date; DBMS_OUTPUT.PUT_LINE('Total: ' || v_total); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE('No data'); END; /
BEGIN DECLARE v_total DECIMAL(10,2) DEFAULT 0; DECLARE v_count INTEGER DEFAULT 0; DECLARE v_start_date DATE; SET v_start_date = CURRENT_DATE; SELECT SUM(amount), COUNT(*) INTO v_total, v_count FROM cs_commissions WHERE created_date >= v_start_date; -- No DBMS_OUTPUT equivalent; use CALL to procedure or return output via OUT parameter -- Exception handling is different (see below) END; /
The Big Rewrite: Cursor Loops → Table Variables
This is where 80% of migration effort goes. Cursor loops are row-by-row processing (slow in HANA). Table variables are bulk operations (fast).
Oracle Pattern: Cursor Loop
DECLARE CURSOR c_trans IS SELECT id, salesperson_id, commission_amount FROM cs_transactions WHERE status = 'APPROVED'; v_trans c_trans%ROWTYPE; BEGIN OPEN c_trans; LOOP FETCH c_trans INTO v_trans; EXIT WHEN c_trans%NOTFOUND; -- Process each row INSERT INTO payout_schedule VALUES (v_trans.id, v_trans.salesperson_id, v_trans.commission_amount); END LOOP; CLOSE c_trans; END; /
HANA Pattern: Table Variable (Bulk Operation)
BEGIN DECLARE TABLE v_trans ( id INTEGER, salesperson_id INTEGER, commission_amount DECIMAL(10,2) ); -- Bulk fetch into table variable INSERT INTO v_trans SELECT id, salesperson_id, commission_amount FROM cs_transactions WHERE status = 'APPROVED'; -- Bulk insert (one operation, no loop) INSERT INTO payout_schedule SELECT id, salesperson_id, commission_amount FROM v_trans; END; /
Variable Type Declarations
Oracle's %TYPE and %ROWTYPE attributes are shorthand for "use the type of this column/row". HANA has no equivalent. You must declare types explicitly.
| Oracle Pattern | HANA Equivalent | Notes |
|---|---|---|
v_id cs_transactions.id%TYPE; | DECLARE v_id INTEGER; | Look up the column type; use it directly. |
v_row cs_transactions%ROWTYPE; | DECLARE TABLE t (id INT, name NVARCHAR, ...) | Define a table structure with each column. |
v_list t_list_type; | DECLARE TABLE t (value ...) | Collections → table variables. |
Exception Handling: EXCEPTION WHEN → DECLARE EXIT HANDLER
DECLARE e_custom EXCEPTION; BEGIN IF v_amount < 0 THEN RAISE e_custom; END IF; EXCEPTION WHEN e_custom THEN DBMS_OUTPUT.PUT_LINE('Invalid amount'); WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('Unexpected error'); END; /
BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN -- Handle any SQL exception END; IF v_amount < 0 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid amount'; END IF; END; /
Packages vs Procedures: Decomposition Strategy
PL/SQL packages are containers for related procedures and functions, with shared package-level variables. HANA has no equivalent.
Oracle Package Structure
CREATE OR REPLACE PACKAGE cs_commission_pkg IS -- Package-level variable (shared state) g_batch_id NUMBER; -- Public procedures PROCEDURE calculate_commissions(p_period_id NUMBER); PROCEDURE assign_tiers(p_period_id NUMBER); PROCEDURE process_payouts(p_period_id NUMBER); END cs_commission_pkg; /
HANA Decomposition Strategy
No packages. Create individual procedures organized by schema or naming convention:
-- Option 1: Use schema (namespace) CREATE SCHEMA cs_commission; CREATE PROCEDURE cs_commission.calculate_commissions(p_period_id INTEGER) ... CREATE PROCEDURE cs_commission.assign_tiers(p_period_id INTEGER) ... -- Option 2: Naming convention CREATE PROCEDURE cs_calc_commissions(p_period_id INTEGER) ... CREATE PROCEDURE cs_assign_tiers(p_period_id INTEGER) ... -- Option 3: Pass shared state as parameters -- If Oracle package shared g_batch_id, pass it as a parameter instead
BULK COLLECT → Direct INSERT/SELECT
DECLARE TYPE t_commission_list IS TABLE OF cs_commissions%ROWTYPE; v_list t_commission_list; BEGIN -- Fetch up to 10000 rows at once (better than row-by-row) BULK COLLECT INTO v_list SELECT * FROM cs_transactions WHERE status = 'APPROVED'; -- Process the collection (usually a loop) FORALL i IN 1 .. v_list.COUNT INSERT INTO payout_schedule VALUES v_list(i); END; /
BEGIN -- No BULK COLLECT needed. Direct INSERT...SELECT is bulk by default. INSERT INTO payout_schedule SELECT * FROM cs_transactions WHERE status = 'APPROVED'; END; /
AUTONOMOUS_TRANSACTION: Workarounds
Oracle's AUTONOMOUS_TRANSACTION pragma allows a procedure to commit independently of the caller. HANA has no equivalent.
| Scenario | Oracle Solution | HANA Workaround |
|---|---|---|
| Logging that survives rollback | PRAGMA AUTONOMOUS_TRANSACTION; COMMIT; | Call a separate procedure with its own transaction (commit after it returns) |
| Audit trail | Autonomous transaction log proc | Use triggers (which auto-commit) or call a separate logging procedure |
| Nested procedures with independent commits | Mark inner proc as autonomous | Refactor to avoid nesting; use separate called procedures |
Migration Checklist: Procedural Code
- Inventory procedures and packages: List all PL/SQL packages, procedures, and functions in Callidus.
- Identify cursor loops: Find all
CURSOR ... OPEN ... FETCH ... LOOPpatterns. These need rewriting as table variable bulk operations. - Map packages to schemas: Each package becomes a schema (or use naming convention). Create the schema in HANA.
- Rewrite cursor loops: Replace with INSERT INTO ... SELECT or UPDATE ... FROM patterns.
- Convert BULK COLLECT: Use direct INSERT INTO ... SELECT (no intermediate collection needed).
- Handle exceptions: Map Oracle exceptions to HANA DECLARE EXIT HANDLER ... FOR SQLEXCEPTION.
- Remove AUTONOMOUS_TRANSACTION: Refactor to separate called procedures or use triggers.
- Test logic equivalence: For each procedure, compare Oracle output (test case) vs HANA output. Validate row counts, calculated values, and side effects.