Skip to content
Migration centers — AWS, GCP, Snowflake, Databricks, Teradata, Cloudera, Informatica, Palantir, SAS, Oracle to Azure

Oracle to Azure Database for PostgreSQL Migration

Comparative positioning note

This document is written from the perspective of Microsoft Azure, Cloud Scale Analytics, and CSA Loom. Any description of third-party or competing products, services, pricing, or capabilities is derived from publicly available documentation and sources believed accurate at the time of writing, and is provided for general comparison only. We do not claim expertise in, or authority over, any non-Microsoft product or service; the respective vendor's official documentation is the authoritative source for their offerings, which may change over time. Nothing here is intended to disparage any vendor — where a competing product has genuine advantages, we aim to note them honestly. Verify all third-party details against the vendor's current official documentation before making decisions.

When to choose PostgreSQL, how to use ora2pg for assessment and conversion, PL/SQL to PL/pgSQL conversion patterns, extension ecosystem, and Citus for horizontal scaling.


When to choose Azure PostgreSQL

Choose Azure Database for PostgreSQL Flexible Server when your organization has an open-source mandate or preference, cost sensitivity is a primary driver (zero license cost), the application can adapt to PL/pgSQL (syntactically closer to PL/SQL than T-SQL), or you need advanced spatial (PostGIS), vector search (pgvector), or horizontal scale-out (Citus) capabilities.


1. Azure Database for PostgreSQL for Oracle DBAs

PostgreSQL is the world's most advanced open-source relational database. Azure Database for PostgreSQL Flexible Server provides a fully managed deployment with enterprise features.

Oracle concept PostgreSQL equivalent
Oracle Instance PostgreSQL server (cluster)
Oracle Database Database
Schema Schema (same concept)
Tablespace Tablespace (similar, less commonly used)
PL/SQL PL/pgSQL (similar syntax)
Oracle packages Schemas + functions/procedures
SQL*Plus psql
RMAN Automated backups (Azure-managed)
Data Guard Zone-redundant HA + read replicas
RAC Citus extension (distributed)
Oracle Text Full-text search (tsvector/tsquery)
Oracle Spatial PostGIS extension
Oracle Advanced Queuing LISTEN/NOTIFY + Azure Service Bus
DBMS_SCHEDULER pg_cron extension
AWR / ASH pg_stat_statements + Query Performance Insight
Enterprise Manager Azure Portal + pgAdmin

2. ora2pg assessment and conversion

2.1 What is ora2pg

ora2pg is the primary open-source tool for Oracle-to-PostgreSQL migration. It performs:

  • Schema analysis with complexity scoring
  • Schema conversion (tables, views, sequences, indexes, constraints, triggers, procedures, functions, packages, types)
  • Data migration with parallel export/import
  • PL/SQL to PL/pgSQL automated conversion

2.2 Installing ora2pg

# On Ubuntu/Debian (recommended for migration workstation)
sudo apt-get update
sudo apt-get install -y ora2pg

# On RHEL/CentOS
sudo yum install -y ora2pg

# From source (latest version)
git clone https://github.com/darold/ora2pg.git
cd ora2pg
perl Makefile.PL
make && sudo make install

# Verify installation
ora2pg --version

2.3 Running an assessment

# Create ora2pg configuration
mkdir -p /opt/ora2pg/migration
cat > /opt/ora2pg/migration/ora2pg.conf << 'EOF'
ORACLE_HOME /usr/lib/oracle/19.0/client64
ORACLE_DSN  dbi:Oracle:host=oracle-prod.agency.gov;sid=FEDDB;port=1521
ORACLE_USER migration_reader
ORACLE_PWD  ***

# Assessment mode
EXPORT_SCHEMA 0
SCHEMA        APP_SCHEMA

# Output
OUTPUT_DIR    /opt/ora2pg/migration/output
DEBUG         0
EOF

# Run assessment report
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t SHOW_REPORT

# Generate detailed migration report
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t SHOW_REPORT --estimate_cost

2.4 ora2pg complexity scoring

ora2pg rates migration complexity from A (trivial) to C (complex):

