Practical Migration from Oracle to PostgreSQL 9.2
Table of Contents
- 1. Migration Background: Stored Procedure Compatibility Challenges
- 2. Automated Conversion in Practice: SQLShift Migration Capabilities
- 3. Conversion Result Validation
- 4. Conclusion: From Tedious Manual Migration to Intelligent Conversion
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:
| Category | Oracle Syntax | PostgreSQL Handling (9.2) |
|---|---|---|
| Procedure definition | CREATE PROCEDURE | Replace with CREATE FUNCTION |
| Procedure delimiter | DELIMITER $$ | Not supported, must be removed |
| Cursor variable | SYS_REFCURSOR | Change to REFCURSOR |
| Bound cursor definition | cursor_name IS SELECT... | Change to cursor_name CURSOR FOR SELECT... |
| Function replacement | NVL(x, y) | Replace with COALESCE(x, y) |
| Row limiting | ROWNUM <= n | Replace with LIMIT n |
| Subquery alias | Optional | Mandatory |
| Cursor completion check | %NOTFOUND | Replace with EXIT WHEN NOT FOUND |
| Dynamic SQL bind variables | :1, :2 | Replace with $1, $2 |
| Dynamic SQL execution | Implicit execution | Use explicit EXECUTE |
| Exception raising | RAISE_APPLICATION_ERROR(...) | Replace with RAISE EXCEPTION '...' |
| Console output | DBMS_OUTPUT.PUT_LINE | Replace with RAISE NOTICE |
| System time | SYSDATE | Replace with CURRENT_TIMESTAMP or now() |
| Affected row count | SQL%ROWCOUNT | Replace with GET DIAGNOSTICS |
| Data types | NUMBER, VARCHAR2, CLOB | Replace 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.
