SSIS - Mappings and transformations¶
This page shows how each SSIS Data Flow component is converted into a dbt model, with a before and after example for each. For the supported-component matrix and the Control Flow deep-dives, see the SSIS overview.
A Data Flow Task becomes a dbt project. Within that project, each component becomes a SQL model: source reads become stg_ staging models, transformations become int_ models, and destinations become models named after the target table. The examples below are taken from the test suite.
Sources and staging¶
OLE DB Source¶
An OLE DB Source becomes a staging model that reads from a table declared in sources.yml.
Conversion behavior¶
In table or view access mode, the staging model is named stg_raw__<component>_<table> and selects every column that the component exposes, aliasing each one to its output name. The table itself isn’t hardcoded: the model reads through {{ source('raw', '<table>') }}, and a generated sources.yml lists the tables that the package reads.
In SQL command access mode, the component runs a query instead of naming a table. The query becomes the body of the staging model and is converted as embedded SQL. Every table that the query reads is replaced with a {{ source('raw', '<table>') }} reference and gets its own entry in sources.yml, so a two-table join produces two entries. When the migration input doesn’t include the definitions of those tables, the model is flagged with SSC-FDM-0007.
Example¶
The first example uses table or view access mode.
sources.yml:
Snowflake (stg_raw__ole_db_source_dimcustomer.sql):
The second example uses SQL command access mode, where the component runs a join across two tables.
SSIS (the component’s SQL command):
Snowflake (stg_raw__ole_db_source.sql):
sources.yml:
Limitations¶
sources.yml is generated with YOUR_DB and YOUR_SCHEMA placeholders. Replace them with your Snowflake database and schema before you run the project.
Excel Source¶
An Excel Source becomes a staging model that reads the worksheet through the excel_source_udf Python UDF.
Conversion behavior¶
The workbook is bound to the landing stage, and an excel_raw_data CTE reads it with TABLE(excel_source_udf('<stage path>', '<worksheet>', '<HDR flag>')). A parsed_data CTE then types each column: text columns are cast with :: VARCHAR, and numeric, date, timestamp, time, and Boolean columns use the matching TRY_TO_ function, so a value that can’t be parsed becomes null instead of failing the load. The original Excel connection string is kept as a comment at the top of the model. When the connection sets HDR=NO, the worksheet has no header row and the columns are named F1, F2, and so on.
Example¶
Snowflake (stg_raw__excel_source.sql):
Limitations¶
An error output that uses the RedirectRow disposition can’t be translated directly. The model still reads the worksheet the same way, but it’s flagged with SSC-EWI-SSIS0028 and the component is reported as Partial in the assessment report. Use TRY_TO_* functions with error flag columns for defensive error handling, and create separate error-capture models if needed.
ADO.NET Source¶
An ADO.NET Source becomes a staging model in the same way an OLE DB Source does.
Conversion behavior¶
In table or view access mode, the model selects every column that the component exposes, aliases each one to its output name, and reads the table through {{ source('raw', '<table>') }}. The schema qualifier in the component’s table or view name is dropped from the source reference, because the schema comes from sources.yml.
Example¶
Snowflake (stg_raw__ado_net_source.sql), for a component that reads "dbo"."Departments":
Oracle Source¶
An Oracle Source becomes a staging model that reads the Oracle table through a dbt source.
Conversion behavior¶
In table access mode, the component’s table name resolves the source table, the schema qualifier is dropped from the source reference, and each column is aliased to its output name. The Oracle SQL command access mode is normalized to the OLE DB SQL command mode, so the query itself is converted as embedded SQL rather than as part of the component.
Example¶
Snowflake (stg_raw__oracle_source.sql), for a component that reads "HR"."DEPARTMENTS":
Limitations¶
When an Oracle Source uses a SQL command and that query can’t be converted to Snowflake SQL, the staging model keeps the original statement as a comment and is flagged with SSC-EWI-SSIS0003:
Flat File Source¶
A Flat File Source becomes a staging model that reads the file from the landing stage through a generated file format.
Conversion behavior¶
The staging model is named stg_flat_file__<component> and reads the file positionally: the first column becomes $1, the second $2, and so on, each cast to the type that the connection manager declares and aliased to the column name. The file itself is read from @public.landing_stage/ssis/<package>/<connection manager>/<file>, and the flat file connection manager becomes a named file format called <package>_<data flow>_<component> that carries the field delimiter, the encoding that matches the code page, and NULL handling.
When the connection manager puts column names in the first data row, the model filters that row out with WHERE METADATA$FILE_ROW_NUMBER > 1. When the component doesn’t retain NULL values, each column is wrapped in COALESCE with a default for its type, so an empty field becomes that default instead of NULL. When the component retains NULL values, the same model is generated without the COALESCE calls. A fixed-width connection manager is read with SUBSTR over $1 instead of positional columns.
Example¶
Snowflake (stg_flat_file__flat_file_source.sql), for a comma-delimited file with a header row and retain NULLs turned off:
Snowflake (file_formats.sql):
Limitations¶
A fixed-width connection manager can only be read when every column declares a width. When one or more columns have no width, the layout can’t be resolved: the staging model falls back to selecting NULL for each column, is flagged with SSC-EWI-SSIS0051, and no file format is generated for it.
Row-level transformations¶
Derived Column¶
A Derived Column becomes an int_ model whose SELECT list carries one expression per derived column.
Conversion behavior¶
The input rows are wrapped in a source_data CTE that reads from the upstream model with {{ ref() }}. Each SSIS expression becomes its Snowflake equivalent in the SELECT list: string concatenation stays ||, and date parts become the matching Snowflake function. Columns that pass through unchanged are selected alongside the derived ones, so downstream components still see the full row.
Example¶
Snowflake (int_derived_column.sql):
Data Convert¶
A Data Convert becomes an int_ model that casts each converted column to the target type.
Conversion behavior¶
Each conversion becomes a :: cast aliased to the component’s output column name. When the component adds a converted copy instead of replacing the column, the original column is kept and the cast column is emitted next to it under its SSIS output name, such as "Copy of BirthDate".
Example¶
Snowflake (int_data_conversion.sql, column list shortened):
Character Map¶
A Character Map becomes an int_ model that applies supported character operations to string columns and passes other columns through unchanged.
Conversion behavior¶
Uppercase and lowercase operations become the Snowflake UPPER and LOWER functions. The generated model reads the upstream model through a source_data CTE and applies each operation in the SELECT list. Operations can replace a column in place or create a new output column.
Example¶
Snowflake (int_character_map.sql):
Limitations¶
Only uppercase and lowercase operations are translated. For unsupported map flags, such as byte reversal, the column passes through unchanged and the generated model includes SSC-EWI-SSIS0019.
OLE DB Command¶
An OLE DB Command becomes an incremental model that applies the component’s parameterized DELETE or UPDATE to the table that the command names.
Conversion behavior¶
The model is named after the target table and is generated under models/marts/. Its config block sets materialized='incremental' and an incremental strategy that matches the command: delete_only for a DELETE and update_only for an UPDATE. The WHERE clause parameters become the unique_key, as a single value for one key column and as an array for a composite key, and a schema-qualified command also sets schema. Each model is tagged oledb_command plus delete_operation or update_operation.
A DELETE model selects only the key columns. An UPDATE model also selects the SET columns and lists them in merge_update_columns. The conversion adds the matching strategy macro to the project, get_incremental_delete_only_sql or get_incremental_update_only_sql, which runs a MERGE INTO that ends in WHEN MATCHED THEN DELETE or WHEN MATCHED THEN UPDATE SET.
Example¶
SSIS (the component’s SQL command):
With ContactID mapped to that parameter, the conversion generates models/marts/Contacts.sql. The model’s config block sets materialized='incremental', incremental_strategy='delete_only', unique_key='ContactID', and schema='dbo', the model carries the oledb_command and delete_operation tags, and its SELECT returns only ContactID.
Combining data¶
Lookup¶
A Lookup becomes an int_ model that joins the input rows to a deduplicated lookup source.
Conversion behavior¶
The generated model has two CTEs: lookup_reference reads the reference model and keeps one row per lookup key with QUALIFY ROW_NUMBER() = 1, and input_data reads the upstream model. The two are combined with an INNER JOIN on the lookup condition, and the returned lookup columns are appended to the input columns.
When the lookup key can contain nulls, the join uses EQUAL_NULL so that null keys match the way they do in SSIS.
Chained lookups become one model each. The second Lookup reads the first one’s model with {{ ref('int_lookup') }} rather than re-reading the source.
Example¶
Snowflake (int_lookup.sql), a lookup with no deterministic ordering:
Snowflake (int_lookup_1.sql), the second Lookup in the same Data Flow, with sort columns available and a null-safe join:
Limitations¶
An SSIS Lookup returns the first matching row. When the conversion can’t determine a deterministic order for the lookup source, it emits SSC-FDM-SSIS0001 and leaves null in the ORDER BY. Replace that null with the columns that make the match deterministic, otherwise the row that Snowflake keeps can vary between runs.
Fuzzy Lookup¶
A Fuzzy Lookup becomes an int_ model that matches rows by string similarity instead of by equality.
Conversion behavior¶
The generated model has two CTEs: lookup_reference reads the reference model, and input_data reads the upstream model. The two are combined with a CROSS JOIN, and each fuzzy-matched column pair is scored with JAROWINKLER_SIMILARITY divided by 100.0. A WHERE clause keeps only the pairs that meet the component’s minimum similarity, and QUALIFY ROW_NUMBER() <= 1, partitioned by the input columns and ordered by the similarity score, keeps the best match for each input row.
The model returns the passthrough input columns, the reference columns that the component copies, a _Similarity and a _Confidence column, and one _Similarity_<column> column per fuzzy-matched column. Copied reference columns keep the output names from the SSIS component. In the example below, the copied CompanyName column is named RefCompanyName because that is the name in the package, not because the conversion adds a prefix.
Example¶
Snowflake (int_fuzzy_lookup.sql):
The reference table is read through its own staging model (stg_raw__fuzzy_lookup.sql) and is listed in the generated sources.yml.
Limitations¶
Every converted Fuzzy Lookup emits SSC-FDM-SSIS0024, because the two matching algorithms don’t behave identically. SSIS uses token-based similarity, while JAROWINKLER_SIMILARITY is character-level, so scores can differ between source and target. _Confidence is approximated as _Similarity, since Snowflake has no equivalent relative confidence metric. Unmatched input rows are excluded from the output, while SSIS keeps them with a similarity of 0 and null reference columns.
Union All¶
A Union All becomes an int_ model that combines its inputs with UNION ALL.
Conversion behavior¶
Each input becomes its own CTE (input_1, input_2, and so on) that reads one upstream model and renames its columns to the Union All output names. The CTEs are then combined with UNION ALL in input order, so columns line up even when the sources name them differently.
Example¶
Snowflake (int_union_all.sql):
Merge¶
A Merge becomes an int_ model that stacks its two inputs with UNION ALL.
Conversion behavior¶
Each input becomes its own CTE, merge_input_1 and merge_input_2, that reads one upstream model and renames its columns to the Merge output names. The two CTEs are then combined with UNION ALL. When one input doesn’t supply a column that the other one does, that branch selects NULL under the output column name, so both branches produce the same shape.
Example¶
Snowflake (int_merge.sql), where only the second input supplies Cost:
Limitations¶
An SSIS Merge assumes sorted inputs and produces a sorted, deterministic output. UNION ALL doesn’t guarantee order, so every converted Merge emits SSC-FDM-SSIS0002. Add an ORDER BY clause if anything downstream depends on the order.
Merge Join¶
A Merge Join becomes an int_ model that joins its two inputs with a SQL join.
Conversion behavior¶
The model reads both upstream models with {{ ref() }} and aliases each one to its model name, so the join predicates and the output columns are qualified. The component’s join type determines the SQL join: a left outer join becomes LEFT JOIN. Each pair of join columns becomes an equality predicate, and multiple pairs are combined with AND. Output columns keep the names the component gives them, quoted when the name contains a space. When the component is set to treat NULLs as equal, each predicate uses EQUAL_NULL(...) instead of =.
Example¶
Snowflake (int_merge_join.sql), for a left outer join on two columns:
Limitations¶
An SSIS Merge Join assumes sorted inputs and produces a sorted, deterministic output. A SQL join doesn’t guarantee order, so every converted Merge Join emits SSC-FDM-SSIS0004. Add an ORDER BY clause if anything downstream depends on the order.
Routing and normalizing¶
Conditional Split¶
A Conditional Split becomes one int_ model per output, including the default output.
Conversion behavior¶
Each output gets a model named int_conditional_split_<output> that wraps the upstream model in a source_data CTE, selects every input column, and filters with a WHERE clause. To reproduce SSIS first-match behavior, a group’s WHERE clause carries its own condition and then negates every condition declared above it. The default output’s model negates all of the conditions with NOT COALESCE(<condition>, FALSE), which keeps rows whose conditions evaluate to NULL in the default output. Each downstream component reads the model for the output it’s connected to.
Example¶
The models below come from a Conditional Split that declares the conditions BaseRate < 10, BaseRate < 20, and BaseRate < 40 in that order, plus a default output.
Snowflake (int_conditional_split_low.sql), the first group, which has no earlier condition to negate:
Snowflake (int_conditional_split_mid.sql), the second group, which negates the first condition:
The third group follows the same pattern with every earlier condition inside one negation, so its filter is BaseRate < 40 AND NOT (BaseRate < 10 OR BaseRate < 20).
Snowflake (int_conditional_split_other_rates.sql), the default output:
Limitations¶
A condition that can’t be converted to Snowflake SQL, such as one that references a package variable, is replaced with a _ placeholder and flagged with SSC-EWI-SSIS0002. The flag appears both in the group that owns the condition and in the default output’s negation of it, so fix the expression in every model that carries it.
For non-default outputs, earlier conditions are negated without COALESCE. If an earlier condition evaluates to NULL, SQL’s three-valued logic can exclude the row from a later output even when that later condition is true. Review nullable expressions and wrap them with COALESCE(<condition>, FALSE) where needed to preserve SSIS first-match behavior.
Multicast¶
A Multicast becomes a single passthrough int_ model.
Conversion behavior¶
In SSIS, a Multicast copies its input to two or more identical outputs. The generated int_multicast model reproduces the input: it selects every input column from the upstream model, without renaming or reordering. Each downstream branch reads this one model, so the fan-out comes from the model references rather than from the component itself. The conversion reports the component as successful and emits no issues.
Example¶
Snowflake (int_multicast.sql):
Cache¶
A Cache Transform becomes a passthrough int_ model.
Conversion behavior¶
In SSIS, the Cache Transform writes its input to a Cache connection manager so that downstream Lookups can read it, and passes the same rows through unchanged. The generated model reproduces the passthrough part: it selects every input column from the upstream model, without renaming or reordering. Cache file persistence and cache index columns don’t appear in the generated SQL, because dbt materializations handle persistence and a downstream Lookup reads the model with its own query.
Property mapping¶
| Source property | Snowflake / dbt | Notes |
|---|---|---|
ConnectionName | Not translated | References the Cache connection manager, which has no runtime equivalent in dbt. |
TreatDuplicateKeysAsError | Informational notice | A true value emits SSC-FDM-SSIS0027. The passthrough SQL is generated either way. |
CacheColumnName | Not translated | Column names are preserved from the upstream model in passthrough mode. |
usageType | Source column reference | Always read-only for a Cache Transform, so every input column is passed through. |
Example¶
Snowflake (int_cache_transform.sql):
Limitations¶
When the component sets TreatDuplicateKeysAsError to true, SSIS fails the package on a duplicate cache key. The generated model doesn’t enforce that check, so duplicate rows pass through unchanged and the model emits SSC-FDM-SSIS0027. The SQL runs correctly as a passthrough. If you need the validation, add a dbt uniqueness test or a Snowflake unique constraint.
Aggregation and ranking¶
Aggregate¶
An Aggregate becomes an int_ model that groups rows and applies aggregate functions.
Conversion behavior¶
Group-by columns appear in both the SELECT and GROUP BY clauses. Aggregate output columns become the corresponding Snowflake functions: COUNT, COUNT(DISTINCT ...), SUM, AVG, MIN, or MAX. When no group-by columns are configured, the functions aggregate the entire input. When the component contains only group-by columns, the generated query groups by those columns without applying aggregate functions.
Property mapping¶
| Source property | Snowflake / dbt | Notes |
|---|---|---|
AggregationType | GROUP BY or an aggregate function | Values map to Group By, Count, Count All, Count Distinct, Sum, Average, Minimum, and Maximum. |
AggregationColumnId | Source column reference | Identifies the input column used by the group or aggregate expression. |
AggregationComparisonFlags | Manual review | A nonzero value emits SSC-EWI-0073 because SSIS string-comparison options don’t have direct Snowflake equivalents. |
IsBig | Manual review | A true value emits SSC-EWI-0073. Snowflake handles large numeric values natively. |
Example¶
Snowflake (int_aggregate.sql):
Pivot¶
A Pivot becomes an int_ model that converts row values into columns by using conditional aggregation.
Conversion behavior¶
Set-key and passthrough columns appear in the SELECT and GROUP BY clauses. Each declared pivot-key value becomes an output column that uses MAX(CASE WHEN ... THEN ... END). The Pivot always generates an intermediate model.
Property mapping¶
| Source property | Snowflake / dbt | Notes |
|---|---|---|
PassThroughUnmatchedPivotKeys | Conditional aggregation | When enabled, unmatched keys produce null values instead of a separate output and the model emits SSC-FDM-SSIS0021. |
PivotUsage | Column role | Maps a column to passthrough, set key, pivot key, or pivot value behavior. |
SourceColumn | Source column reference | Identifies the input column from which an output column is derived. |
PivotKeyValue | CASE comparison value | Maps a specific pivot-key value to its output column. |
Example¶
Snowflake (int_pivot.sql):
Limitations¶
SSIS expects Pivot input to be sorted by the set key. The generated Snowflake query uses GROUP BY, which doesn’t require or preserve that order, so it emits SSC-FDM-SSIS0022. Verify that downstream consumers don’t depend on sorted output.
UnPivot¶
An UnPivot becomes an int_ model that converts input columns into rows.
Conversion behavior¶
When all unpivoted columns map to one destination value column, the model uses Snowflake UNPIVOT. When they map to multiple destination columns, the model generates one filtered SELECT per pivot-key value and combines them with UNION ALL. Passthrough columns remain unchanged. An additional UNION preserves the SSIS behavior for input rows in which all unpivoted values are null.
Property mapping¶
| Source property | Snowflake / dbt | Notes |
|---|---|---|
PivotKeyValue | Pivot-key literal or passthrough marker | A nonempty value identifies the source column in the unpivoted output. An empty value identifies a passthrough column. |
DestinationColumn | Destination value column | Determines whether the model uses native UNPIVOT or the multiple-destination UNION ALL pattern. |
PivotKey | Pivot-key output column | Identifies the output column that receives each PivotKeyValue. |
Example¶
Snowflake (int_unpivot.sql):
Sorting¶
Sort¶
A Sort becomes an int_ model that orders the rows and keeps one row per sort key.
Conversion behavior¶
The input rows are wrapped in a source_data CTE. The model then applies QUALIFY ROW_NUMBER() = 1 partitioned by the sort columns, which reproduces the Sort component’s removal of rows with duplicate sort values, and an ORDER BY that carries each sort column and its direction.
Example¶
Snowflake (int_sort.sql):
Keys and load strategy¶
Row Count¶
A Row Count becomes a pass-through int_ model that records the row count in the package variable.
Conversion behavior¶
The model is materialized as a view and selects every input column unchanged, so the component doesn’t alter the data flow. The count itself is captured by a pre_hook that calls the m_update_row_count_variable macro with the SSIS variable name, the relation to count, and the variable scope.
Example¶
Snowflake (int_data_flow_task_row_count.sql, column list shortened):
Targets¶
OLE DB Destination¶
An OLE DB Destination becomes the model that writes the Data Flow’s output to the destination table.
Conversion behavior¶
The model is named after the destination component and carries a config(alias=...) that points at the destination table, so the table keeps its original name even when the component doesn’t. The model reads the last upstream model, aliases each column to the destination column name, and applies any cast that the column mapping requires.
Example¶
Snowflake (ole_db_destination.sql):
Excel Destination¶
An Excel Destination becomes the model that writes the Data Flow’s output, using the worksheet name as the model alias.
Conversion behavior¶
The model follows the same pattern as an OLE DB Destination: it reads the last upstream model in a source_data CTE and aliases each column to the destination column name. The config(alias=...) value is the worksheet name with its trailing $, surrounding quotes, and brackets removed, so Sheet1$ becomes Sheet1 and 'Sales Data$' becomes Sales Data. When the component doesn’t name a worksheet, the model is generated without an alias.
Example¶
Snowflake (excel_destination.sql):
Oracle Destination¶
An Oracle Destination becomes the model that writes the Data Flow’s output to the Oracle target table.
Conversion behavior¶
An Oracle Destination has no access mode property, so the conversion resolves the schema and table from the component’s table name. The table name becomes the model’s config(alias=...), and the model reads the last upstream model in a source_data CTE and aliases each column to the destination column name.
Example¶
Snowflake (oracle_destination.sql), for a component that writes to "HR"."EMPLOYEES":
Flat File Destination¶
A Flat File Destination becomes a model under models/marts/, and, when the destination can be bound to the landing stage, an unload from that model back out to the stage.
Conversion behavior¶
The model is named after the component and carries a config(alias=...) that holds the flat file connection manager name. It reads the last upstream model in a source_data CTE and aliases each column to the destination column name, the same way an OLE DB Destination does. What the config block adds on top of that depends on the destination’s overwrite setting and on whether the connection manager can be bound to the stage:
- A bound destination that overwrites is materialized as a table and gets a
post_hookthat calls the generatedcopy_into_stagemacro with the package’s landing prefix. The macro runs aCOPY INTOfrom the model out to that stage path using a CSV file format built from the connection manager’s options. The conversion also addsmacros/copy_into_stage.sqland the same sharedpublic.landing_stagethat direct COPY loads use. - A bound destination that appends drops the
materialized='table'setting and passes the connection manager’s field delimiter, text qualifier, and header setting to the macro along withoverwrite=false. - A destination that can’t be bound keeps the plain mart with no unload at all. This covers a fixed-width or ragged-right connection manager, a multi-file or plain file connection manager, and a connection manager that the package never declares.
- An eligible Flat File Source that feeds a Flat File Destination set to overwrite skips dbt entirely. The Data Flow must be a two-component passthrough flow with a supported source, a destination bound to the landing stage, and mapped columns. No mart and no macro are generated, and the Data Flow becomes a single stage-to-stage
COPY INTOinside the package task. Other flows use the dbt model path. This is a different case from the direct COPY loads described later on this page, which move a Flat File Source into an OLE DB Destination.
Example¶
Snowflake (models/marts/flat_file_destination.sql), a bound destination that overwrites:
Snowflake (models/marts/flat_file_destination.sql), the same destination set to append, with a pipe delimiter, a double-quote text qualifier, and no header row:
Snowflake (models/marts/flat_file_currency_info.sql), a destination that isn’t bound to the stage, so the model is generated without a post_hook:
Snowflake (StageCopyPackage.sql), a Flat File Source feeding a Flat File Destination set to overwrite, which becomes a stage-to-stage copy instead of a dbt model:
Limitations¶
A destination whose flat file connection manager can’t be bound to the landing stage still produces a model, but nothing writes the rows back out to a file. Add the unload yourself if the package depends on the output file. When the package doesn’t declare the connection manager at all, the conversion can’t resolve a name for it and the model’s alias becomes CONNECTION_MANAGER_NAME_WAS_NOT_FOUND. Replace that alias before you run the project.
Direct copy loads¶
Direct COPY¶
A Data Flow that only moves a Flat File Source into an OLE DB Destination becomes a direct COPY INTO load instead of a dbt project.
Conversion behavior¶
This is the default for eligible Flat File Source to OLE DB Destination graphs. The Data Flow becomes a Snowflake task that runs one COPY INTO statement, and no dbt project is generated for that Data Flow. The statement lists the destination columns, reads the staged file positionally as $1, $2, and so on, and applies the casts that the column mapping requires. The flat file connection manager becomes a named file format that carries the field delimiter, the number of header lines to skip, and null handling, and the file itself is read from the shared landing stage. The --SimplifySsisDataFlows conversion option is outside the scope of this page, and a Data Flow that isn’t eligible for a direct load falls back to the dbt project conversion described earlier on this page.
Example¶
Snowflake (DirectFlatFileLoad.sql):
Snowflake (DirectFlatFileLoad/file_formats.sql):
Snowflake (stages.sql):
Limitations¶
Every converted flat-file read binds to the generated public.landing_stage. Retarget its URL or storage integration for your account, and keep the stage name and the subfolder layout that the COPY INTO statements expect.