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¶
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.
Example¶
sources.yml:
Snowflake (stg_raw__ole_db_source_dimcustomer.sql):
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:
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):
Routing and normalizing¶
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":
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.