Teradata to Snowflake Migration: What Converts Cleanly and What Needs Rework

Quick answer: Snowflake's SnowConvert AI is free and converts Teradata SQL, BTEQ, MultiLoad and TPT scripts, macros and procedures. Since June 2026 it sits inside Snowflake AIM, which also offers data migration and validation for Teradata, both labelled Preview in October 2026. Most DDL and DML convert automatically. The work that remains is about behaviour: SET tables lose their duplicate protection, primary indexes have no equivalent, string comparisons become case-sensitive, and BTEQ error handling needs to be tested by hand.

Last updated: October 2026

Until recently, the first decision in a Teradata migration was which conversion tool to buy. That decision has mostly gone away. Snowflake made SnowConvert free in January 2025, renamed it SnowConvert AI in June 2025, and in June 2026 brought it into Snowflake AIM, a migration platform that also covers data movement and validation.


The tooling does not settle the behavioural differences between the two platforms. Teradata and Snowflake treat duplicate rows, physical design, string comparison and script error handling differently, and code that converts without a single warning can still return different results. This guide covers the tooling as it stands in October 2026, the differences that cause real defects, and a validation approach that finds them before cutover.


What Snowflake's migration tooling covers in 2026


Snowflake AIM brings together SnowConvert AI, the Snowpark Migration Accelerator and technology from Datometry, which Snowflake agreed to acquire in November 2025. For a warehouse migration, Snowflake recommends the AIM agent, which runs inside Snowflake CoCo (the new name for Cortex Code), or the scai command-line tool. The SnowConvert Desktop application still exists, but Snowflake's documentation describes it as handling code conversion only, without the newer migration features.


Teradata inputDefault SnowConvert AI output
DDL and DML files (.sql, .ddl, .dml)Snowflake SQL, with issue codes wherever behaviour may differ
BTEQ scriptsPython that calls the snowconvert.helpers library, or Snowflake Scripting if you change the setting
MultiLoad and TPT scriptsPython
Stored procedures and macrosSnowflake Scripting procedures, or JavaScript if you change the setting
Join indexesDynamic tables

FastLoad and TPump scripts are also accepted as input. Every conversion produces an assessment report and a list of issue codes: EWI codes mark code that needs a manual fix, and FDM codes mark converted code whose behaviour may differ from the original. On a large estate the FDM list deserves as much attention as the EWI list, because FDM code usually deploys and runs, and any difference shows up only in the results.


One point is worth checking before you plan around it. SnowConvert AI's release notes record AI-assisted conversion being enabled for Teradata in version 2.18.0 on 30 March 2026, but the AIM support matrix still lists AI conversion for Teradata as planned. We recommend confirming the current status in the support matrix for the version you install, and treating deterministic conversion as the baseline.


Datometry for Snowflake, in public preview since April 2026, takes a different approach. It virtualises Teradata SQL behaviour on Snowflake so that existing applications can run without their SQL being rewritten. For applications that are expensive to change, such as a vendor tool you cannot modify, it is worth evaluating, with the preview status taken into account. Snowflake also runs LiftOff, a migration programme that can partly fund migration activities and requires AIM for its workshops.


Start from what is actually used


Migrations finish sooner when the scope is based on what people actually use. In a telecom migration we delivered, the Teradata system had accumulated more than 300 BTEQ scripts. Only 47 of them had been run in the previous 90 days, and another 80 had not run in over a year. We asked each department head to sign off on the reports they relied on, migrated those, and left the rest behind. The details are in the telecom CDR case study.


Teradata's query logging (DBQL), where it is enabled, is the most reliable record of what runs and who runs it. Pair it with the SnowConvert assessment report, which counts objects and conversion issues, and you have a scope based on use, which is far easier to estimate than one based on inventory.


SET tables and primary indexes


A Teradata SET table does not allow duplicate rows. Snowflake has no equivalent, so SnowConvert AI drops the SET keyword and adds the note SSC-FDM-TD0024, "TABLE MIGHT HAVE DUPLICATE ROWS". Any load that relied on the table to discard duplicates will now keep them, and the first sign is usually a total that is slightly too high.