Rating Description Typical effort per object Auto-conversion rate
A Simple objects, direct translation Minutes 90%+
B- Moderate complexity, some manual fixes Hours 70-90%
B+ Significant PL/SQL, Oracle-specific features Days 50-70%
C Complex packages, Oracle-specific features Weeks 30-50%

2.5 Schema conversion

# Convert all schema objects
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t TABLE -o tables.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t VIEW -o views.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t SEQUENCE -o sequences.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t TRIGGER -o triggers.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t FUNCTION -o functions.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t PROCEDURE -o procedures.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t PACKAGE -o packages.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t TYPE -o types.sql
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t GRANT -o grants.sql

# Or convert everything at once
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t ALL -o full_schema.sql

3. PL/SQL to PL/pgSQL conversion patterns

PL/pgSQL is syntactically closer to PL/SQL than T-SQL, making PostgreSQL a natural migration target for PL/SQL-heavy codebases.

3.1 Function conversion

-- Oracle PL/SQL
CREATE OR REPLACE FUNCTION get_employee_salary(
    p_emp_id IN NUMBER
) RETURN NUMBER IS
    v_salary NUMBER(10,2);
BEGIN
    SELECT salary INTO v_salary
    FROM employees
    WHERE employee_id = p_emp_id;

    RETURN v_salary;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN NULL;
    WHEN TOO_MANY_ROWS THEN
        RAISE_APPLICATION_ERROR(-20001, 'Multiple employees found');
END get_employee_salary;
/
-- PL/pgSQL (Azure PostgreSQL)
CREATE OR REPLACE FUNCTION get_employee_salary(
    p_emp_id integer
) RETURNS numeric AS $$
DECLARE
    v_salary numeric(10,2);
BEGIN
    SELECT salary INTO STRICT v_salary
    FROM employees
    WHERE employee_id = p_emp_id;

    RETURN v_salary;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN NULL;
    WHEN TOO_MANY_ROWS THEN
        RAISE EXCEPTION 'Multiple employees found' USING ERRCODE = 'P0001';
END;
$$ LANGUAGE plpgsql;

Key differences:

PL/SQL PL/pgSQL Notes
RETURN type (function signature) RETURNS type Plural form
IS / AS AS $$ ... $$ LANGUAGE plpgsql Dollar-quoted body
NUMBER(p,s) numeric(p,s) Data type name
VARCHAR2(n) varchar(n) Data type name
IN / OUT parameter modes Same (IN/OUT/INOUT) Same concept
RAISE_APPLICATION_ERROR RAISE EXCEPTION Error handling
DBMS_OUTPUT.PUT_LINE RAISE NOTICE Debug output
SQL%ROWCOUNT GET DIAGNOSTICS row_count = ROW_COUNT or FOUND Row count after DML
EXECUTE IMMEDIATE EXECUTE Dynamic SQL

3.2 Package conversion

Oracle packages have no direct PostgreSQL equivalent. Convert to schemas with individual functions/procedures.

-- Oracle package spec
CREATE OR REPLACE PACKAGE hr_pkg AS
    c_max_salary CONSTANT NUMBER := 500000;

    FUNCTION validate_salary(p_salary NUMBER) RETURN BOOLEAN;
    PROCEDURE give_raise(p_emp_id NUMBER, p_pct NUMBER);
END hr_pkg;
/

CREATE OR REPLACE PACKAGE BODY hr_pkg AS
    FUNCTION validate_salary(p_salary NUMBER) RETURN BOOLEAN IS
    BEGIN
        RETURN p_salary > 0 AND p_salary <= c_max_salary;
    END;

    PROCEDURE give_raise(p_emp_id NUMBER, p_pct NUMBER) IS
        v_new_salary NUMBER;
    BEGIN
        SELECT salary * (1 + p_pct/100) INTO v_new_salary
        FROM employees WHERE employee_id = p_emp_id;

        IF NOT validate_salary(v_new_salary) THEN
            RAISE_APPLICATION_ERROR(-20002, 'Salary exceeds maximum');
        END IF;

        UPDATE employees SET salary = v_new_salary
        WHERE employee_id = p_emp_id;
    END;
END hr_pkg;
/
-- PL/pgSQL equivalent
CREATE SCHEMA IF NOT EXISTS hr_pkg;

