Oracle to Snowflake data type mapping: every column, and the eight places Snowflake's own two documents disagree about it
12 min read Migration The Adapters team
Last updated September 2026
Field mapping auto-plugged · tap a port to rewire
Plug a source port into
Transform on this cable
JSON in
JSON out
5 sample records ready
Snowflake publishes two Oracle to Snowflake type mappings. One lives in the SnowConvert translation reference, the other in the Openflow connector documentation, and they disagree about the most common numeric column in an Oracle schema. An Oracle NUMBER declared with no precision becomes NUMBER(38,19) under one and NUMBER(38,18) under the other. That one digit decides whether a twenty digit value loads or overflows. Below is every mapping, every place the two documents part company, and the query that proves each risky conversion is safe, run against Oracle before the load rather than against Snowflake after go-live.
Key takeaways
- NUMBER with no precision has two official targets. NUMBER(38,19) in the connector docs, NUMBER(38,18) in the SnowConvert reference. Nineteen integer digits versus twenty.
- Oracle scale runs from -84 to 127, Snowflake from 0 to 37. Anything outside that window is dropped, rounded away, or stored as text.
- Oracle DATE has no fractional seconds and no time zone. It arrives in Snowflake with nine fractional digits it never had.
- Any type the connector does not list becomes TEXT. No warning, no error, and old Oracle schemas are full of candidates.
- Tables with no key cannot be replicated at all. The connector needs a primary key, unique constraint, unique index or a declared logical key.
- LOB ceilings default to 16 MB and 8 MB. Oracle LOBs run far past that, and the limits are raisable but not raised for you.
Oracle to Snowflake data type mapping, in one sentence
Most Oracle types have an honest Snowflake target and a converter will pick it without complaint. Sort the rest into three piles. Pile one is genuinely clean: VARCHAR2, CHAR under 4000 bytes, TIMESTAMP WITH TIME ZONE, TIMESTAMP WITH LOCAL TIME ZONE, RAW, JSON and the 23ai BOOLEAN all have real equivalents, and JSON improves in the move because Snowflake VARIANT is a better home for it than an Oracle LOB. Pile two has no target at all and both Snowflake documents say so: intervals, ANYDATA, ROWID, object types. Pile three is the one that costs money. Those columns convert, load, reconcile on row counts, and mean something different afterwards.
Pile three is invisible to the tooling for a structural reason. A converter can tell you a type has no target, because that is a fact about its own mapping table. It cannot tell you that a NUMBER column with no declared precision holds twenty digit values, or that a CHAR column has been quietly relying on blank padding for equality. Those are facts about your data, and the only place the evidence still exists is the Oracle database you are about to demote to a source.
Why do two Snowflake documents disagree about the same Oracle column?
Because they were written for different jobs. The SnowConvert reference exists to translate DDL, PL/SQL and views, so it thinks in terms of what a generated CREATE TABLE statement should say, and it prefers to keep source semantics where it can. The Openflow connector documentation describes what a running replication pipeline actually writes into a table, so it commits to one concrete physical type per source type and says so. Neither document is lying. They answer different questions, and if you read only one you end up with a different schema than a colleague who read the other.
The practical consequence is that "we follow Snowflake's documented mapping" is not an answer to any question you should be asking a vendor on this route. There are two documented mappings. Ask which one, and ask specifically about NUMBER with no precision, about DATE, and about intervals, because those are the three where the answer changes your schema. Where the two disagree, the connector documentation is usually the more literal and the more useful, because it describes behavior rather than intent.
The full Oracle to Snowflake data type mapping table
Read from the SnowConvert Oracle built-in data types reference and the Openflow Connector for Oracle data mapping page on 4 September 2026, checked against the Oracle Database 23ai SQL Language Reference and the Snowflake SQL reference the same day. Where the two Snowflake documents differ, both targets are printed and the disagreement is adjudicated below.
| Oracle | Snowflake | Note |
|---|---|---|
| NUMBER (no precision) | NUMBER(38,19) or NUMBER(38,18) | The two Snowflake documents differ by one digit of scale. See the disagreement table |
| NUMBER(p) | NUMBER(p) | Clean while p is 38 or under, which Oracle guarantees since its own maximum is 38 |
| NUMBER(p,s) | NUMBER(p,s) | Clean while s is 37 or under. Oracle allows scale up to 127, Snowflake stops at 37 |
| NUMBER(p,-s) | NUMBER(p) or TEXT | Snowflake has no negative scale. One tool drops it, the other falls back to text |
| FLOAT, FLOAT(p) | FLOAT | Both 64 bit IEEE 754 in Snowflake. SnowConvert warns of small rounding differences |
| BINARY_FLOAT | FLOAT | Widens from 4 bytes to 8. Sums stop matching a source that kept 4 byte arithmetic |
| BINARY_DOUBLE | FLOAT | Clean. Both are 64 bit binary floating point |
| CHAR(n), NCHAR(n) | TEXT | Blank padding stops being a property of the type. See the verification table below |
| VARCHAR2(n), NVARCHAR2(n) | TEXT | Clean under 4000 bytes. Extended sizes are a separate problem, see below |
| VARCHAR2 above 4000 bytes | TEXT | SnowConvert records this as not supported and ignores MAX_STRING_SIZE entirely |
| CLOB, NCLOB | TEXT | Connector default accepts 16 MB. Oracle LOBs run to terabytes. Raisable, not raised |
| LONG | TEXT or VARCHAR | Oracle allows 2 GB, Snowflake 128 MB, the connector defaults to 16 MB. Three ceilings |
| BLOB | BINARY | Connector default accepts 8 MB against an Oracle LOB measured in terabytes |
| RAW(n) | BINARY | Clean. Extended RAW above 2000 bytes is treated as ordinary RAW |
| LONG RAW | BINARY | Same 8 MB connector default. Oracle allows 2 GB in this column |
| DATE | TIMESTAMP_NTZ or TIMESTAMP | The single most common column on the route, and the two docs word it differently |
| TIMESTAMP(n) | TIMESTAMP_NTZ or TIMESTAMP(n) | Oracle defaults to 6 fractional digits, Snowflake to 9. See below |
| TIMESTAMP WITH TIME ZONE | TIMESTAMP_TZ | Both documents agree, and both are right. One of the safe rows |
| TIMESTAMP WITH LOCAL TIME ZONE | TIMESTAMP_LTZ | Both documents agree. The value is normalized to the session zone, as in Oracle |
| INTERVAL YEAR TO MONTH | TEXT or VARCHAR(20) | One is unbounded text, the other is capped at 20 characters. See below |
| INTERVAL DAY TO SECOND | TEXT or VARCHAR(20) | Same cap, and the two tools write the duration in different text formats |
| BOOLEAN (23ai) | BOOLEAN | Clean, and only the connector documents it. SnowConvert has no row for it |
| JSON (21c and later) | VARIANT | A genuine improvement. Access members with the colon operator, not a dot |
| XMLTYPE | TEXT | Becomes a string. XPath and XMLTABLE queries need rewriting against Snowflake functions |
| ROWID, UROWID | TEXT | Physical row addresses that mean nothing in Snowflake. Drop them rather than carry them |
| ANYDATA, ANYTYPE | Not supported | SnowConvert states outright that ANYDATA is not supported in Snowflake |
| SDO_GEOMETRY, user defined types | TEXT | Falls through the connector default. Rebuild as GEOGRAPHY from WKT, or as an object |
| Anything else | TEXT | The connector maps every unlisted Oracle type to TEXT and raises nothing |
Eight places Snowflake's two Oracle mappings disagree
Each row quotes what both documents say, then settles it against the primary reference for whichever product is being described. This is the part of the route no tool will surface for you, because each tool is internally consistent. The disagreement only appears when you put both documents on the same desk.
NUMBER with no precision
What each document says: The Openflow connector maps an undefined-precision Oracle NUMBER to NUMBER(38, 19). SnowConvert maps the same column to NUMBER(38, 18). One digit of scale, two Snowflake documents, same route.
Checked against the primary source: Oracle states that for NUMBER "the absence of precision and scale designators specifies the maximum range and precision for an Oracle number", which is 38 significant digits with scale from -84 to 127. Snowflake caps precision at 38 and scale at 37. Since both mappings spend the full 38 digits, the scale figure decides how many digits are left for the integer part: 18 leaves 20, 19 leaves 19. A value with 20 digits before the decimal point therefore fits one Snowflake mapping and overflows the other.
What to do: Measure the real magnitude on the source, then declare NUMBER(38,s) yourself rather than accepting either default
NUMBER with negative scale
What each document says: SnowConvert converts NUMBER(5,-2) to NUMBER(5), noting that "Snowflake does not allow negative scale, so it is being removed" and that this may cause functional inequivalence. The Openflow connector says only that when precision or scale exceeds Snowflake limits "the value is stored as TEXT".
Checked against the primary source: Oracle allows scale down to -84, and a negative scale rounds to the left of the decimal point, so NUMBER(5,-2) stores values rounded to the nearest hundred. Dropping the scale keeps the digits and drops the rounding rule, which is a silent change in meaning. Falling back to TEXT keeps the value and destroys arithmetic. They are different failures for one source column.
What to do: Find these columns first. There are usually under ten of them, and each needs a human decision, not a default
DATE
What each document says: The Openflow connector pins Oracle DATE to TIMESTAMP_NTZ. SnowConvert emits a bare TIMESTAMP.
Checked against the primary source: Those are the same thing only while a session parameter holds its default. Snowflake documents TIMESTAMP as "a user-specified alias associated with one of the TIMESTAMP_* variations", resolved by TIMESTAMP_TYPE_MAPPING, which defaults to TIMESTAMP_NTZ but is commonly set to TIMESTAMP_LTZ by teams that want local time rendering. Set it that way and a SnowConvert-generated schema and an Openflow-replicated one disagree about your single most common column type.
What to do: Set TIMESTAMP_TYPE_MAPPING deliberately at account level, and declare TIMESTAMP_NTZ explicitly rather than relying on the alias
INTERVAL columns
What each document says: SnowConvert maps both interval types to VARCHAR(20) and rewrites the value into a shorthand such as "1d, 2h, 3m, 4s". The Openflow connector maps them to unbounded TEXT.
Checked against the primary source: Two different string formats for one source column, and one of them carries a length cap. Snowflake has no interval column type to map to, so text is the only honest answer, but a downstream parser written against one format silently produces nulls against the other. The VARCHAR(20) cap is the sharper edge, since a long duration written out in that shorthand runs past twenty characters.
What to do: Store the duration as a NUMBER of seconds plus a unit column and rewrite the arithmetic once, rather than parsing either text format forever
LONG and the LOB family
What each document says: The connector maps LONG, CLOB and NCLOB to TEXT with a default ceiling of 16 MB, and BLOB and LONG RAW to BINARY with a default ceiling of 8 MB. SnowConvert maps LONG to VARCHAR and notes it must be cast through TO_LOB().
Checked against the primary source: Oracle allows 2 GB in a LONG column and far more in a LOB. Snowflake VARCHAR reaches 128 MB. The connector defaults are lower again at 16 MB and 8 MB, raisable but not raised for you. That is three separate ceilings on one column, and the rows that hit the lowest one are your largest documents, which are exactly the rows nobody spot checks after a load.
What to do: Measure MAX(DBMS_LOB.GETLENGTH(col)) on the source and raise the connector limit before the first load, not after
Extended VARCHAR2 and RAW
What each document says: SnowConvert records VARCHAR2 above 4000 bytes, NVARCHAR2 above 4000 bytes and RAW above 2000 bytes as "Not supported in Snowflake", transforms them as ordinary types, and states that the MAX_STRING_SIZE parameter is not recognized.
Checked against the primary source: Oracle documents exactly those thresholds: VARCHAR2 and RAW reach 32767 bytes when MAX_STRING_SIZE is EXTENDED, against 4000 and 2000 bytes when it is STANDARD. Snowflake TEXT holds far more than either, so the target is never the constraint here. The risk is that the converter has stopped reading the setting that told it how wide the source column really is, so any length assertion it generates is based on the standard limit.
What to do: Read MAX_STRING_SIZE from the source database yourself and treat any generated length as unverified
BOOLEAN and JSON
What each document says: The Openflow connector lists BOOLEAN mapping to BOOLEAN and JSON mapping to VARIANT. The SnowConvert built-in type table carries neither row.
Checked against the primary source: Both types are real and recent on the Oracle side, JSON from 21c and BOOLEAN from 23ai, so the gap is a documentation lag rather than a disagreement. It still matters, because SnowConvert states that anything outside its table has no rule, and the connector states that "any Oracle data types not listed in this table are mapped to TEXT by default". Read only the older document and a real boolean arrives as a string.
What to do: Declare BOOLEAN and VARIANT explicitly. These are the two rows where the newer document is the right one
Everything the tables do not list
What each document says: The connector is explicit: "Any Oracle data types not listed in this table are mapped to TEXT by default." SnowConvert simply has no row.
Checked against the primary source: On an Oracle schema of any age that catch-all is doing real work, because Oracle estates accumulate object types, collections, SDO_GEOMETRY, ANYDATA and ROWID columns that no mapping table covers. The load does not fail. Each one arrives as a string, and the failure surfaces months later as a report that cannot aggregate a column everyone assumed was numeric.
What to do: List every non-scalar type in ALL_TAB_COLUMNS before the migration, and decide each one on purpose
Eight risky conversions, and the query that proves each one is safe
For each risky conversion there is a query you can run against Oracle today, before you load anything, that tells you whether the risk is theoretical or already sitting in your data. All eight run in an afternoon and they are the cheapest work in the whole migration. Replace YOUR_SCHEMA with the owner you are migrating. Before you retype a column, it is also worth knowing which reports and downstream jobs read it, because tracing what depends on a column turns an argument about the correct target type into a short list of the people affected.
NUMBER columns with no declared precision
This is the row where Snowflake contradicts itself, and it is also the most common numeric column in an Oracle schema, because NUMBER with no arguments is what most legacy DDL contains. One Snowflake mapping leaves you 19 digits before the decimal point and the other leaves 20. Neither tells you which side of that line your data sits on.
SELECT owner, table_name, column_name
FROM all_tab_columns
WHERE data_type = 'NUMBER'
AND data_precision IS NULL
AND owner = 'YOUR_SCHEMA'
ORDER BY table_name, column_name;
How to read the result: Every row is a column whose target precision is being guessed for you. For the ones that hold real magnitudes, invoices, quantities, identifiers, run SELECT MAX(LENGTH(TRUNC(ABS(col)))) FROM tbl to get the widest integer part actually present. Under 19 and either mapping is safe. At 19 or 20 you have found a column that loads under one Snowflake document and fails under the other.
NUMBER columns with negative scale
Oracle allows scale from -84 to 127 and Snowflake allows 0 to 37. A negative scale is a rounding rule, not a formatting choice: NUMBER(5,-2) holds values rounded to the nearest hundred. SnowConvert removes the negative scale and flags functional inequivalence. The connector may instead store the column as text.
SELECT owner, table_name, column_name,
data_precision, data_scale
FROM all_tab_columns
WHERE data_scale < 0
AND owner = 'YOUR_SCHEMA'
ORDER BY table_name;
How to read the result: Most schemas return nothing here and you can move on in a minute. If rows come back, each one needs a decision rather than a default: keep the rounding by writing it into the load with ROUND(col, -2), or accept the extra digits and record that the column now stores a precision the business process never intended.
Scale above 37
Oracle NUMBER accepts scale up to 127. Snowflake stops at 37, and the connector documents that when precision or scale exceeds Snowflake limits "the value is stored as TEXT". A numeric column arriving as text is the failure that reconciles perfectly on row counts and breaks every aggregate built on it.
SELECT owner, table_name, column_name, data_scale
FROM all_tab_columns
WHERE data_scale > 37
AND owner = 'YOUR_SCHEMA'
ORDER BY data_scale DESC;
How to read the result: Rare but worth thirty seconds, because the failure mode is severe and silent. Any row returned is a column that will not be numeric on the other side unless you intervene. Decide the real precision the business needs, usually far below 37, and declare it explicitly in the target DDL.
DATE columns and the fractional seconds you are about to invent
Oracle is unambiguous that DATE stores "year, month, day, hour, minute, and second" and that "it does not have fractional seconds or a time zone". Snowflake TIMESTAMP_NTZ carries up to nine fractional digits. Nothing is lost, but every DATE column arrives looking more precise than the source ever was, and code that compares timestamps for equality starts behaving differently.
SELECT table_name, column_name
FROM all_tab_columns
WHERE data_type = 'DATE'
AND owner = 'YOUR_SCHEMA'
ORDER BY table_name;
How to read the result: Usually the longest list this query set produces, and that is the point: Oracle DATE is the default temporal column in most Oracle schemas. Do not try to change all of them. Confirm TIMESTAMP_TYPE_MAPPING at account level so the bare TIMESTAMP alias resolves the way you expect, and truncate to the second in any comparison that used to rely on Oracle having no sub-second component.
CHAR columns and blank padding
Oracle CHAR is a fixed length type and pads values to the declared width. Both Snowflake mappings send it to TEXT, and Snowflake CHAR is only a synonym for VARCHAR, so no padding survives. The stored values keep the spaces they were given, and comparison semantics change under them.
SELECT table_name, column_name, data_length
FROM all_tab_columns
WHERE data_type IN ('CHAR','NCHAR')
AND owner = 'YOUR_SCHEMA'
ORDER BY table_name;
How to read the result: For each row, run SELECT COUNT(*) FROM tbl WHERE col <> TRIM(col) to see whether padding is actually present in the data rather than only in the type. Where it is, apply RTRIM during the load and compare trimmed values on both sides, or a join on a padded country or product code returns fewer rows in Snowflake with no error anywhere.
LOB columns against the connector default
The Openflow connector defaults to 16 MB for CLOB and NCLOB and 8 MB for BLOB. Oracle LOBs are measured in gigabytes and up. The limits are raisable and they are not raised for you, so the affected rows are your largest documents, images and payloads.
SELECT table_name, column_name, data_type
FROM all_tab_columns
WHERE data_type IN ('CLOB','NCLOB','BLOB','LONG','LONG RAW')
AND owner = 'YOUR_SCHEMA'
ORDER BY data_type, table_name;
How to read the result: For each row returned, run SELECT MAX(DBMS_LOB.GETLENGTH(col)) FROM tbl. That single number tells you whether the default ceiling is fine or whether you need to raise it before the first load. If any LONG columns appear, note that Oracle itself says "do not create tables with LONG columns" and that they exist only for backward compatibility, so the migration is a good moment to retire them.
Types no mapping table covers
The connector states that any Oracle type not in its table becomes TEXT by default, and nothing is raised when that happens. Older Oracle estates are full of candidates: object types, VARRAYs and nested tables, SDO_GEOMETRY, ANYDATA, XMLTYPE, ROWID.
SELECT data_type, COUNT(*) AS cols
FROM all_tab_columns
WHERE owner = 'YOUR_SCHEMA'
AND data_type NOT IN (
'NUMBER','VARCHAR2','NVARCHAR2','CHAR','NCHAR',
'DATE','FLOAT','BINARY_FLOAT','BINARY_DOUBLE',
'CLOB','NCLOB','BLOB','RAW')
GROUP BY data_type
ORDER BY cols DESC;
How to read the result: This is the single most useful query on the page, because it turns an unknown into a short list in one run. Anything here that is not a TIMESTAMP or INTERVAL variant is heading for TEXT unless you decide otherwise. On a typical Oracle schema the answer is a handful of types across a few dozen columns, and those columns are the entire semantic risk of the migration.
Tables the connector cannot replicate at all
Snowflake states that with its Oracle connector "each replicated table must have a primary key, a qualifying unique constraint, a qualifying unique index, or a user-declared logical key". Legacy Oracle schemas routinely contain staging, audit and history tables that have none of those.
SELECT t.table_name
FROM all_tables t
WHERE t.owner = 'YOUR_SCHEMA'
AND NOT EXISTS (
SELECT 1 FROM all_constraints c
WHERE c.owner = t.owner
AND c.table_name = t.table_name
AND c.constraint_type IN ('P','U'))
ORDER BY t.table_name;
How to read the result: Every table listed needs either a declared logical key or a different loading strategy, decided during planning rather than discovered during the cutover weekend. This query usually surprises people, because the tables without keys are the append-only ones nobody has touched in a decade, and they are often the largest tables in the schema.
What does Oracle NUMBER map to in Snowflake?
NUMBER(p,s) maps to NUMBER(p,s) and is clean, because Oracle's own maximum precision is 38 and so is Snowflake's. The trouble is only with the undeclared form. Oracle says that omitting precision and scale "specifies the maximum range and precision for an Oracle number", and Snowflake has no equivalent concept, so a fixed precision and scale has to be invented. Snowflake's connector documentation invents NUMBER(38,19) and its SnowConvert reference invents NUMBER(38,18).
Both spend the full 38 digits, so the difference is entirely about where the decimal point sits. NUMBER(38,18) leaves twenty digits for the integer part, NUMBER(38,19) leaves nineteen. If your largest value has nineteen digits or fewer before the point, which covers almost every real schema, the two are equivalent and you can stop worrying. If you carry high precision identifiers, financial values in minor units, or anything generated by a wide sequence, run the first query above and find out rather than assume.
Does Oracle DATE become DATE or TIMESTAMP in Snowflake?
TIMESTAMP, and it has to. Oracle DATE stores year, month, day, hour, minute and second, and Oracle states plainly that "it does not have fractional seconds or a time zone". Snowflake DATE stores only a date. Mapping an Oracle DATE to a Snowflake DATE would throw away the time component of every row, so both Snowflake documents correctly send it to a timestamp type instead.
The nuance is which timestamp type. The connector pins it to TIMESTAMP_NTZ. SnowConvert emits a bare TIMESTAMP, which Snowflake documents as "a user-specified alias associated with one of the TIMESTAMP_* variations", resolved through the TIMESTAMP_TYPE_MAPPING parameter. That defaults to TIMESTAMP_NTZ, so out of the box the two agree, and on any account where somebody set the parameter to TIMESTAMP_LTZ they stop agreeing. Set that parameter deliberately at account level and the ambiguity disappears.
Why did my Oracle number column arrive in Snowflake as text?
Almost always because the precision or scale sat outside what Snowflake can represent. Snowflake NUMBER stops at 38 digits of precision and 37 of scale. Oracle allows scale anywhere from -84 to 127. The Openflow connector documents its behavior for that case directly: when precision or scale exceeds Snowflake limitations, "the value is stored as TEXT". No error is raised, the load succeeds, and the row count reconciles.
The second cause is that the source type was never in the mapping table at all. The connector states that "any Oracle data types not listed in this table are mapped to TEXT by default", which catches object types, collections, SDO_GEOMETRY and ANYDATA. Either way the tell is the same: an aggregate that used to work now needs a cast, and the column that used to sort numerically now sorts as a string, so 100 comes before 20.
Does Snowflake enforce the primary keys my Oracle schema relies on?
No, not on standard tables. Snowflake accepts PRIMARY KEY, UNIQUE and FOREIGN KEY definitions and records them as metadata the optimizer can use, then does not enforce any of them except NOT NULL and CHECK, which are always enforced. Coming from Oracle this lands harder than from most sources, because Oracle enforces the lot and a mature Oracle schema has usually been leaning on that for twenty years.
There is a second, sharper key problem specific to this route. Snowflake requires that every replicated table have "a primary key, a qualifying unique constraint, a qualifying unique index, or a user-declared logical key" before its Oracle connector will replicate it at all, and it states that it "doesn't detect at runtime when you drop or modify the primary key, unique constraint, or unique index". So the key both gates replication and silently stops being watched once replication has started. Run the last query above during planning, and treat any DDL change to a key as an event that needs a human.
What happens to Oracle interval columns in Snowflake?
They become text, because Snowflake has no interval column type to receive them. Both Snowflake documents agree on that much and disagree on everything after it. SnowConvert maps INTERVAL DAY TO SECOND and INTERVAL YEAR TO MONTH to VARCHAR(20) and rewrites the value into a shorthand along the lines of "1d, 2h, 3m, 4s". The Openflow connector maps both to unbounded TEXT and leaves the representation alone.
So the same source column arrives in two different string formats depending on which Snowflake tool produced it, and one of those formats carries a twenty character cap. The target worth choosing is neither: store the duration as a NUMBER of seconds with a separate unit column, decide it during the migration, and rewrite the arithmetic once. Every query that added an interval to a date or summed elapsed time is going to need rewriting anyway.
Do it in this order
Inventory the types first, with the catch-all query above, because it turns an unknown into a list of a few dozen columns in one run. Run the other seven checks while Oracle is still live and still authoritative. Write the target DDL by hand for whatever those checks flag, which is normally under thirty columns out of several hundred, and let the tool generate the rest. Decide explicitly what happens to every unique index you are about to stop enforcing. Load once, reconcile on values rather than counts, and only then turn on continuous replication.
The tool by tool comparison for this route, including what each vendor bills by and the Oracle license that sits behind Snowflake's own first-party connector, is on our Oracle to Snowflake migration tools page. If Postgres rather than Snowflake is the destination, the equivalent work is in Oracle to PostgreSQL migration tools and the code side is covered in convert Oracle PL/SQL to PostgreSQL. The same treatment for the neighboring warehouse routes is in convert Postgres data types to Snowflake and convert SQL Server data types to Snowflake. Oracle usually stays alive alongside the warehouse for years, so the ongoing case is in Snowflake ETL tools, and the budget model behind either program is in what a data migration really costs.
Decide the mapping once, then let it run
Set the field and type mapping yourself, run the backfill, then keep Oracle and Snowflake in agreement incrementally with retries, alerts and per-record logs. From $49 a month, never metered by rows.
No credit card required.