For each former SET table, decide whether duplicates are possible in the source feed. If they are, enforce uniqueness in the load. A MERGE on the business key is the simplest way to do that:


Replace SET table behaviour with a MERGE on the business key
-- Teradata relied on a SET table to reject duplicates.
-- In Snowflake, de-duplicate the batch and merge on the key.
MERGE INTO sales.orders AS t
USING (
    SELECT order_id, line_no, customer_id, order_date, amount
    FROM staging.orders_load
    QUALIFY ROW_NUMBER() OVER (
        PARTITION BY order_id, line_no ORDER BY order_date DESC) = 1
) AS s
  ON t.order_id = s.order_id AND t.line_no = s.line_no
WHEN MATCHED THEN UPDATE SET
    customer_id = s.customer_id,
    order_date  = s.order_date,
    amount      = s.amount
WHEN NOT MATCHED THEN INSERT (order_id, line_no, customer_id, order_date, amount)
    VALUES (s.order_id, s.line_no, s.customer_id, s.order_date, s.amount);

-- After the first full load, confirm nothing slipped through.
SELECT order_id, line_no, COUNT(*) AS copies
FROM sales.orders
GROUP BY order_id, line_no
HAVING COUNT(*) > 1;

Primary indexes need a different kind of decision. In Teradata the primary index controls how rows are distributed, and a unique primary index also enforces uniqueness. Snowflake manages micro-partitions automatically, so SnowConvert AI turns a unique primary index into a UNIQUE constraint and comments out PARTITION BY clauses with the note SSC-FDM-0038. Snowflake does not enforce UNIQUE, PRIMARY KEY or FOREIGN KEY constraints on standard tables (NOT NULL is enforced, and hybrid tables do enforce them), so uniqueness that the database used to guarantee becomes something your load or your tests have to check.


It is tempting to turn every partitioned primary index into a clustering key. We recommend against it. Snowflake's own documentation says clustering keys are not intended or recommended for all tables, and automatic clustering has an ongoing cost. Load the data without clustering keys, look at pruning on the largest tables under real queries, and add CLUSTER BY only where queries consistently filter on the same columns and pruning is poor.


Case sensitivity converts cleanly and still breaks reports


In Teradata's default TERA session mode, character columns are NOT CASESPECIFIC, so 'Tango' = 'TANGO' is true. In Snowflake, comparisons are case-sensitive by default and trailing spaces are not trimmed. A filter, join or GROUP BY on a text column can therefore return fewer rows, or split one group into two, without any error.


SnowConvert AI handles this in one of two ways. By default it adds COLLATE to emulate NOT CASESPECIFIC; if you disable that setting, it translates comparisons to RTRIM(UPPER(expression)). Both preserve the results, and both have costs. Snowflake's documentation notes that collation can slow comparisons and reduce micro-partition pruning, and collation is not supported with UDFs.


Two ways to keep Teradata's case-insensitive behaviour
-- Option 1: declare the behaviour on the column.
-- 'ci' ignores case, 'rtrim' ignores trailing spaces.
CREATE OR REPLACE TABLE ref.customer (
    customer_code VARCHAR(20) COLLATE 'en-ci-rtrim',
    customer_name VARCHAR(200)
);

-- Option 1b: a default for tables and columns created later.
ALTER SCHEMA ref SET DEFAULT_DDL_COLLATION = 'en-ci-rtrim';

-- Option 2: normalise once at load time, compare plainly.
INSERT INTO ref.customer (customer_code, customer_name)
SELECT UPPER(TRIM(customer_code)), customer_name
FROM staging.customer_load;

Our preference is to decide column by column. For codes and join keys, normalising the value once at load time keeps comparisons plain and pruning intact. For descriptive text that users search, collation on the column is usually the safer choice, because it does not depend on every query remembering to apply UPPER.


BTEQ, macros and procedures


BTEQ scripts convert to Python by default. If you choose Snowflake Scripting instead, the conversion follows a consistent pattern: the script body is wrapped in EXECUTE IMMEDIATE $$ ... $$, .IF ERRORCODE checks become checks on STATUS_OBJECT['SQLCODE'], .IF ACTIVITYCOUNT reads the row count through RESULT_SCAN(LAST_QUERY_ID()), and .GOTO and .LABEL become nested procedures. .EXPORT and .IMPORT become COPY INTO with PUT or GET through the Snowflake CLI.