-- Package constants become a configuration table or function
CREATE OR REPLACE FUNCTION hr_pkg.max_salary()
RETURNS numeric AS $$
BEGIN
    RETURN 500000;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE OR REPLACE FUNCTION hr_pkg.validate_salary(p_salary numeric)
RETURNS boolean AS $$
BEGIN
    RETURN p_salary > 0 AND p_salary <= hr_pkg.max_salary();
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE PROCEDURE hr_pkg.give_raise(
    p_emp_id integer,
    p_pct numeric
) AS $$
DECLARE
    v_new_salary numeric;
BEGIN
    SELECT salary * (1 + p_pct/100.0) INTO v_new_salary
    FROM employees WHERE employee_id = p_emp_id;

    IF NOT hr_pkg.validate_salary(v_new_salary) THEN
        RAISE EXCEPTION 'Salary exceeds maximum' USING ERRCODE = 'P0002';
    END IF;

    UPDATE employees SET salary = v_new_salary
    WHERE employee_id = p_emp_id;
END;
$$ LANGUAGE plpgsql;

3.3 Collection types

-- Oracle: Nested table type
CREATE OR REPLACE TYPE number_list AS TABLE OF NUMBER;
/

CREATE OR REPLACE FUNCTION sum_numbers(p_numbers number_list)
RETURN NUMBER IS
    v_sum NUMBER := 0;
BEGIN
    FOR i IN 1..p_numbers.COUNT LOOP
        v_sum := v_sum + p_numbers(i);
    END LOOP;
    RETURN v_sum;
END;
/
-- PL/pgSQL: Use arrays
CREATE OR REPLACE FUNCTION sum_numbers(p_numbers numeric[])
RETURNS numeric AS $$
DECLARE
    v_sum numeric := 0;
    v_num numeric;
BEGIN
    FOREACH v_num IN ARRAY p_numbers LOOP
        v_sum := v_sum + v_num;
    END LOOP;
    RETURN v_sum;
END;
$$ LANGUAGE plpgsql;

-- Or simpler with built-in aggregate:
-- SELECT sum(unnest) FROM unnest(p_numbers);

4. Extension ecosystem

PostgreSQL's extension ecosystem provides capabilities that Oracle charges separately for or does not offer.

Capability PostgreSQL extension Oracle equivalent Oracle cost
Spatial / GIS PostGIS Oracle Spatial $17,500/processor
Full-text search Built-in (tsvector) Oracle Text Included (EE)
Vector search / AI pgvector Oracle AI Vector Search (23ai) Included (23ai only)
Time-series TimescaleDB Manual partitioning Partitioning option ($11,500/proc)
Job scheduling pg_cron DBMS_SCHEDULER Included
Horizontal scale-out Citus RAC $23,000/processor
Columnar storage citus_columnar In-Memory option $23,000/processor
Graph Apache AGE Graph (23c+) Included (23c only)
Foreign data access postgres_fdw, oracle_fdw Database Links Included
Audit logging pgAudit Fine-Grained Auditing Included (EE)
Password management Custom pg_hba.conf + Entra ID Oracle profiles Included

4.1 Enabling extensions on Azure PostgreSQL

-- Enable extensions (Azure PostgreSQL Flexible Server)
-- Some extensions are pre-installed, others need allow-listing in Azure Portal

-- Check available extensions
SELECT * FROM pg_available_extensions ORDER BY name;

-- Enable PostGIS
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;

-- Enable pgvector for AI/RAG patterns
CREATE EXTENSION IF NOT EXISTS vector;

-- Enable pg_cron for scheduling
CREATE EXTENSION IF NOT EXISTS pg_cron;

-- Enable pgAudit for audit logging
-- (Must be enabled in server parameters first via Azure Portal)
CREATE EXTENSION IF NOT EXISTS pgaudit;

-- Enable Citus for distributed tables
-- (Available on Citus-enabled server configuration)
CREATE EXTENSION IF NOT EXISTS citus;

5. Citus for horizontal scaling

For Oracle RAC workloads that need horizontal scale-out, Citus on Azure PostgreSQL provides distributed table capabilities.

