ROWID / UROWID
Survived by a workaround.
✓ certified by the mortician
In life
ROWID is the pseudocolumn and data type that gives a row's physical address in a heap table: data object number, file, block and slot. UROWID is the universal form that also covers logical rowids for index-organized tables and rowids of foreign tables. Oracle warns that a rowid can change, for example when a row moves, so storing rowids for later use yields unpredictable results.
Migrations care because rowids are used in idioms that have no target equivalent: de-duplication with MIN(ROWID), bulk-update loops that re-fetch by rowid, and application frameworks that cache rowids as row handles. SQL Server exposes no stable physical row address, and PostgreSQL's system column changes on every update, so each idiom needs a key-based rewrite.
The data type is supported and not deprecated.
Cause of departure
Not deprecated by Oracle. Listed here because Azure offers no clean equivalent.
Obituary
ROWID / UROWID, born before 11.2, served as Oracle’s physical row address exposed to SQL. It located rows and supported storage-position ordering, duplicate elimination, pagination, trigger correlation, and batching.
It departed because Azure offers no faithful equivalent: PostgreSQL’s ctid changes after updates or VACUUM FULL, while Azure SQL’s uniqueidentifier is a GUID, not a physical address. Ora2Pg’s default oid mapping can also fail during import.
It is survived by Azure Database for PostgreSQL Flexible Server, only by workaround, and Azure SQL Database, only by workaround. The contested account advises mapping no type at all: rewrite uses around primary keys, adding deliberate surrogates only for keyless tables.
Survived by
Azure Database for PostgreSQL Flexible Server workaround effort M
PostgreSQL's ctid is a physical row locator like ROWID, but it changes on every UPDATE and after VACUUM FULL, so it is unusable as an identifier beyond a single statement. Ora2Pg maps ROWID to oid by default and warns that this fails during data import, recommending bigserial, text or uuid instead. Code should be rewritten around primary keys or an identity column.
- Override the Ora2Pg DATA_TYPE for ROWID and UROWID to bigint (identity) or uuid.
- Add a generated identity or uuid column to tables whose rows lacked a natural key.
- Replace ROWID-based duplicate deletion with ctid only inside a single statement, or with ROW_NUMBER().
- Remove ROWID columns from result sets consumed by applications.
Complications
Azure SQL Database workaround effort M
ROWID is a physical row address exposed to SQL; SQL Server has no such value, so SSMA maps the type to uniqueidentifier and can add a ROWID column defaulting to NEWID() with a unique index, which gives applications a surrogate row identity but not the physical addressing, fast-access or duplicate-elimination tricks Oracle code used ROWID for. Queries that used ROWID to locate the current row should use the primary key instead.
- Find ROWID and UROWID usage in code (SELECT ROWID, WHERE ROWID =, DELETE duplicates by ROWID).
- Decide whether SSMA should generate ROWID columns (default only for tables with triggers) and whether a unique index is wanted.
- Rewrite duplicate-removal and cursor-update patterns to use primary keys or ROW_NUMBER().
- Drop the generated ROWID column once the application no longer references it.
Complications
Contested accounts
What does ROWID become: uniqueidentifier or oid?
SSMA turns ROWID into uniqueidentifier, a random 16-byte GUID. Ora2Pg turns it into oid and its own documentation warns the data import will then fail. The two converters do not merely differ; neither produces something with ROWID's meaning, because ROWID is a physical row address that no other engine exposes.
SSMA maps ROWID and UROWID to uniqueidentifier, a 16-byte GUID generated by newid(), which is not a physical row address and cannot be used to locate or order rows by storage position.
Ora2Pg converts ROWID and UROWID to oid by default and its documentation warns that this causes an error during data import because there is no equivalent type; it recommends overriding DATA_TYPE to bigserial, text or uuid.
Resolution. Do not map ROWID as a type at all. Find every use (dedupe by ROWID, MIN(ROWID) tricks, pagination, trigger correlation, ROWID-based batching) and rewrite it against the primary key or a surrogate key. Only when a table genuinely has no key add an explicit surrogate: uniqueidentifier with a unique index on Azure SQL, or bigserial or uuid on PostgreSQL, and override the converter's default so the column is created deliberately rather than by accident. (Data Types (SQL Language Reference 19c))
Notices of correction
- oracle Data Types (SQL Language Reference 19c) · SQL Language Reference 19c · read 2026-09-26
- oracle Tables and Table Clusters (Database Concepts 19c) · Database Concepts 19c, E96138-11, Sep 2025 · read 2026-09-26
- community Ora2Pg documentation (configuration directives) · Ora2Pg 25.0 (changelog 2025-04-20); text read from doc/Ora2Pg.pod in the GitHub repository · read 2026-09-26
- community PostgreSQL documentation: System columns (ctid) · PostgreSQL 18 · read 2026-09-26
- microsoft SSMA for Oracle: Project Settings (Type Mapping) · SSMA for Oracle, page updated 2026-08-27 · read 2026-09-26
- microsoft SSMA for Oracle: Project Settings (Conversion) · SSMA for Oracle, page updated 2026-08-27 · read 2026-09-26