Stored procedures, packages, functions, and triggers parsed structurally. Converted to BigQuery SQL procedures and scripting. 2,000+ built-in function mappings, full lineage, validated parity.
Upload PL/SQL, get converted code →BigQuery scripting supports DECLARE, SET, IF/ELSE, LOOP, and exception handling. MigryX maps Oracle's procedural blocks to BigQuery's BEGIN...EXCEPTION...END structure. Temporary tables replace PL/SQL collections. 280 packages converted in a single engagement.
BigQuery has first-class support for recursive CTEs with WITH RECURSIVE syntax. MigryX converts Oracle's START WITH...CONNECT BY PRIOR hierarchical queries preserving LEVEL semantics, path construction, and cycle detection -- no workarounds needed.
Oracle NUMBER, VARCHAR2, DATE, CLOB, and collection types map to BigQuery INT64, STRING, DATETIME, STRING, and ARRAY/STRUCT. MigryX handles precision mapping, implicit type coercion, and Oracle-specific NULL semantics that differ in BigQuery.
A PL/SQL procedure with cursor processing, DECODE, and Oracle date functions -- converted to a BigQuery SQL procedure with scripting, CASE WHEN, and native date handling.
-- Procedure: refresh_department_metrics
CREATE OR REPLACE PROCEDURE refresh_metrics AS
v_count NUMBER := 0;
CURSOR c_depts IS
SELECT department_id,
NVL(department_name, 'Unassigned'),
DECODE(status, 'A', 'Active',
'I', 'Inactive', 'Unknown')
FROM departments
WHERE create_date >=
ADD_MONTHS(SYSDATE, -6);
BEGIN
FOR rec IN c_depts LOOP
INSERT INTO dept_metrics (
dept_id, dept_name, status_label,
headcount, run_date
)
SELECT rec.department_id,
rec.department_name,
rec.status,
COUNT(*),
SYSDATE
FROM employees e
WHERE e.dept_id = rec.department_id;
v_count := v_count + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE(
'Processed: ' || TO_CHAR(v_count));
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
-- PL/SQL procedure → BigQuery SQL
CREATE OR REPLACE PROCEDURE
dataset.refresh_metrics()
BEGIN
DECLARE v_count INT64 DEFAULT 0;
INSERT INTO dataset.dept_metrics
(dept_id, dept_name, status_label,
headcount, run_date)
SELECT
d.department_id,
COALESCE(d.department_name,
'Unassigned'),
CASE d.status
WHEN 'A' THEN 'Active'
WHEN 'I' THEN 'Inactive'
ELSE 'Unknown'
END AS status_label,
COUNT(e.employee_id) AS headcount,
CURRENT_DATE() AS run_date
FROM dataset.departments d
LEFT JOIN dataset.employees e
ON e.dept_id = d.department_id
WHERE d.create_date >=
DATE_SUB(CURRENT_DATE(),
INTERVAL 6 MONTH)
GROUP BY d.department_id,
d.department_name, d.status;
SET v_count = (
SELECT COUNT(DISTINCT dept_id)
FROM dataset.dept_metrics
WHERE run_date = CURRENT_DATE());
EXCEPTION WHEN ERROR THEN
SELECT @@error.message;
END;
Cursor FOR loop replaced with set-based INSERT...SELECT with GROUP BY. DECODE mapped to CASE WHEN. NVL mapped to COALESCE. ADD_MONTHS mapped to DATE_SUB with INTERVAL. SYSDATE mapped to CURRENT_DATE(). Row-by-row processing eliminated.
| Oracle PL/SQL Component | BigQuery Equivalent | Notes |
|---|---|---|
| Stored Procedure | BigQuery SQL procedure | BEGIN/EXCEPTION scripting structure |
| Package (spec + body) | Dataset + routines | Package functions become individual routines |
| Function | BigQuery UDF (SQL or JS) | Scalar and table-valued functions |
| CONNECT BY hierarchical query | WITH RECURSIVE CTE | Native recursive CTE support |
| DECODE | CASE WHEN | Multi-branch DECODE fully expanded |
| NVL / NVL2 | COALESCE() / IF() | NVL2 mapped to IF with null check |
| Trigger | Scheduled query + Cloud Function | Event-driven logic via Pub/Sub |
| Sequence | GENERATE_UUID() / ROW_NUMBER() | Identity generation patterns mapped |
| Materialized View | BigQuery Materialized View | Direct equivalent with auto-refresh |
| DBMS_SCHEDULER | BigQuery scheduled query | Cron-based scheduling, Cloud Scheduler |
| EXECUTE IMMEDIATE | EXECUTE IMMEDIATE | Direct equivalent in BigQuery scripting |
| Cursor FOR loop | FOR...IN (SELECT ...) / set-based SQL | Row iteration or set-based conversion |
| Database Link | BigQuery connection / federated query | Cross-source access via BigLake |
| Collections (TABLE/VARRAY) | ARRAY / STRUCT | Nested types mapped to BigQuery native |
Data Matching compares Oracle output against BigQuery output -- row by row, column by column. In the case study below, all stored procedure results were validated with full production backtesting against the original Oracle database.
See how Data Matching works →3,800 PL/SQL objects converted to BigQuery SQL procedures and UDFs. 280 packages decomposed into BigQuery routines organized by dataset. CONNECT BY hierarchical queries rewritten as recursive CTEs. DECODE/NVL patterns mapped to CASE WHEN/COALESCE. Oracle license costs eliminated, serverless compute enabled zero-infrastructure operations.
Read the full case study →Upload a PL/SQL package or stored procedure. Get parsed lineage, BigQuery SQL code, and a validation report.