5.1 Citus architecture

                   ┌─────────────────┐
                   │   Coordinator   │
                   │   (Routes SQL)  │
                   └────────┬────────┘
            ┌───────────────┼───────────────┐
            │               │               │
    ┌───────┴──────┐ ┌──────┴───────┐ ┌─────┴──────┐
    │  Worker 1    │ │  Worker 2    │ │  Worker 3  │
    │  (Shards)    │ │  (Shards)    │ │  (Shards)  │
    └──────────────┘ └──────────────┘ └────────────┘

5.2 Converting Oracle RAC workloads to Citus

-- Distribute a high-volume table across workers
SELECT create_distributed_table('transactions', 'tenant_id');

-- Reference tables (small lookup tables replicated to all workers)
SELECT create_reference_table('transaction_types');
SELECT create_reference_table('currencies');

-- Queries run in parallel across all workers
SELECT tenant_id, COUNT(*), SUM(amount)
FROM transactions
WHERE transaction_date >= '2024-01-01'
GROUP BY tenant_id;
-- Citus automatically parallelizes and aggregates

6. Data migration with ora2pg

# Configure data migration in ora2pg.conf
# Add to existing config:
cat >> /opt/ora2pg/migration/ora2pg.conf << 'EOF'

# Data migration settings
PG_DSN     dbi:Pg:dbname=feddb;host=pg-flex.postgres.database.azure.com;port=5432
PG_USER    migration_admin
PG_PWD     ***

# Performance tuning
DATA_LIMIT     10000
JOBS           4
ORACLE_COPIES  4
PG_COPIES      4
DROP_FKEY      1
TRUNCATE_TABLE 1
EOF

# Migrate data (parallel)
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t COPY -j 4

# Or use INSERT mode for complex data types
ora2pg -c /opt/ora2pg/migration/ora2pg.conf -t INSERT -j 4

7. CSA-in-a-Box integration

7.1 ADF pipeline for PostgreSQL ingestion

Azure Database for PostgreSQL integrates with CSA-in-a-Box through Azure Data Factory pipelines.

{
    "name": "PostgreSQL_to_OneLake",
    "properties": {
        "activities": [
            {
                "name": "CopyPostgreSQLToLakehouse",
                "type": "Copy",
                "inputs": [
                    {
                        "referenceName": "AzurePostgreSQLSource",
                        "type": "DatasetReference"
                    }
                ],
                "outputs": [
                    {
                        "referenceName": "OneLakeDelta",
                        "type": "DatasetReference"
                    }
                ],
                "typeProperties": {
                    "source": {
                        "type": "AzurePostgreSqlSource",
                        "query": "SELECT * FROM app_schema.employees WHERE updated_at >= '@{pipeline().parameters.watermark}'"
                    },
                    "sink": {
                        "type": "LakehouseTableSink",
                        "tableActionOption": "Append"
                    }
                }
            }
        ]
    }
}

7.2 dbt source configuration

# domains/shared/dbt/models/sources.yml
sources:
    - name: oracle_migrated_pg
      description: "Oracle workloads migrated to Azure PostgreSQL"
      database: feddb
      schema: app_schema
      tables:
          - name: employees
            description: "Employee records (migrated from Oracle HR)"
            columns:
                - name: employee_id
                  tests: [not_null, unique]
          - name: departments
            description: "Department hierarchy (migrated from Oracle HR)"
          - name: transactions
            description: "Financial transactions (migrated from Oracle EBS)"

8. Post-migration validation

-- PostgreSQL: Validate row counts against Oracle source
SELECT schemaname, relname AS table_name,
       n_live_tup AS approximate_row_count
FROM pg_stat_user_tables
WHERE schemaname = 'app_schema'
ORDER BY relname;

-- Validate data types were correctly mapped
SELECT column_name, data_type, character_maximum_length,
       numeric_precision, numeric_scale
FROM information_schema.columns
WHERE table_schema = 'app_schema' AND table_name = 'employees'
ORDER BY ordinal_position;

-- Performance baseline
SELECT query, calls, mean_exec_time, total_exec_time,
       rows, shared_blks_hit, shared_blks_read
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

Maintainers: csa-inabox core team Last updated: 2026-04-30