The ERRORCODE conversion carries the note SSC-FDM-TD0013 because Snowflake's error codes are not Teradata's. Any script that branches on a specific Teradata error number, for example to skip a DROP when a table does not exist, needs to be read and tested by hand. We run each converted job once with a deliberately forced failure to confirm that the error path still does what the original did.


Snowflake now describes SnowSQL as a legacy client and recommends the Snowflake CLI, although SnowSQL 1.5.x remains supported until 16 April 2028. New scheduling and wrapper scripts should be written against the Snowflake CLI.


Macros become Snowflake Scripting procedures that return a table through a RESULTSET. A macro that returned several result sets writes them to temporary tables instead (SSC-FDM-0020). Callers change from EXEC to CALL, which matters for BI tools and applications that invoke macros directly.


A handful of function and statement differences account for most of the remaining issues:



Surface ambiguous updates while testing
-- In test environments, make multi-match UPDATEs fail loudly
-- instead of silently choosing one source row.
ALTER SESSION SET ERROR_ON_NONDETERMINISTIC_UPDATE = TRUE;

Data types mostly map without trouble. Teradata NUMBER without precision becomes NUMBER(38,18), CLOB becomes VARCHAR, BLOB becomes BINARY, JSON becomes VARIANT, and PERIOD becomes VARCHAR. INTERVAL becomes VARCHAR(20) unless you opt into Snowflake's interval data types, which were still in preview in October 2026. Snowflake now allows VARCHAR values up to 128 MB, although the default size is still 16 MB.


Replacing FastLoad, MultiLoad and TPT


Teradata Parallel Transporter (TPT) wraps the older utilities as operators: Load for FastLoad, Update for MultiLoad, Stream for TPump and Export for FastExport. The mapping below is ours, based on how each utility is typically used.


Teradata utilityTypical useSnowflake option
FastLoad (TPT Load)Bulk load into empty tablesCOPY INTO from a stage
MultiLoad (TPT Update)Batch inserts, updates and deletesCOPY INTO a staging table, then MERGE
TPump (TPT Stream)Continuous low-volume changesSnowpipe for files, Snowpipe Streaming for rows
FastExport (TPT Export)Bulk extractsCOPY INTO a stage location (unload)

Two pricing changes since 2024 affect this choice. Since 8 December 2025, Snowpipe on Standard and Enterprise editions has been billed at 0.0037 credits per GB, with no per-file charge. Snowpipe Streaming has a new high-performance architecture, generally available since September 2025 on AWS and November 2025 on Azure, billed by uncompressed volume, with latency that Snowflake documents as low as 5 seconds. For bulk loads, Snowflake recommends files of roughly 100 to 250 MB compressed.


Workload management works differently as well. Teradata's TASM uses workload definitions, throttles and priorities within one system. In Snowflake the usual pattern is a separate warehouse per workload, multi-cluster warehouses (Enterprise Edition) for concurrency, and resource monitors for spend. Resource monitors cover warehouses only, so serverless features need budgets. Note also that STATEMENT_TIMEOUT_IN_SECONDS defaults to two days, so a runaway query can run for that long unless each warehouse sets a lower limit.


Separate warehouses with explicit limits
CREATE WAREHOUSE IF NOT EXISTS etl_wh
  WAREHOUSE_SIZE = 'MEDIUM'
  AUTO_SUSPEND = 60
  AUTO_RESUME = TRUE
  STATEMENT_TIMEOUT_IN_SECONDS = 7200;

CREATE WAREHOUSE IF NOT EXISTS bi_wh
  WAREHOUSE_SIZE = 'SMALL'
  MIN_CLUSTER_COUNT = 1
  MAX_CLUSTER_COUNT = 3        -- multi-cluster needs Enterprise Edition
  SCALING_POLICY = 'STANDARD'
  AUTO_SUSPEND = 60
  AUTO_RESUME = TRUE
  STATEMENT_TIMEOUT_IN_SECONDS = 900
  STATEMENT_QUEUED_TIMEOUT_IN_SECONDS = 300;

