Case q02 · dispute
How should an unconstrained NUMBER column be mapped when migrating to Azure SQL Database versus Azure Database for PostgreSQL?
Expected: SSMA maps unconstrained NUMBER to float(53) by default (lossy for exact decimals); Ora2Pg's type table says numeric but its shipped defaults (PG_INTEGER_TYPE, DEFAULT_NUMERIC) emit bigint. Answer must present the disagreement and the recorded resolution (profile values, pin decimal/numeric explicitly). [expected corrected 2026-09-26 after research; question unchanged]
No tools
larger model · 1 turn · 29 s · 16.5k in / 2.9k out✗ verdict ✗ grounded ✓ cited
Grader: Omits Ora2Pg doc-vs-default conflict; unsupported specifics like 131,072 digits
Answer
Mapping an unconstrained Oracle NUMBER to Azure SQL Database vs. Azure Database for PostgreSQL
Short answer: PostgreSQL has an exact equivalent, unconstrained NUMERIC, so you can map the column safely without knowing what's in it. Azure SQL Database has no equivalent. There you need to profile the data and pick INT/BIGINT or a fixed DECIMAL(p,s). Neither default tool mapping should be accepted without checking: SSMA's default is float(53), and Ora2Pg's is bigint.
Why this column is hard to map
- An Oracle
NUMBERwith no precision or scale is an exact decimal with up to 38 significant digits and a floating scale (-84 to 127). The same column can hold42,3.14159and1E-30. - Unconstrained
NUMBERis very common in legacy schemas. It is often used for IDs filled from sequences, and just as often for money or ratios.
Azure SQL Database (T-SQL)
- SSMA default is
float(53). That holds only about 15–17 significant digits and is an approximate binary type. Large IDs and financial values can lose precision without any error, and equality joins and comparisons can behave differently. Treat this default as a placeholder, not a decision. DECIMAL/NUMERICis capped at 38 digits with a fixed scale.DECIMAL(38,10)gives you 28 integer digits and 10 fractional digits. No singleDECIMAL(p,s)covers the full Oracle range.- What to do: profile each column, then map it:
- Integers only (for example, sequence-populated PKs and FKs): use
BIGINT(orINT). This is the most common result and the best for performance and indexing. - Fractional values with a known maximum precision and scale: use
DECIMAL(p,s)sized to what you observed plus some headroom. - Genuinely scientific or approximate data:
float(53)is acceptable, as a deliberate choice.
- Integers only (for example, sequence-populated PKs and FKs): use
- You can override the mapping per column or type in SSMA (Project Settings → Type Mapping) and in the Azure migration tooling.
- Quick profiling query in Oracle:
SELECT MIN(col), MAX(col), SUM(CASE WHEN col <> TRUNC(col) THEN 1 ELSE 0 END) AS non_integers, MAX(LENGTH(TO_CHAR(ABS(col)))) AS max_chars FROM t;
Azure Database for PostgreSQL
- Unconstrained
NUMERICis a faithful, lossless match. It supports up to 131,072 digits before the decimal point and 16,383 after, so every Oracle value converts exactly. - Ora2Pg default trap: with
PG_INTEGER_TYPE=1, an unconstrainedNUMBERbecomesDEFAULT_NUMERIC, which defaults tobigint. Fractional values then get rounded silently:1.5is stored as2. SetDEFAULT_NUMERIC numericunless you have confirmed the column is integer-only. - Performance trade-off:
numericis slower and larger thanbigint. For integer-only key columns, still map tobigint, and do the same on the matching FK columns so join types stay consistent. Usenumericeverywhere else.
Recommended rule
| Profiled content | Azure SQL DB | Azure PostgreSQL |
|---|---|---|
| Integers only (IDs, counts) | BIGINT / INT |
bigint / integer |
| Fixed-scale decimals (money, quantities) | DECIMAL(p,s) sized from profiling |
numeric(p,s) or unconstrained numeric |
| Unknown or mixed scale | Profile it; DECIMAL(38,s) best fit; float(53) only if approximate values are acceptable |
Unconstrained numeric (safe default) |
After the load, validate row-level checksums or SUM/MIN/MAX per numeric column to catch any rounding.
Sources
- Oracle
NUMBERsemantics: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html - SSMA for Oracle type mapping: https://learn.microsoft.com/en-us/sql/ssma/oracle/project-settings-type-mapping-oracletosql
- T-SQL
decimal/numeric: https://learn.microsoft.com/en-us/sql/t-sql/data-types/decimal-and-numeric-transact-sql - PostgreSQL numeric types: https://www.postgresql.org/docs/current/datatype-numeric.html
- Ora2Pg
PG_INTEGER_TYPE/DEFAULT_NUMERIC: https://ora2pg.darold.net/documentation.html
Keyword search
larger model · 1 turn · 12 s · 19.0k in / 1.1k out✓ verdict ✓ grounded ✗ cited
Grader: Both defaults, numeric-vs-bigint conflict, profile-and-pin resolution
8 keyword-search passages
caveat_number-ssma-float53score 24.845mapping_number-unconstrained--azure-pg-flexscore 23.095dispute_number-unconstrained-float-vs-numericscore 18.918mapping_number-unconstrained--azure-sql-dbscore 16.201feature_vector-datatypescore 13.819caveat_number-ora2pg-bigint-defaultscore 13.579target_azure-dmsscore 13.193source_ms-adf-oracle-connectorscore 13.069
Answer
Short answer: Don't accept either tool's default. Check the real values in each column first, then set an exact type yourself: decimal(p,s)/numeric(p,s) on Azure SQL Database, numeric(p,s) on Azure Database for PostgreSQL. Use an integer type only when the data never has fractions [3].
Azure SQL Database (SSMA)
- By default, SSMA maps an unconstrained
NUMBERtofloat(53). The same default applies to arguments, return values and local variables. OnlyNUMBERwith an explicit precision becomesnumeric[1]. float(53)is binary floating point. It can't store most decimal fractions exactly, so money amounts and IDs can drift [1][4].- An Oracle
NUMBERis an exact decimal with up to 38 significant digits. The safe target isdecimal/numericwith a precision and scale chosen from the data, orbigintif the column only ever held integers [4]. - What to do:
- For each column, measure the most digits before and after the decimal point.
- On SSMA's Type Mapping page (project or table level), change
float[53]tonumeric(p,s)orbigintbefore you run Convert Schema. - Keep
floatonly for real scientific measurements. - After migration, compare
SUM()andCOUNT(DISTINCT)on key columns between source and target [3][4].
Azure Database for PostgreSQL (Ora2Pg)
- PostgreSQL
numericis exact and effectively unbounded, so it can hold any OracleNUMBERvalue. Ora2Pg's documented type table mapsNUMBERto it [2]. - The catch is Ora2Pg's shipped settings.
PG_INTEGER_TYPEis on by default andDEFAULT_NUMERICdefaults tobigint, so an out-of-the-box run outputsbigint. The fractional digits are then silently dropped at load time. This matches the Ora2Pg 25.0 source code [2][6]. - What to do:
- Run
SHOW_COLUMNand look for columns marked#for review. - Set
DEFAULT_NUMERICtonumeric, or turn offPG_INTEGER_TYPE, for schemas that have decimals in unconstrained columns. - Use
MODIFY_TYPEfor individual columns that should be integer types for speed. - After loading, check the data with
TEST_DATAor checksums [2].
- Run
Related points
- Azure DMS has no Oracle-to-PostgreSQL scenario. For Oracle to Azure SQL, it relies on SSMA for schema conversion, so the SSMA default above still applies [7].
- If you move data with Azure Data Factory, its Oracle connector maps
NUMBERwithout precision toDecimal(256,130)in v2.0 andDoublein v1.0. Avoid v1.0 for these columns (https://learn.microsoft.com/en-us/azure/data-factory/connector-oracle) [8].
Sanity Context · GROQ
smaller model · 5 turns · 26 s · 45.0k in / 2.1k out✓ verdict ✓ grounded ✓ cited
Grader: Complete dispute and resolution, both tool defaults correctly stated
4 tool calls
- initial_context on groq · 4,126 chars back
- groq_query on groq · 616 chars back
*[_type=="oracleFeature" && category=="datatype" && (name match "*NUMBER*" || slug.current match "*number*")]{_id, name, "slug": slug.current, introducedIn, deprecatedIn, desupportedIn} - groq_query on groq · 6,133 chars back
*[_type=="mapping" && oracleFeature._ref=="feature_number-unconstrained"]{ _id, fidelity, effort, rationale, appliesToOracleVersions, "azureTarget": azureTarget->{name, service, tierNotes}, "sources": sources[]->{title, url}, "caveats": *[_type=="caveat" && references(^._id)]{ severity, statement, appliesTo, firsthand, "evidence": evidence->{title, url} } } - groq_query on groq · 1,715 chars back
*[_type=="dispute" && (claimA._ref in ["caveat_number-ora2pg-bigint-default","caveat_number-ora2pg-numeric","caveat_number-ssma-float53"] || claimB._ref in ["caveat_number-ora2pg-bigint-default","caveat_number-ora2pg-numeric","caveat_number-ssma-float53"])]{title, whatDisagrees, resolution, "resolvedBy": resolvedBy->title}
Answer
Verdict: Neither tool's default is safe as-is — an unconstrained Oracle NUMBER (exact decimal, up to 38 significant digits) needs an explicitly profiled decimal(p,s)/numeric(p,s) on both targets; the out-of-the-box conversion tools silently pick lossy or inconsistent defaults.
Azure SQL Database
- Fidelity: partial (effort S). Oracle
NUMBERw/o precision/scale is exact decimal up to 38 digits; the safe target isdecimal/numericwith explicit precision/scale from the actual data, orbigintif the column only ever held integers. — Oracle to Azure SQL Database: migration guide - Caveat (major): SSMA for Oracle's default type mapping converts unconstrained
NUMBERtofloat(53)(binary floating point) — onlyNUMBERwith explicit precision maps tonumericby default — so exact monetary/identifier values can silently drift unless you override the mapping. — SSMA for Oracle: Project Settings (Type Mapping) - Applies to Oracle 11.2–26ai (all versions listed in the mapping).
Azure Database for PostgreSQL (Flexible Server)
- Fidelity: partial (effort S). PostgreSQL
numericis exact and effectively unbounded, matching OracleNUMBER's semantics — that's Ora2Pg's base type-table mapping. — Ora2Pg documentation (configuration directives) - Caveat (major): Ora2Pg ships with
PG_INTEGER_TYPEenabled by default, which routes unconstrainedNUMBERtoDEFAULT_NUMERIC(default =bigint), silently dropping fractional digits at load time unless you disablePG_INTEGER_TYPEor setDEFAULT_NUMERICtonumeric. — Ora2Pg source code: lib/Ora2Pg.pm and lib/Ora2Pg/Oracle.pm - Applies to Oracle 11.2–26ai.
Dispute: "Unconstrained NUMBER: float(53), bigint, or numeric?"
- Claim A (Microsoft/SSMA): default conversion →
float(53), which cannot represent most decimal fractions exactly. - Claim B (Ora2Pg): documented type table says
numeric, but shipped defaults (PG_INTEGER_TYPEon,DEFAULT_NUMERIC=bigint) actually emitbigint, dropping fractions. - Recorded resolution (per Ora2Pg documentation, the resolving source): "Trust none of the defaults." Profile the real data (since unconstrained
NUMBERhas no dictionary-enforced bound) to find actual max precision/scale, then pin an explicit type:decimal(p,s)on Azure SQL,numeric(p,s)on PostgreSQL, or a true integer type only if sampling shows no fractional values. For Ora2Pg, overrideDEFAULT_NUMERICor disablePG_INTEGER_TYPE; for SSMA, edit the Type Mapping page before running Convert Schema.
No gaps: both mappings, both caveats, and the governing dispute are all present in the dataset for this feature.
GROQ + Knowledge Base
smaller model · 6 turns · 35 s · 62.4k in / 2.6k out✓ verdict ✓ grounded ✓ cited
Grader: Explicit dispute with Claims A and B, recorded resolution quoted
5 tool calls
- initial_context on groq · 4,126 chars back
- initial_context on kb · 7,679 chars back
- groq_query on groq · 1,774 chars back
*[_type=="oracleFeature" && category=="datatype" && name match "*NUMBER*"]{_id, name, slug, category, summary} - groq_query on groq · 5,337 chars back
*[_type=="mapping" && oracleFeature->slug.current=="number-unconstrained"]{ _id, fidelity, effort, rationale, appliesToOracleVersions, "azureTarget": azureTarget->{name, slug, service}, "sources": sources[]->{title, url}, "caveats": *[_type=="caveat" && references(^._id)]{ severity, statement, appliesTo, firsthand, "evidence": evidence->{title, url}, "_id": _id } } - groq_query on groq · 3,247 chars back
*[_type=="dispute" && (claimA->_id in ["caveat_number-ssma-float53","caveat_number-ora2pg-bigint-default","caveat_number-ora2pg-numeric"] || claimB->_id in ["caveat_number-ssma-float53","caveat_number-ora2pg-bigint-default","caveat_number-ora2pg-numeric"])]{ title, whatDisagrees, resolution, "claimA": claimA->{statement, "src": evidence->{title,url}}, "claimB": claimB->{statement, "src": evidence->{title,url}}, "resolvedBy": resolvedBy->{title,url} }
Answer
Verdict: An unconstrained Oracle NUMBER (no precision/scale) has partial fidelity on both Azure SQL Database and Azure Database for PostgreSQL Flexible Server — neither tool's default produces an exact match, and the right answer is to profile the data rather than trust the default converter. Applies to Oracle 11.2 through 26ai (core SQL type, not deprecated) — NUMBER without precision/scale summary (per the oracleFeature record).
Azure SQL Database
- Fidelity: partial (effort S). Oracle's bare
NUMBERis exact, up to 38 significant digits with a floating decimal point. SSMA for Oracle's default column mapping converts it tofloat(53)— SQL Server's binary double — which cannot represent most decimal fractions exactly; SSMA only maps tonumericwhen the sourceNUMBERhas an explicit precision. SSMA for Oracle: Project Settings (Type Mapping) - Recommended target:
decimal(p,s)/numeric(p,s)with precision/scale chosen from actual data profiling, orbigintonly if the sampled data is confirmed to hold no fractions. Oracle to Azure SQL Database: migration guide
Azure Database for PostgreSQL (Flexible Server)
- Fidelity: partial (effort S), even though PostgreSQL's
numerictype is arbitrary-precision and exact and is a natural fit in principle. The trap is tooling defaults, not the type itself:- Ora2Pg's documented base type table maps
NUMBER→numeric, but itsPG_INTEGER_TYPEdirective is enabled by default and routes an unconstrainedNUMBERtoDEFAULT_NUMERIC, which itself defaults tobigint— silently dropping fractional digits at load time. Ora2Pg documentation (configuration directives) - This is confirmed at the source-code level:
Ora2Pg.pmsetspg_integer_type=1anddefault_numeric=bigintwhen unset, andOracle.pm's resolver returnsdefault_numericfor a no-precision/no-scaleNUMBER, flagging the column with#inSHOW_COLUMNoutput for manual review. Ora2Pg source code: lib/Ora2Pg.pm and lib/Ora2Pg/Oracle.pm
- Ora2Pg's documented base type table maps
- Recommended target: explicit
numeric(p,s), achieved by disablingPG_INTEGER_TYPEor settingDEFAULT_NUMERICtonumeric.
Dispute: "Unconstrained NUMBER: float(53), bigint, or numeric?"
The dataset records an explicit three-way tooling disagreement over this exact case:
- Claim A (Microsoft, via SSMA docs): default converts to
float(53), losing exactness for fractions. - Claim B (Ora2Pg source, community): documented type table says
numeric, but shipped defaults emitbigint, dropping fractions entirely. - Resolution (per Ora2Pg documentation (configuration directives), the resolving source): "Trust none of the defaults. Profile the real values (unconstrained NUMBER has no dictionary bound, so sample the maximum precision and scale in the data), then pin an exact type explicitly: decimal(p,s) on Azure SQL, numeric(p,s) on PostgreSQL, or a true integer type only when the sample shows no fractions. In Ora2Pg that means overriding DEFAULT_NUMERIC or disabling PG_INTEGER_TYPE; in SSMA it means editing the Type Mapping page before Convert Schema."
No caveats in the dataset restrict this by Oracle edition or by Azure tier beyond what's stated above.