🔥 V7.0 Released! DB2/MySQL migration, 13 new paths, and performance diagnosis.
Free Trial

Table of Contents

As enterprises modernize database architectures, migration from Oracle to open-source PostgreSQL has become a major trend. However, the two systems differ significantly in syntax, functions, data types, and execution behavior. This challenge is even greater when targeting older PostgreSQL versions such as 9.2.
This article uses a real SQLShift migration example to explain key issues and automated handling strategies for converting Oracle stored procedures to PostgreSQL 9.2, demonstrating SQLShift’s practical capabilities in SQL auto-conversion.

1. Migration Background: Stored Procedure Compatibility Challenges

We use a typical Oracle stored procedure that includes cursor processing, exception handling, dynamic SQL, and debug output. The original code depends heavily on Oracle-specific syntax and functions and cannot run directly on PostgreSQL (especially version 9.2).

Click to view original Oracle stored procedure code
DELIMITER $$
CREATE OR REPLACE PROCEDURE CHECKANDUPDATESALARIES(p_dept_id IN NUMBER, p_result OUT CLOB, p_percent IN NUMBER, p_min_dept IN NUMBER DEFAULT NULL, p_max_dept IN NUMBER DEFAULT NULL)
IS
  TYPE stats_rec IS RECORD (
    total_emp NUMBER,
    avg_salary NUMBER,
    max_salary NUMBER
  );
  TYPE p_result_rec IS RECORD (
    employee_id NUMBER,
    last_name VARCHAR2(50),
    salary NUMBER(8,2)
  );
  v_stats stats_rec;
  v_result p_result_rec;
  v_cursor SYS_REFCURSOR;
  v_sql VARCHAR2(1000);
  v_dept_name VARCHAR2(50);
  v_rows_updated NUMBER := 0;
  v_total_budget NUMBER := 0;
  CURSOR dept_cur IS
    SELECT department_id, budget FROM DEPARTMENTS
    WHERE (department_id >= p_min_dept OR p_min_dept IS NULL)
      AND (department_id <= p_max_dept OR p_max_dept IS NULL);
BEGIN
  -- Department validation
  SELECT department_name INTO v_dept_name FROM DEPARTMENTS WHERE department_id = p_dept_id;

  -- Basic statistics
  SELECT COUNT(*), AVG(salary), MAX(salary)
  INTO v_stats.total_emp, v_stats.avg_salary, v_stats.max_salary
  FROM EMPLOYEES WHERE department_id = p_dept_id;

  -- Dynamic SQL generation
  v_sql := 'SELECT * FROM (SELECT employee_id, last_name, salary FROM employees '
        || 'WHERE department_id = :1 ORDER BY salary DESC) WHERE ROWNUM <= 5';

  OPEN v_cursor FOR v_sql USING p_dept_id;

  -- Build JSON output
  p_result := '{
  "department": "' || v_dept_name || '",
  "total_employees": ' || v_stats.total_emp || ',
  "salary_stats": {
    "average": ' || v_stats.avg_salary || ',
    "max": ' || v_stats.max_salary || '
  },
  "top_earners": [';

  LOOP
    FETCH v_cursor INTO v_result;
    EXIT WHEN v_cursor%NOTFOUND;
    p_result := p_result || '
    {"id": ' || v_result.employee_id || ', "name": "' || v_result.last_name || '", "salary": ' || v_result.salary || '},';
  END LOOP;

  p_result := RTRIM(p_result, ',') || '
  ]
}';

  CLOSE v_cursor;

  FOR dept_rec IN dept_cur LOOP
    -- Salary update process
    UPDATE EMPLOYEES
    SET salary = NVL(salary, 3000) * (1 + p_percent/100)
    WHERE department_id = dept_rec.department_id;

    v_rows_updated := v_rows_updated + SQL%ROWCOUNT;

    -- Budget validation
    SELECT SUM(salary) INTO v_total_budget
    FROM EMPLOYEES WHERE department_id = dept_rec.department_id;

    IF v_total_budget > dept_rec.budget THEN
      RAISE_APPLICATION_ERROR(-20001, 'Budget exceeded in department ' || dept_rec.department_id);
    END IF;
  END LOOP;

  -- Output processing result
  DBMS_OUTPUT.PUT_LINE('Updated ' || v_rows_updated || ' records, Time ' || SYSDATE);

EXCEPTION
  WHEN NO_DATA_FOUND THEN
    p_result := '{"error": "Department not found"}';
  WHEN OTHERS THEN
    p_result := '{"error": "' || SQLERRM || '"}';
END;
$$

Major incompatibilities include:

CategoryOracle SyntaxPostgreSQL Handling (9.2)
Procedure definitionCREATE PROCEDUREReplace with CREATE FUNCTION
Procedure delimiterDELIMITER $$Not supported, must be removed
Cursor variableSYS_REFCURSORChange to REFCURSOR
Bound cursor definitioncursor_name IS SELECT...Change to cursor_name CURSOR FOR SELECT...
Function replacementNVL(x, y)Replace with COALESCE(x, y)
Row limitingROWNUM <= nReplace with LIMIT n
Subquery aliasOptionalMandatory
Cursor completion check%NOTFOUNDReplace with EXIT WHEN NOT FOUND
Dynamic SQL bind variables:1, :2Replace with $1, $2
Dynamic SQL executionImplicit executionUse explicit EXECUTE
Exception raisingRAISE_APPLICATION_ERROR(...)Replace with RAISE EXCEPTION '...'
Console outputDBMS_OUTPUT.PUT_LINEReplace with RAISE NOTICE
System timeSYSDATEReplace with CURRENT_TIMESTAMP or now()
Affected row countSQL%ROWCOUNTReplace with GET DIAGNOSTICS
Data typesNUMBER, VARCHAR2, CLOBReplace with numeric, varchar, text

2. Automated Conversion in Practice: SQLShift Migration Capabilities

Facing so many conversion points, SQLShift used built-in syntax rules and contextual understanding to automatically convert this complex stored procedure into an executable PostgreSQL 9.2 function structure. Key capabilities demonstrated include:

✅ Multi-syntax recognition

Accurately identifies and replaces constructs such as NVL, ROWNUM, SYSDATE, and :1.

✅ Cursor and structure refactoring

Correctly maps RECORD structures, cursor loops, and dynamic SQL binding logic into PostgreSQL REFCURSOR patterns and variable systems.

✅ Exception and debug output migration

Replaces Oracle exception and debug output (RAISE_APPLICATION_ERROR, PUT_LINE) with PostgreSQL RAISE EXCEPTION and NOTICE, preserving runtime diagnostics.

✅ Intelligent data type replacement

Automatically maps NUMBER, VARCHAR2, and CLOB to PostgreSQL 9.2-supported types.

3. Conversion Result Validation

The converted procedure executed successfully in a PostgreSQL 9.2 environment. You can view the full conversion process and execution screenshots at:

4. Conclusion: From Tedious Manual Migration to Intelligent Conversion

Traditional Oracle stored procedure migration involves heavy workload, high error-handling costs, and difficult testing. SQLShift significantly reduces manual rewriting effort, especially for:

  • Legacy enterprise systems that must migrate to older PostgreSQL versions (such as 9.2)
  • Complex migrations of business logic in stored procedures and functions
  • Projects that require rapid validation and automatic rewriting Through automated recognition, syntax mapping, semantic rewriting, and execution validation, SQLShift is helping more teams complete a smooth transition from Oracle to PostgreSQL.

Start Your Free SQLShift Trial Now

Break cross-database barriers and intelligently migrate non-table objects like stored procedures with less effort and lower migration cost.

Start Free Trial