CREATE RESOURCE MONITOR IF NOT EXISTS etl_monthly
  WITH CREDIT_QUOTA = 500
  FREQUENCY = MONTHLY
  START_TIMESTAMP = IMMEDIATELY
  TRIGGERS ON 80 PERCENT DO NOTIFY
           ON 100 PERCENT DO SUSPEND;

ALTER WAREHOUSE etl_wh SET RESOURCE_MONITOR = etl_monthly;

Moving the history


Since SnowConvert AI 2.29.0 (28 May 2026), the scai CLI can move Teradata data as well as code. The feature is labelled Preview. It runs an orchestrator with workers, supports pause, resume and retry, and offers three extraction strategies:



Incremental runs are supported only with watermark or checksum synchronisation, so plan the final catch-up load before cutover rather than after. There is no Openflow connector for Teradata. If you prefer to run the extract yourself, the manual path is to export to files in cloud storage and load them with COPY INTO.


Validating before cutover


SnowConvert AI's data validation, also labelled Preview, compares Teradata with Snowflake at three levels. Level 1 checks the schema and row counts. Level 2 compares metrics per column, including minimum, maximum, sum, average, null count and distinct count. Level 3 compares rows using primary-key columns and an MD5 hash per partition. It is off by default, and on Teradata it needs the HASH_MD5 UDF installed on the source. Results land in SNOWCONVERT_AI.DATA_VALIDATION with a Streamlit dashboard on top.


Data validation commands in the SnowConvert AI CLI
scai data validate generate-config
scai data validate start
scai data validate status
scai data validate revalidate

Matching tables do not prove that the converted logic behaves the same way. We add a short list of tests aimed at the differences described above:



In the telecom migration mentioned earlier, every rebuilt report had to match Teradata on row counts, with metric totals within 0.01% to allow for floating-point differences, before any user group moved. Teradata was then decommissioned in three phases over four weeks, one department at a time. A phased cutover like that costs a few weeks of running both systems, and a problem found late then affects one department at a time.


Pranay Vatsal, Founder & CEO

Pranay Vatsal is the Founder & CEO of CelestInfo with deep expertise in Snowflake, data architecture, and building production-grade data systems for global enterprises.

Related Articles

Frequently Asked Questions

Q: Is SnowConvert AI free for Teradata migrations?

Yes. Snowflake's SnowConvert AI FAQ states that it is free for everyone with full conversion functionality, subject to Snowflake's Conversion Software Terms of Use. The AI-assisted features need a connection to a Snowflake account because they use Cortex AI.

Q: What replaces a Teradata primary index in Snowflake?

Nothing directly. Snowflake manages micro-partitions automatically, so SnowConvert AI converts a unique primary index into a UNIQUE constraint, which Snowflake does not enforce on standard tables, and comments out PARTITION BY clauses. Add clustering keys only to large tables where queries consistently filter on the same columns and pruning is poor.

Q: Does Snowflake support QUALIFY like Teradata?

Yes. Snowflake supports QUALIFY natively, so a filter such as QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) = 1 converts without changes.

Q: How do I keep Teradata's case-insensitive comparisons in Snowflake?

Snowflake comparisons are case-sensitive by default. You can declare a collation such as 'en-ci-rtrim' on the column, set DEFAULT_DDL_COLLATION for new objects, or normalise values with UPPER and TRIM at load time. SnowConvert AI adds COLLATE by default, or RTRIM(UPPER()) if you disable that setting.

Q: What do BTEQ scripts become in Snowflake?

By default SnowConvert AI converts BTEQ scripts to Python that uses its snowconvert.helpers library. You can choose Snowflake Scripting instead, where ERRORCODE and ACTIVITYCOUNT checks are translated to Snowflake equivalents. Error-code branches need manual review because the codes differ between the platforms.

Q: Can Teradata applications run on Snowflake without rewriting their SQL?

Snowflake offers Datometry for Snowflake, in public preview since April 2026, which virtualises Teradata SQL behaviour on Snowflake so existing applications can run without a rewrite. Because it is in preview, we would evaluate it for specific hard-to-change applications and convert the rest.