SSIS - Snowflake Scripting data flows and components¶
This page describes how an SSIS Data Flow Task is converted to a Snowflake stored procedure in the Snowflake Scripting output format. For the dbt output format, see dbt data flows and components. For the supported-component matrix, see the Snowflake Scripting overview.
Warning
The Snowflake Scripting output format is in active development, so the generated code may change between releases. A component that isn’t converted produces a placeholder marked with an EWI code, as shown in Unsupported components. For the current status of each component, see Supported components.
The data flow procedure¶
Each Data Flow Task becomes one stored procedure named public.<DataFlowName>, where spaces in the Data Flow Task name are replaced by underscores. The procedure takes a single scope parameter, returns VARCHAR, and runs as the caller. The Data Flow becomes the body, wrapped in a BEGIN ... END block:
The conversion builds the body in data-flow order:
- If the Data Flow reads package variables, a
LETdeclaration for each one reads its value withGetControlVariableUDFand thescopeparameter. - Each source becomes a CTE.
- Each transformation becomes a CTE that reads the CTE before it, or a temporary table when more than one later statement reads its result.
- Each destination becomes an
INSERTstatement that carries the upstream CTEs inside its source query.
The CTEs are nested inside the INSERT rather than placed before it, because Snowflake Scripting doesn’t accept a leading WITH clause as a statement inside a BEGIN ... END block.
The scope parameter is forwarded from the calling Task so the procedure resolves the same package variables that the Task graph set at runtime. The Task sets the scope to the package name and calls the procedure:
When the Data Flow reads a package variable, for example through a parameter of an OLE DB Source SQL command, the procedure reads the variable at the top of the body and uses it as a bind variable (see OLE DB Source):
Naming conventions¶
Every generated object name is derived from the name of the SSIS component it came from. The procedure keeps the original casing of the Data Flow Task name, and the CTE and temporary table names are lowercased. Spaces become underscores in all of them:
| Name | What it is |
|---|---|
public.<Data_Flow_Task_name> | The stored procedure for a Data Flow Task. A numeric suffix such as Data_Flow_Task_1 keeps names unique within a package. |
cte_<component> | The CTE for a source or transformation, for example cte_ole_db_source. When two components have the same name, a numeric suffix keeps the CTE names unique, for example cte_ole_db_source_1. |
tmp_cte_<component> | The same step, persisted as a temporary table because more than one later statement reads it. |
Direct loads¶
Two Data Flow shapes skip the CTE chain. They are decided by the shape of the whole graph, not by an option, so check the generated output to see which path each Data Flow took.
Direct INSERT¶
A Data Flow that only moves rows from one relational table into another relational table becomes a single INSERT INTO ... SELECT statement. The eligible shapes are a source that feeds a destination directly, or a source that feeds a destination through one Data Conversion or Row Count. The source must be an OLE DB, ADO.NET, or Oracle read of one named table or view, and the destination must be an OLE DB or Oracle destination that writes one named table. A source that runs a SQL command, takes its table name from a variable, or binds parameters isn’t eligible. A Data Conversion qualifies only when every conversion fails the component on error and truncation and the destination reads its normal output, and a Row Count qualifies only when it publishes exactly one output variable. A Data Flow inside a ForEach File container isn’t eligible either. The dbt output also uses this direct load.
Because the whole Data Flow is one statement, no procedure is created. The statement is written into the package Task:
When the Data Flow includes a Row Count, the Row Count variable is updated after the INSERT from SQLROWCOUNT:
Direct COPY¶
A Data Flow that only moves an eligible Flat File Source into an OLE DB Destination becomes a single COPY INTO statement. In the Snowflake Scripting format, the statement is created as a procedure, and the Task calls it with the package scope. 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. The file itself is read from the shared public.landing_stage.
Snowflake (Data_Flow_Task.sql):
Snowflake (etl_configuration/file_formats/file_formats.sql):
Every converted flat-file read binds to the generated public.landing_stage, so the file must be landed under the prefix the statement reads before the procedure runs. For the stage prefixes, the ingestion manifest, and the Openflow flows that land the files, see Ingestion. For ForEach File containers, which keep their loop in the Task graph, see ForEach File containers. A Flat File Source that isn’t an eligible Direct COPY becomes a placeholder. See Unsupported components.
Sources¶
OLE DB Source¶
An OLE DB Source becomes a CTE named cte_<component> that selects the exposed columns from the source table. In SQL command access mode, the command is converted as embedded SQL from Transact-SQL to Snowflake SQL and becomes the body of the CTE.
Example¶
Table or view access mode (cte_ole_db_source):
SQL command access mode:
When the migration input doesn’t include the definitions of the tables that the command reads, the CTE is flagged with SSC-FDM-0007.
A parameter marker (?) in the command is replaced by the package variable that the component binds it to. The procedure declares the variable at the top of its body and uses it as a bind variable:
Limitations¶
A source whose table name or SQL command comes from a variable isn’t converted, because the rows only exist once the source runs. It becomes a placeholder marked with SSC-EWI-SSIS0001.
A SQL command that can’t be converted to Snowflake SQL, or whose parameter markers don’t match its bindings, becomes a placeholder marked with SSC-EWI-SSIS0003. Declining a command with unmatched markers avoids emitting a query that silently filters on the wrong value. Convert the command by hand and replace the NULL columns:
ADO.NET Source and Oracle Source¶
An ADO.NET Source becomes a cte_ado_net_source CTE, and an Oracle Source becomes a cte_oracle_source CTE. Both follow the same rules as the OLE DB Source: a table read selects the exposed columns, and a SQL command is converted and used as the body of the CTE.
Excel Source¶
An Excel Source becomes a CTE that reads the staged workbook through the generated excel_source_udf function. The function takes the stage path of the workbook, which the conversion stores in a package variable, the sheet name, and whether the first row is a header. Each column is read from the row object and cast to its SSIS type: text columns use :: VARCHAR, and numeric columns use TRY_TO_DOUBLE.
The workbook must be landed on the stage before the procedure runs. See Ingestion.
Row-level transformations¶
Derived Column¶
A Derived Column becomes a CTE whose SELECT list carries one translated expression per derived column, followed by the columns that pass through unchanged. SSIS expressions become their Snowflake equivalents: string concatenation with + becomes ||, the SSIS equality operator == becomes =, and functions such as LOWER keep their names.
Example¶
SSIS expressions: FirstName + " " + LastName and LOWER(Email).
Limitations¶
If one expression in a Derived Column can’t be converted, the whole component becomes a placeholder marked with SSC-EWI-SSIS0001, and its columns return NULL. Fix the expression by hand, or use the dbt output format.
Data Convert¶
A Data Convert becomes a CTE that passes through the input columns and adds one cast for each conversion, aliased to the component’s output column name. Snowflake :: casts are used. In the following example, the list of pass-through columns is shortened:
Row Count¶
A Row Count becomes a pass-through CTE that selects its output columns from the CTE before it. The rows flow downstream unchanged:
Limitations¶
The procedure doesn’t write the row count into the SSIS package variable that the Row Count publishes, and the conversion doesn’t emit an EWI for it. If a later task reads that variable, set it yourself. The exception is the Direct INSERT shape, which updates the variable from SQLROWCOUNT.
Multicast¶
A Multicast becomes a pass-through CTE. When the Multicast feeds more than one destination, its result is read by more than one statement, so it’s persisted as a temporary table. See Multiple destinations.
Cache Transform¶
A Cache Transform becomes a pass-through CTE that selects its columns from the CTE before it. The cache itself isn’t materialized.
OLE DB Command¶
An OLE DB Command that runs a stored procedure becomes a call that runs once for each incoming row. The conversion persists the incoming rows as a temporary table, then runs an EXECUTE IMMEDIATE block that opens a cursor over them and calls the procedure for each row. Snowflake rejects :rec.COLUMN inside a CALL, so each field is first assigned to a declared variable and then passed as a bind variable.
For a command that runs dbo.usp_WriteHistory with the ID and NAME columns, the block that EXECUTE IMMEDIATE runs is:
The block calls the procedure once for each row, so a command that handles many rows runs many calls. A command that has no input columns becomes a single CALL <procedure>().
Limitations¶
An OLE DB Command that runs an INSERT, UPDATE, or DELETE statement isn’t converted in this format and becomes a placeholder marked with SSC-EWI-SSIS0001. The dbt output converts these commands into an incremental model.
Routing and combining data¶
Conditional Split¶
A Conditional Split is converted in one of two ways, depending on where its outputs go.
When one output continues the pipeline, the split becomes a CTE that filters the rows with that output’s condition. The SSIS condition IsActive == TRUE becomes IsActive = TRUE:
When every output writes directly to a destination, the split owns the writes. It persists the input rows in a temporary table with a routing column, ssis_output_name, that holds the name of the output each row belongs to. The conversion then emits one INSERT for each destination, filtered by that name. The routing column is a CASE expression that lists the conditions in the order SSIS evaluates them, so each row goes to the first output whose condition is true, and rows that match no condition go to the default output.
The INSERT statements for the Mid, Large, and Other rates outputs follow the same pattern.
Limitations¶
A Conditional Split that has an empty or duplicate output name, a condition that can’t be converted, or outputs that mix direct destinations with other components becomes a placeholder marked with SSC-EWI-SSIS0001.
The destinations in this example are Flat File Destinations. A Flat File Destination that receives rows from a Conditional Split is written with an INSERT into a table named after its connection manager. No file is written. Review these statements, and create the table or retarget the INSERT before you run the procedure. Flat File Destinations in any other position become placeholders. See Unsupported components.
Union All¶
A Union All becomes a CTE that combines every input with UNION ALL, which preserves duplicate rows the way SSIS does. Each input column is aliased to the Union All output column that it maps to:
Merge¶
A Merge becomes a CTE that combines its two inputs with UNION ALL. A column that exists in only one input is padded with NULL in the other branch.
SSIS requires sorted inputs for a Merge and produces a sorted output. UNION ALL doesn’t guarantee an order, so the CTE is flagged with SSC-FDM-SSIS0002. Add an ORDER BY to the final query if row order matters downstream.
Merge Join¶
A Merge Join becomes a CTE that joins its left and right inputs on the join keys. The join type of the component decides the SQL join: inner, left outer, or full outer. Each output column is selected from the input that SSIS maps it to, through the left_source and right_source aliases.
Like the Merge, the Merge Join assumes sorted inputs and produces a sorted output, so the CTE is flagged with SSC-FDM-SSIS0004.
Lookup¶
A Lookup that matches the supported shape becomes a temporary table that left-joins the input rows to the reference query, plus one INSERT for each destination the Lookup feeds. The temporary table adds a match marker column, ssis_lookup_matched_key, taken from the reference key. A matched output writes the rows where the marker isn’t NULL, and a no-match output writes the rows where it is NULL.
The Lookup is converted only when it has all of these properties: an OLE DB connection, the No cache mode, a SQL query for the reference rows, a single equality condition, and exactly one column that it adds to the rows. Its no-match behavior must be Fail component or Redirect rows to no match output, and its outputs must connect directly to destinations.
Example¶
A Lookup that enriches each row with RefName and writes the matched rows. In this example, the reference query returns constant rows, so the converted query is a VALUES list. The reference query is converted as embedded SQL, the same way an OLE DB Source SQL command is, so a query that reads a table keeps its FROM clause:
When the Lookup redirects unmatched rows to a no-match output, the destination of that output reads the rows where the marker is NULL:
Limitations¶
A Lookup outside the supported shape, such as one that caches its reference rows or joins on several columns, becomes a placeholder marked with SSC-EWI-SSIS0001. Use the dbt output format for those Lookups.
With Fail component as the no-match behavior, SSIS stops the Data Flow when a row has no match. The procedure doesn’t fail. It writes only the matched rows, so unmatched rows are dropped without an error. If a missing match must stop the load, add that check yourself.
Aggregation and ordering¶
Aggregate¶
An Aggregate becomes a CTE with a GROUP BY over the group-by columns. Each aggregation is renamed to the output column that SSIS exposes, so a group-by column can be renamed in the same step. An Aggregate with no group-by columns aggregates over the whole input and produces a single row.
Example¶
An Aggregate that groups by Name, exposes it as Category, and calculates a sum and a row count:
The aggregation operations map as follows:
| SSIS operation | Snowflake |
|---|---|
| Count all | COUNT(*) |
| Count | COUNT(col), which ignores NULL values |
| Count distinct | COUNT(DISTINCT col) |
| Sum, Minimum, Maximum | SUM(col), MIN(col), MAX(col) |
| Average, with a numeric or decimal output column | TRUNC(AVG(col), scale), truncated to the scale of the output column as SSIS does |
| Average, with a float output column | AVG(CAST(col AS FLOAT)), a double average even over an integer input |
The following example has a decimal output column AvgValue with scale 2 and a float output column AvgQty:
Limitations¶
An Aggregate with more than one output isn’t converted and becomes a placeholder marked with SSC-EWI-SSIS0001. An Average whose output column isn’t numeric, decimal, or float, such as a currency column, has no proven mapping, so the whole Aggregate becomes a placeholder too.
Snowflake groups text by exact value. When the Aggregate sets a comparison flag such as Ignore case, the grouping stays exact and the CTE is flagged with SSC-EWI-0073 so that you can review whether the difference matters for your data.
Sort¶
A Sort becomes a CTE with an ORDER BY over the sort keys, in key-precedence order and with each key’s direction. The columns that the Sort doesn’t use as a key pass through unchanged.
When the Sort removes duplicate rows, the CTE keeps one row for each distinct combination of the sort keys with QUALIFY ROW_NUMBER() = 1:
SSIS keeps the last row of each duplicate group in arrival order, which Snowflake doesn’t record. When two rows share the key but differ in other columns, the row that survives can differ from SSIS, and SSC-FDM-SSIS0030 reminds you to add the column that decides the survivor to the ORDER BY of the ROW_NUMBER window. A Sort that sets a comparison flag such as Ignore case keeps the exact-value comparison and also carries SSC-EWI-0073. A Sort that removes duplicates but has no sort keys produces a plain projection with no deduplication.
Pivot¶
A Pivot becomes a static pivot. The CTE computes a set key for each row, groups the rows by it, and builds one output column for each declared pivot value with MAX(CASE WHEN ... END).
Example¶
SSIS fails the Data Flow on a pivot key it doesn’t know and on a duplicate key for the same set. A SELECT has no direct way to fail on a row, so the CTE forces a runtime error: the TO_NUMBER calls try to convert the text SSIS Pivot invalid key: ... or SSIS Pivot duplicate key: ..., which fails the statement with an error that names the offending key. When the set keys arrive interleaved in the input, the CTE adds window functions that rebuild the contiguous segments SSIS reads, and a pass-through column takes its value from the first row of each set with FIRST_VALUE.
Limitations¶
A Pivot is converted only when the conversion can reproduce the order in which SSIS reads its input. The Pivot must have exactly one incoming path, and the output that feeds it must be marked as sorted, with sort key positions on its columns. The pivot values must be of a supported type, the declared pivot keys can’t repeat, and the Pivot can have only one set key. Otherwise, the Pivot becomes a placeholder marked with SSC-EWI-SSIS0001. In the following example, a Pivot without a declared input order is replaced by a placeholder while the rest of the Data Flow converts:
When Pass through unmatched pivot keys is enabled, SSIS sends rows with unknown pivot keys to a separate output. The CTE doesn’t do that, so those rows produce NULL values instead, and the CTE is flagged with SSC-FDM-SSIS0021.
UnPivot¶
An UnPivot becomes a CTE that turns each configured source column into a row. With one destination column, the CTE uses a Snowflake UNPIVOT, aliasing each source column to the pivot key value that the component declares. With several destination columns, it emits one SELECT for each key group and combines them with UNION ALL, so the values in each group stay aligned.
A row whose pivoted columns are all NULL is emitted once, with NULL values for the pivot key and the value, by an extra UNION ALL branch.
Example¶
Single destination column:
Destinations¶
OLE DB Destination and Oracle Destination¶
An OLE DB Destination or Oracle Destination in table or view access mode becomes an INSERT INTO <table> (<columns>) statement. The upstream CTEs are carried inside its source query, and the final SELECT reads the CTE of the component before the destination:
A destination that takes its table name from a variable or runs a SQL command becomes a placeholder.
Excel Destination¶
An Excel Destination is written as an INSERT into a table named after the Excel sheet, with the same shape as the OLE DB Destination. No Excel file is created, so create a table with the sheet name, or change the target of the INSERT, before you run the procedure:
Multiple destinations¶
When a Data Flow writes more than once, the shared step is read by more than one statement, and a CTE can’t be shared across statements. The conversion persists the step as a temporary table named tmp_cte_<component>, then emits one INSERT for each destination. A Multicast that feeds two OLE DB Destinations is the most common case:
Snowflake (Two_Destinations_Data_Flow.sql):
A Multicast that fans out to a single destination doesn’t need a temporary table. The temporary table lives only for the session that runs the procedure.
A complete example¶
The following Data Flow Task reads customers, keeps the active ones with a Conditional Split, derives a full name and a normalized email address, counts the rows, passes them through a Multicast, and loads them into a table. The Conditional Split condition is IsActive == TRUE, and the Derived Column expressions are FirstName + " " + LastName and LOWER(Email).
Snowflake (CompleteDataFlow.sql):
Snowflake (Complete_Data_Flow.sql):
The package declares the User::RowCountVar variable, which the root Task initializes. The Row Count in this procedure passes the rows through but doesn’t populate that variable. See Row Count.
Unsupported components¶
When a Data Flow uses a component that the Snowflake Scripting format doesn’t convert, the conversion keeps the Data Flow connected by emitting a placeholder CTE. The placeholder selects NULL for each output column and is marked with SSC-EWI-SSIS0001. Fuzzy Lookup is one such component:
Downstream components still read the placeholder by name, so the rest of the procedure is generated normally. When a destination reads a placeholder, the destination loads NULL values. For some components, such as Copy Column, the placeholder also includes the original component definition as comments, which makes the component easier to convert by hand.
This example Data Flow has no destination, so the conversion closes the chain of CTEs with a trivial SELECT 1 to keep the statement valid. A Data Flow with a destination ends in the destination’s INSERT instead. A destination that becomes a placeholder, such as a Flat File Destination, also ends the Data Flow in SELECT 1, so nothing is written.
The following components become a placeholder:
- Flat File Source, unless the Data Flow is an eligible Direct COPY.
- Flat File Destination, unless it receives rows directly from a Conditional Split, as described in Conditional Split.
- Character Map, Fuzzy Lookup, and Script Component.
- Any component that isn’t listed in Supported components, such as Copy Column.
- A supported component that is outside the shape the conversion handles, such as a Lookup that caches its reference rows, an Aggregate with more than one output, or an OLE DB Command that runs
INSERT,UPDATE, orDELETE. The Limitations of each component describe its shape.
Convert the flagged component manually, or generate the package in the dbt output format, which supports more components today. Each placeholder is also listed in the conversion issues report, with the component that produced it.