Module 7

Pipelines

Fabric Data Factory Pipelines provide orchestration for moving and processing data across sources and Fabric destinations.

18 lessonsMicrosoft FabricDP-700 Track
Module 7 · Lesson 7.1

Pipeline Activities

7.1 Pipeline Activities

7.1.1 What is a Pipeline Activity?

A pipeline activity is an individual task performed inside a Microsoft Fabric Data Factory pipeline.

Think of a pipeline as a workflow and activities as the individual steps:

Pipeline
   │
   ├── Activity 1 → Read configuration
   │
   ├── Activity 2 → Copy data
   │
   ├── Activity 3 → Transform data
   │
   └── Activity 4 → Load Warehouse

For example, an ETL pipeline could be:

SQL Server
    │
    ▼
Lookup Configuration
    │
    ▼
Copy Data
    │
    ▼
Notebook
    │
    ▼
Warehouse
    │
    ▼

Audit

7.1.2 Major Categories of Activities

Fabric Data Factory activities can broadly be thought of as:

Pipeline Activities
│
├── Data Movement
│    └── Copy Data
│
├── Transformation / Processing
│    ├── Notebook
│    ├── Dataflow Gen2
│    └── SQL-related activities
│
├── Control Flow
│    ├── ForEach
│    ├── If Condition
│    ├── Switch
│    ├── Until
│    └── Wait
│
├── Lookup / Metadata
│    └── Lookup
│
├── SQL / Database
│    └── Stored Procedure / SQL execution
│
└── External Integration
     └── Web / HTTP-based activities

The exact activity catalog can change as Microsoft adds capabilities to Fabric Data Factory.

7.1.3 Copy Data Activity

The Copy Data activity is one of the most important activities for data engineers.

Its purpose is:

Move data from a source to a destination.

Example:

SQL Server
    │
    ▼
Copy Data
    │
    ▼

Fabric Lakehouse

Another example:

Azure Blob
    │
    ▼
Copy Data
    │
    ▼

Warehouse

Example: SQL Server → Lakehouse

SQL Server.Customers
        │
        ▼
     Copy Data
        │
        ▼

Bronze_Customers

A typical configuration includes:

Source

Connection: SQL Server

Table: Customers

Destination

Connection: Fabric

Destination: Lakehouse

Table: Bronze_Customers

7.1.4 Copy Data Modes

Depending on the connector and scenario, Copy Data can support patterns such as:

Full Load

Incremental Load

Partitioned/Parallel Copy

Full Load

Source
  │
  ▼
Read all records
  │
  ▼

Destination

Incremental Load

Source
  │
  ▼
Changed records only
  │
  ▼

Destination

Example:

SELECT *

FROM Orders

WHERE ModifiedDate > @LastWatermark;

7.1.5 Lookup Activity

Lookup retrieves data that the pipeline can use to make decisions.

Example:

PipelineConfig
      │
      ▼
   Lookup
      │
      ▼

Configuration

Suppose the configuration table contains:

SourceTableTargetTableLoadType
CustomersBronze_CustomersFull
ProductsBronze_ProductsFull
OrdersBronze_OrdersIncremental

The Lookup can retrieve these records.

7.1.6 Lookup + ForEach

This is a very important real-world pattern.

              Lookup
                │
                ▼

[Customers, Products, Orders]

                │
                ▼
             ForEach
                │
       ┌────────┼────────┐
       ▼        ▼        ▼
    Customers Products   Orders
       │        │        │
       ▼        ▼        ▼

Copy Copy Copy

Instead of creating three pipelines, you can create one metadata-driven pipeline.

7.1.7 ForEach Activity

ForEach repeats activities for each item in a collection.

Suppose Lookup returns:

Customers

Products

Orders

ForEach executes:

For each item:

Copy source → target

Conceptually:

ForEach
  │
  ├── Customer → Copy
  ├── Product  → Copy
  └── Order    → Copy

Inside ForEach, the current item is typically referenced through dynamic content such as:

item()

For an object:

item().SourceTable

7.1.8 Switch Activity

Switch chooses a branch based on an expression.

Example:

LoadType
   │
   ├── Full
   │     ↓
   │   Full Load
   │
   ├── Incremental
   │     ↓
   │   Incremental Load
   │
   └── CDC

CDC Load

For example:

LoadType = Incremental

results in:

Incremental branch

This is particularly useful in metadata-driven pipelines.

7.1.9 If Condition Activity

If Condition evaluates a Boolean expression.

Example:

IsActive?

   │
 ┌─┴───────┐
Yes        No
 │          │
 ▼          ▼

Copy Skip

Example condition:

IsActive == true

Another example:

RecordCount > 0

Then:

TRUE

Continue processing

FALSE

Skip / Alert

7.1.10 Notebook Activity

Fabric pipelines can orchestrate notebook execution.

Example:

Copy Data
    │
    ▼
Bronze
    │
    ▼
Notebook
    │
    ▼

Silver

The notebook could perform:

df = spark.read.table("Bronze_Orders")

clean_df = df.dropDuplicates(
    ["OrderID"]

)

clean_df.write \

    .format("delta") \
    .mode("overwrite") \
    .saveAsTable("Silver_Orders")

This creates a powerful combination:

Data Factory
     +

Spark

7.1.11 Dataflow Gen2 Activity

A pipeline can orchestrate Dataflow Gen2 for low-code transformation.

Example:

SQL Server
    │
    ▼
Dataflow Gen2
    │
    ├── Filter
    ├── Clean
    ├── Transform
    └── Join
    │
    ▼

Lakehouse / Warehouse

Use Dataflow Gen2 when a graphical transformation experience is preferable.

7.1.12 Stored Procedure Activity

A pipeline can execute stored-procedure-based SQL logic where supported.

Example:

Copy Data
    │
    ▼
Stored Procedure
    │
    ▼

Update Audit

For example:

EXEC dbo.usp_UpdateSalesSummary;

A procedure could:

Update audit information

Perform warehouse transformations

Reconcile data

Execute reusable SQL business logic

7.1.13 SQL Execution Activity

SQL execution can be used for database-related processing where supported by the Fabric Data Factory experience.

Example:

Silver Load
    │
    ▼
SQL Processing
    │
    ▼

FactSales

For example:

INSERT INTO dbo.FactSales

(

SalesKey,

CustomerKey,

ProductKey,

SalesAmount

)

SELECT

SalesKey,

CustomerKey,

ProductKey,

SalesAmount

FROM dbo.StageSales;

7.1.14 Web Activity

A Web/HTTP activity can be used to call an external web endpoint where supported.

Example:

Pipeline
   │
   ▼
Web Activity
   │
   ▼

REST API

For example:

Fabric Pipeline
       │
       ▼
HTTP Request
       │
       ▼
External API
       │
       ▼

JSON Response

This is useful for integrations with external systems.

7.1.15 Wait Activity

Wait pauses pipeline execution.

Example:

Start
  │
  ▼
Copy Data
  │
  ▼
Wait 5 minutes
  │
  ▼

Check Status

Possible use cases:

Waiting for an external process

Polling patterns

Coordinating asynchronous operations

Avoid unnecessary waits because they increase pipeline execution time.

7.1.16 Until Activity

Until repeats activities until a condition becomes true.

Conceptually:

Start
  │
  ▼
Check Status
  │
  ├── Not Ready ──► Wait ──► Check Again
  │
  └── Ready ──────► Continue

Example:

Until

IsFileAvailable = true

This can be useful when waiting for an external file or process.

Use appropriate timeout limits to avoid infinite loops.

7.1.17 Set Variable Activity

Used to assign a value to a pipeline variable.

Example:

Start
  │
  ▼
Set Variable
  │
  ▼

Copy Data

Variable:

BatchID = 20260822_001

Then later activities can use the value.

7.1.18 Append Variable Activity

Useful when building an array/list during pipeline execution.

Conceptually:

Array Variable
     │
     ├── Customer
     ├── Product
     └── Order

This can be useful for collecting processing results.

7.1.19 Activity Dependencies

Activities don't necessarily execute independently.

You can define dependencies such as:

Success

Failure

Completion

Skipped

Example:

Copy Customers
      │
   Success
      ▼
Transform Customers
      │
   Success
      ▼

Load Warehouse

7.1.20 Success Path
┌──────────────┐
│  Copy Data   │
└──────┬───────┘
       │ Success
       ▼
┌──────────────┐
│   Notebook   │
└──────┬───────┘
       │ Success
       ▼
┌──────────────┐
│   Warehouse  │
└──────────────┘
7.1.21 Failure Path
┌──────────────┐
│  Copy Data   │
└──────┬───────┘
       │ Failure
       ▼
┌──────────────┐
│  Log Error   │
└──────┬───────┘
       ▼
┌──────────────┐
│    Alert     │
└──────────────┘

This pattern is essential for production pipelines.

7.1.22 Completion Path

Sometimes cleanup should happen regardless of whether the previous activity succeeded or failed.

Copy Data

/ \

Success Failure

\ /

\ /

Cleanup

Use the appropriate completion dependency.

7.1.23 Activity Retry

Transient failures can sometimes be retried.

Copy Data
    │
 Failure
    ▼
 Retry
    │
 ┌──┴──────────┐
 ▼             ▼
Success      Failure
 │             │
 ▼             ▼

Continue Alert

Good candidates:

Temporary network failures

Service throttling

Temporary unavailable resources

Bad candidates:

Invalid SQL

Invalid credentials

Invalid table names

Schema errors

7.1.24 Activity Timeout

Long-running activities should have sensible timeout settings.

For example:

Copy Data
   │
   └── Timeout → Failure Handler

This prevents a pipeline from waiting indefinitely.

7.1.25 Activity Outputs

Many activities produce outputs that can be consumed by later activities.

Example:

Lookup
  │
  ▼
Output
  │
  ▼

ForEach

A Lookup might return:

[

{

"Table": "Customers"

},

{

"Table": "Products"

}

]

ForEach can consume this collection.

7.1.26 Dynamic Content

Dynamic expressions allow pipeline activities to work with runtime values.

Common concepts include:

pipeline parameters

pipeline variables

activity outputs

current item

system/runtime values

Examples:

@pipeline().parameters.TableName

@variables('BatchID')

@item().SourceTable

@activity('LookupConfig').output

The exact output structure depends on the activity.

7.1.27 Example: Dynamic Copy Pipeline

Suppose configuration contains:

SourceTable       TargetTable
-----------------------------------

Customers Bronze_Customers

Products Bronze_Products

Orders Bronze_Orders

Pipeline:

Lookup
  │
  ▼
ForEach
  │
  ▼

Copy Data

Inside Copy:

Source:

@item().SourceTable

Target:

@item().TargetTable

Now one pipeline can process all three tables.

7.1.28 Parallel Execution

Suppose:

Customers

Products

Orders

are independent.

You can process them concurrently:

             ForEach
                │
       ┌────────┼────────┐
       ▼        ▼        ▼
   Customers Products  Orders
       │        │        │
       ▼        ▼        ▼

Copy Copy Copy

This can reduce total execution time.

But don't use unrestricted parallelism when:

Source DB cannot handle the load.

API has rate limits.

Destination capacity is limited.

Tables have dependencies.

7.1.29 Sequential Execution

If dependencies exist:

Customer
   │
   ▼
Product
   │
   ▼

Order

use sequential processing where appropriate.

For example, if Orders depend on Products being loaded first:

Load Products
      │
      ▼

Load Orders

7.1.30 Real-World Pipeline Example

Let's build:

SQL Server → Bronze Lakehouse → Silver Lakehouse → Warehouse

Pipeline:
┌─────────────────────┐
│ Lookup Configuration│
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│       ForEach       │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│       Switch        │
│ Full/Incremental    │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│      Copy Data      │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│   Bronze Lakehouse  │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│      Notebook       │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│   Silver Lakehouse  │
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│ Warehouse Processing│
└──────────┬──────────┘
           ▼
┌─────────────────────┐
│    Audit / Logging  │
└─────────────────────┘

Failure:

Any Activity
     │
   Failure
     ▼
Error Handler
     │
 ┌───┴────┐
 ▼        ▼

Audit Alert

7.1.31 Activity Selection Guide

RequirementRecommended Activity
Move dataCopy Data
Read configurationLookup
Process listForEach
Choose processing pathSwitch
Boolean decisionIf Condition
Run Spark transformationNotebook
Low-code transformationDataflow Gen2
Execute SQL logicSQL/Stored Procedure activity where supported
Call external serviceWeb
WaitWait
Repeat until conditionUntil
Store runtime valueSet Variable
Collect valuesAppend Variable

7.1.32 Recommended Pipeline Design

For an enterprise ingestion pipeline:

                 START
                   │
                   ▼
             Set Variables
                   │
                   ▼
          Lookup Configuration
                   │
                   ▼
                ForEach
                   │
                   ▼
                Switch
                   │
        ┌──────────┴──────────┐
        ▼                     ▼
      Full                Incremental
        │                     │
        └──────────┬──────────┘
                   ▼
               Copy Data
                   │
             ┌─────┴─────┐
             ▼           ▼
          Success      Failure
             │           │
             ▼           ▼
          Notebook     Log Error
             │           │
             ▼           ▼
          Silver       Alert
             │
             ▼
         Warehouse
             │
             ▼

Audit

7.1.33 Best Practices

1. Make pipelines reusable

Use parameters instead of hardcoding table names.

2. Prefer metadata-driven processing

Use:

Lookup → ForEach → Copy

instead of creating hundreds of nearly identical pipelines.

3. Separate responsibilities

For example:

Pipeline → Orchestration

Notebook → Spark transformation

Warehouse → SQL serving

4. Implement failure paths

Every important activity should have appropriate failure handling.

5. Use meaningful names

Instead of:

Copy1

Copy2

Activity3

use:

Copy_Customers

Copy_Products

Transform_Orders

Load_FactSales

6. Monitor execution

Track:

Pipeline run ID

Activity status

Duration

Rows read

Rows written

Error message

7. Avoid unnecessary activities

Don't create a complicated pipeline when a simpler design will work.

7.1.34 Interview Questions

What is a pipeline activity?

An individual task executed within a data pipeline.

What is Copy Data used for?

Moving data between supported source and destination systems.

What is Lookup used for?

Retrieving configuration or data for use by subsequent pipeline activities.

Why use ForEach?

To process multiple items using the same workflow.

Why use Switch?

To select one processing branch based on a value.

Difference between If Condition and Switch?

If Condition

→ True / False

Switch

→ Multiple possible cases

What is a Notebook activity used for?

To orchestrate Spark notebook-based transformations.

Why are dependencies important?

They control execution order and allow success/failure handling.

How do you build a metadata-driven pipeline?

Configuration

Lookup

ForEach

Dynamic Copy

How do you handle failures?

Use:

Failure dependency

Error logging

Retry where appropriate

Notification

7.1.35 Final Cheat Sheet

COPY DATA

Move data

LOOKUP

Get configuration/data

FOREACH

Loop through items

SWITCH

Choose one of multiple branches

IF CONDITION

True/False decision

NOTEBOOK

Run Spark/PySpark processing

DATAFLOW GEN2

Low-code data transformation

STORED PROCEDURE

Execute reusable SQL logic

WEB

Call external HTTP service

WAIT

Pause execution

UNTIL

Repeat until condition

SET VARIABLE

Set runtime value

ACTIVITY DEPENDENCY

Control execution flow

RETRY

Recover from transient failures

Key Concept

The most important pipeline pattern to remember is:

       CONFIGURATION
             │
             ▼
          LOOKUP
             │
             ▼
          FOREACH
             │
             ▼
          SWITCH
             │
       ┌─────┴─────┐
       ▼           ▼
      FULL     INCREMENTAL
       │           │
       └─────┬─────┘
             ▼
         COPY DATA
             │
             ▼
          BRONZE
             │
             ▼
         NOTEBOOK
             │
             ▼
          SILVER
             │
             ▼
         WAREHOUSE
             │
             ▼

POWER BI

This pattern gives you the foundation for building reusable, metadata-driven, production-grade Fabric ETL pipelines.

↑ Back to top
Module 7 · Lesson 7.2

Module 7 · Lesson 7.2

Variables

7.2 Variables

7.2.1 What is a Pipeline Variable?

A pipeline variable is a value that can be stored and used during the execution of a Microsoft Fabric Data Factory pipeline.

Variables are useful when a pipeline needs to remember or change a value while it is running.

For example:

Pipeline starts
      │
      ▼

BatchID = 20260822

      │
      ▼
Copy Data
      │
      ▼

Status = SUCCESS

      │
      ▼

Audit

Think of a variable as a temporary storage area inside the pipeline run.

7.2.2 Why Do We Need Variables?

Without variables, you may have to hardcode values repeatedly.

For example:

BatchID = 20260822_001

Instead, create:

Variable:

Name = BatchID

Type = String

Then activities can use the variable dynamically.

Variables are useful for:

Batch IDs

File names

Table names

Status values

Environment names

Counters

Flags

Runtime values

Arrays/lists

7.2.3 Variables vs Parameters

This is an important interview question.

FeatureParameterVariable
Supplied from outsideYesNo
Value available at pipeline startYesInitialized by pipeline
Can change during executionGenerally noYes
PurposeConfigure pipelineMaintain runtime state
ExampleSourceTableCurrentTable

Simple rule

Parameter

"What should this pipeline process?"

Variable

"What value should the pipeline remember/change while running?"

7.2.4 Example

Suppose we create:

Variable Name:

Environment

Type:

String

Initial Value:

DEV

The pipeline can use:

Environment = DEV

Later, another activity can use the variable in dynamic content.

Conceptually:

@variables('Environment')

7.2.5 Common Variable Types

Depending on the current Fabric Data Factory interface, variables commonly include:

String

Integer

Boolean

Array

String

BatchID = "20260822_001"

Integer

RetryCount = 3

Boolean

IsSuccess = true

Array

Tables =

[

"Customers",

"Products",

"Orders"

]

7.2.6 Creating a Variable

In a pipeline, define a variable such as:

Name:

BatchID

Type:

String

Default/Initial Value:

20260822_001

Then the pipeline can reference it.

7.2.7 Set Variable Activity

The Set Variable activity changes the value of a variable.

Example:

Variable:

Status

Initial:

STARTED

Pipeline:

Start
  │
  ▼
Set Variable
  │
  │ Status = RUNNING
  ▼

Copy Data

After successful completion:

Status = SUCCESS

7.2.8 Example: Pipeline Status

Create:

Variable:

PipelineStatus

Initial value:

STARTED

Pipeline:

Start
  │
  ▼

Set PipelineStatus = RUNNING

  │
  ▼
Copy Data
  │
  ▼

Set PipelineStatus = SUCCESS

Failure path:

Copy Data
    │
 Failure
    ▼

Set PipelineStatus = FAILED

Then:

Audit

can record the status.

7.2.9 Dynamic Content

Variables become useful when combined with dynamic expressions.

Example:

@variables('PipelineStatus')

If:

PipelineStatus = SUCCESS

the expression returns:

SUCCESS

Similarly:

@variables('BatchID')

could return:

20260822_001

7.2.10 Variables in File Names

Suppose:

Variable:

FileName

Value:

customers.csv

A destination path can be dynamically constructed using the variable.

Conceptually:

Files/

customers.csv

If the variable changes:

FileName = products.csv

the same pipeline can process:

Files/

products.csv

This makes pipelines reusable.

7.2.11 Variables in Table Processing

Suppose:

CurrentTable = Customers

A pipeline can use:

@variables('CurrentTable')

for dynamic processing where the relevant activity/property supports expressions.

Then:

CurrentTable = Products

allows the same pipeline logic to process Products.

However, for metadata-driven pipelines, parameters + Lookup + ForEach are often a better pattern than relying heavily on mutable variables.

7.2.12 Variable for Batch ID

A very common production pattern is:

BatchID

Example:

BatchID = 20260822_140501

Pipeline:

Generate/Set Batch ID
        │
        ▼
Copy Data
        │
        ▼
Transform
        │
        ▼
Warehouse
        │
        ▼

Audit

Every stage can associate its records with the same batch.

Example audit record:

BatchIDPipelineStatus
20260822_140501PL_SalesSUCCESS

7.2.13 Variable for Environment

Create:

Environment = DEV

Then:

DEV

Development configuration

TEST

Test configuration

PROD

Production configuration

For example:

Environment = PROD

could be used to select the appropriate configuration.

In practice, pipeline parameters and environment-specific configuration are usually preferable for deployment configuration rather than changing a variable manually.

7.2.14 Boolean Variable

A Boolean variable can act as a flag.

Example:

IsValid = false

Pipeline:

Validation
    │
    ▼
Set IsValid
    │
    ▼

If Condition

Conceptually:

IsValid == true

      │
   ┌──┴──┐
  Yes    No
   │      │
   ▼      ▼

Continue Reject

7.2.15 Integer Variable

An integer variable can store a number.

Example:

RetryCount = 0

You might increment it during controlled processing.

Conceptually:

RetryCount = 0

RetryCount = 1

RetryCount = 2

RetryCount = 3

Be careful with concurrent ForEach execution because shared mutable variables can introduce unexpected behavior.

7.2.16 Array Variable

An array can contain multiple values.

Example:

Tables =

[

"Customers",

"Products",

"Orders"

]

Conceptually:

Tables
  │
  ├── Customers
  ├── Products
  └── Orders

It can then be used with looping/control-flow patterns.

7.2.17 Append Variable

The Append Variable pattern adds a value to an array variable.

Start:

ProcessedTables = []

After Customers:

ProcessedTables =

[

"Customers"

]

After Products:

ProcessedTables =

[

"Customers",

"Products"

]

After Orders:

ProcessedTables =

[

"Customers",

"Products",

"Orders"

]

This can be useful for collecting runtime results.

7.2.18 Variables with ForEach

Suppose:

ForEach:

Customers

Products

Orders

You might want to track processing results.

ForEach
   │
   ├── Customers → Success
   │
   ├── Products  → Success
   │
   └── Orders    → Success

An array variable could collect:

[

"Customers",

"Products",

"Orders"

]

However, if ForEach executes in parallel, be cautious about modifying shared variables from concurrent iterations. Prefer activity outputs or metadata-driven patterns where possible.

7.2.19 Variables + Lookup

A common pattern is:

Lookup
   │
   ▼
Set Variable
   │
   ▼

Copy Data

Example Lookup returns:

Watermark = 2026-08-21 23:59:59

Set:

LastWatermark

Then Copy Data uses it.

Conceptually:

Lookup
  │
  ▼
LastWatermark
  │
  ▼

Incremental Copy

7.2.20 Variables for Incremental Loading

Suppose the source table contains:

OrderID

Amount

ModifiedDate

We store:

LastWatermark

Example:

LastWatermark =

2026-08-21 23:59:59

Then retrieve:

SELECT *

FROM Orders

WHERE ModifiedDate > @LastWatermark;

After successful processing:

NewWatermark =

2026-08-22 14:30:00

Pipeline:

Get Watermark
      │
      ▼
Set Variable
      │
      ▼
Copy Changed Records
      │
      ▼
Process Data
      │
      ▼

Update Watermark

For production systems, storing the watermark in a persistent control table is usually preferable to relying only on a pipeline variable, because a variable exists only for that pipeline run.

7.2.21 Variables + Error Handling

Example:

PipelineStatus = STARTED

        │
        ▼
Copy Data
        │
   ┌────┴────┐
   ▼         ▼
Success    Failure
   │         │
   ▼         ▼
SUCCESS    FAILED
   │         │
   └────┬────┘
        ▼

Audit

Audit can record:

PipelineName

RunID

PipelineStatus

7.2.22 Variables + Audit Logging

Suppose you define:

BatchID

PipelineStatus

RecordCount

Pipeline:

Start
 │
 ▼
Set BatchID
 │
 ▼
Copy Data
 │
 ▼
Set Status
 │
 ▼

Audit

Audit table:

CREATE TABLE dbo.PipelineAudit

(

PipelineName VARCHAR(200),

BatchID VARCHAR(100),

Status VARCHAR(50),

RecordCount BIGINT

);

Then the pipeline can record:

PL_Sales

20260822_001

SUCCESS

1250000

7.2.23 Variable Scope

A pipeline variable belongs to the pipeline execution context.

Conceptually:

Pipeline Run
│
├── Variables
│     ├── BatchID
│     ├── Status
│     └── Environment
│
├── Activity 1
├── Activity 2
└── Activity 3

It isn't a permanent database value.

If the pipeline finishes:

Pipeline Run Ends

Runtime variables disappear

If you need the value tomorrow, store it in a persistent location such as a control/audit table.

7.2.24 Variable vs Database Control Table

This distinction is very important for real-world pipelines.

Variable

Temporary runtime state

Example:

CurrentTable = Orders

Control Table

Persistent pipeline state

Example:

Orders

LastWatermark = 2026-08-22 14:00:00

Architecture:

                Pipeline
                   │
          ┌────────┴────────┐
          ▼                 ▼

Variables Control Table

Runtime only Persistent

Use variables for current execution state and control tables for persistent operational state.

7.2.25 Parameters + Variables Together

A good reusable pipeline might look like:

Parameters
│
├── SourceSystem
├── TargetTable
└── LoadType
       │
       ▼
Pipeline
       │
       ├── Variable: BatchID
       ├── Variable: Status
       └── Variable: RecordCount

Example:

Parameter:

SourceTable = Orders

Parameter:

LoadType = Incremental

Variable:

BatchID = 20260822_001

Variable:

Status = RUNNING

7.2.26 Real-World Example

Let's build an incremental Orders pipeline.

Parameters

SourceTable = Orders

TargetTable = Bronze_Orders

Variables

BatchID

PipelineStatus

LastWatermark

Pipeline:

Start
 │
 ▼
Set BatchID
 │
 ▼

Set Status = RUNNING

 │
 ▼
Lookup LastWatermark
 │
 ▼
Set LastWatermark
 │
 ▼
Copy Incremental Orders
 │
 ├──────── Failure ────────► Status = FAILED
 │                              │
 │                              ▼
 │                            Audit
 │
 ▼ Success

Set Status = SUCCESS

 │
 ▼
Update Watermark
 │
 ▼

Audit

7.2.27 Example Expressions

Common dynamic-content concepts include:

Pipeline parameter

@pipeline().parameters.SourceTable

Pipeline variable

@variables('BatchID')

Current ForEach item

@item()

Current item's property

@item().SourceTable

Activity output

@activity('Lookup_Config').output

The exact expression required depends on the activity's output structure and the property into which you're inserting the expression.

7.2.28 Common Mistakes

Mistake 1 — Using variables for permanent state

Don't use:

Variable: LastWatermark

as the only persistent watermark mechanism.

Use a control table or other persistent state.

Mistake 2 — Hardcoding values

Instead of:

Bronze_Orders

everywhere, use parameters/configuration where appropriate.

Mistake 3 — Excessive variables

Don't create variables for every value.

Use:

Parameters → configuration

Variables → runtime state

Mistake 4 — Shared variables in parallel ForEach

Parallel iterations can make mutable shared state difficult to reason about.

Prefer independent processing and activity outputs where possible.

Mistake 5 — Confusing variables with parameters

Remember:

Parameter = input

Variable = runtime state

7.2.29 Best Practices

Use parameters for reusable configuration

SourceTable

TargetTable

LoadType

Use variables for runtime state

BatchID

Status

Counter

Persist important state

For example:

Watermark

Pipeline status

Audit information

should generally be persisted externally if needed across runs.

Use meaningful names

Good:

v_BatchID

v_PipelineStatus

v_RecordCount

or a consistent naming convention such as:

BatchID

PipelineStatus

RecordCount

Keep variables simple

If the pipeline can derive a value directly from an activity output, don't necessarily create a separate variable.

7.2.30 Hands-On Exercise

Create a pipeline:

PL_Orders_Load

Variables

Create:

BatchID → String

PipelineStatus → String

RecordCount → Integer

IsValid → Boolean

Initialize:

PipelineStatus = STARTED

RecordCount = 0

IsValid = false

Step 1 — Set Batch ID

Set:

BatchID = runtime-generated value

Step 2 — Set Status

PipelineStatus = RUNNING

Step 3 — Copy Orders

SQL Server

Copy Data

Bronze_Orders

Step 4 — Validate

RecordCount > 0

Set:

IsValid = true

Step 5 — Success

PipelineStatus = SUCCESS

Step 6 — Failure

Failure dependency:

PipelineStatus = FAILED

Step 7 — Audit

Store:

PipelineName

BatchID

PipelineStatus

RecordCount

7.2.31 Interview Questions

1. What is a pipeline variable?

A runtime value stored and potentially modified during pipeline execution.

2. What is the difference between parameter and variable?

Parameter → input/configuration

Variable → runtime state

3. Can a variable change during pipeline execution?

Yes, through supported variable activities.

4. What is a String variable used for?

Values such as:

FileName

BatchID

Status

TableName

5. What is an Array variable?

A variable containing multiple values.

Example:

[

"Customers",

"Products",

"Orders"

]

6. What is Set Variable used for?

To assign/change a variable value.

7. What is Append Variable used for?

To add a value to an array variable.

8. Should a variable be used for persistent watermarks?

Not by itself. Persist the watermark in a control table or another durable state store.

9. Can variables be problematic in parallel ForEach?

Yes. Shared mutable variables can create concurrency issues.

10. Why use variables?

To maintain runtime state and make pipeline execution dynamic.

7.2.32 Variables Cheat Sheet

              PIPELINE
                  │
                  ▼

PARAMETERS

"What should I do?"

                  │
                  ▼

VARIABLES

"What do I know now?"

                  │
       ┌──────────┼──────────┐
       ▼          ▼          ▼
    String     Integer     Boolean
       │          │          │
       └──────────┼──────────┘
                  ▼
                ARRAY
                  │
                  ▼

Pipeline Activities

Remember

PARAMETER

Input to pipeline

VARIABLE

Runtime value

SET VARIABLE

Change a variable

APPEND VARIABLE

Add to array

CONTROL TABLE

Persistent state

The most important distinction is: parameters configure a pipeline, while variables maintain runtime state during pipeline execution.

↑ Back to top
Module 7 · Lesson 7.4

Module 7 · Lesson 7.3

Parameters

7.3 Parameters

7.3.1 What is a Pipeline Parameter?

A pipeline parameter is an input value supplied to a Microsoft Fabric Data Factory pipeline when the pipeline is executed.

Parameters make pipelines reusable, configurable, and metadata-driven.

Instead of hardcoding:

SQL Server → Customers → Bronze_Customers

you can build one generic pipeline:

SourceTable → Parameter

TargetTable → Parameter

LoadType → Parameter

and reuse it for:

Customers

Products

Orders

Inventory

7.3.2 Why Use Parameters?

Consider three separate pipelines:

PL_Customers

PL_Products

PL_Orders

All three perform almost the same operation:

Source → Copy → Lakehouse

This creates duplicate logic.

Instead, create:

PL_Generic_Copy

with parameters:

SourceTable

TargetTable

LoadType

Then:

PL_Generic_Copy
       │
       ├── Customers → Bronze_Customers
       ├── Products  → Bronze_Products
       └── Orders    → Bronze_Orders

One pipeline, multiple use cases.

7.3.3 Parameter vs Variable

This is one of the most important concepts in Fabric pipelines.

ParameterVariable
Input to pipelineRuntime value
Usually defined before executionUsed during execution
Passed by caller/triggerSet/changed by pipeline
Normally immutable during runCan be modified
Used for configurationUsed for runtime state
Example: SourceTableExample: BatchID

Simple rule

Parameter = What should the pipeline do?

Variable = What value does the pipeline need to remember while running?

7.3.4 Example

Suppose we create:

Pipeline:

PL_Generic_Copy

Parameters:

SourceTable

TargetTable

Run 1:

SourceTable = Customers

TargetTable = Bronze_Customers

Run 2:

SourceTable = Products

TargetTable = Bronze_Products

Run 3:

SourceTable = Orders

TargetTable = Bronze_Orders

The pipeline logic doesn't change.

7.3.5 Common Parameter Types

Pipeline parameters commonly use types such as:

String

Integer

Boolean

Array

String

SourceTable = "Customers"

Integer

BatchSize = 100000

Boolean

IsIncremental = true

Array

Tables =

[

"Customers",

"Products",

"Orders"

]

The exact parameter types available can depend on the current Fabric Data Factory experience.

7.3.6 Creating Parameters

Inside a pipeline, define parameters such as:

Parameter Name: SourceTable

Type: String

Parameter Name: TargetTable

Type: String

Parameter Name: LoadType

Type: String

You might have:

SourceTable = Orders

TargetTable = Bronze_Orders

LoadType = Incremental

7.3.7 Using Parameters in Activities

Parameters become powerful when used as dynamic content.

For example:

@pipeline().parameters.SourceTable

If:

SourceTable = Orders

the expression resolves to:

Orders

Similarly:

@pipeline().parameters.TargetTable

could resolve to:

Bronze_Orders

7.3.8 Parameterized Copy Pipeline

Let's create:

PL_Generic_Copy

Parameters:

SourceTable

TargetTable

Pipeline:

Start
  │
  ▼
Copy Data
  │
  ▼

Target

Copy activity:

Source:

@pipeline().parameters.SourceTable

Target:

@pipeline().parameters.TargetTable

Now the same pipeline can process many tables.

7.3.9 Example — Customers

Pipeline parameters:

SourceTable = Customers

TargetTable = Bronze_Customers

Execution:

SQL Server.Customers
        │
        ▼
    Copy Data
        │
        ▼

Bronze_Customers

7.3.10 Example — Products

Same pipeline:

SourceTable = Products

TargetTable = Bronze_Products

Execution:

SQL Server.Products
        │
        ▼
    Copy Data
        │
        ▼

Bronze_Products

No pipeline redesign is required.

7.3.11 Example — Orders

SourceTable = Orders

TargetTable = Bronze_Orders

Execution:

SQL Server.Orders
        │
        ▼
    Copy Data
        │
        ▼

Bronze_Orders

7.3.12 LoadType Parameter

A very useful parameter is:

LoadType

Possible values:

Full

Incremental

CDC

Pipeline:

             LoadType
                │
        ┌───────┼────────┐
        ▼       ▼        ▼
       Full Incremental  CDC
        │       │        │
        ▼       ▼        ▼

Full Delta CDC

Load Load Load

Example:

LoadType = Incremental

The pipeline can use a Switch activity to select the incremental branch.

7.3.13 Parameterized Incremental Load

Parameters:

SourceTable

TargetTable

WatermarkColumn

LoadType

Example:

SourceTable = Orders

TargetTable = Bronze_Orders

WatermarkColumn = ModifiedDate

LoadType = Incremental

Pipeline:

Parameters
    │
    ▼
Switch
    │
    ▼
Incremental Load
    │
    ▼

Copy Data

7.3.14 Environment Parameter

You can also parameterize environment-specific behavior.

Example:

Environment

Values:

DEV

TEST

PROD

Pipeline:

Environment = DEV

could use development configuration.

Another execution:

Environment = PROD

could use production configuration.

However, in a mature deployment process, environment-specific settings are often better handled through deployment configuration and connection/environment mappings rather than manually changing pipeline parameters.

7.3.15 Configuration-Driven Pipelines

Parameters become even more powerful when combined with a configuration table.

Suppose:

PipelineConfig
------------------------------------------------

SourceTable TargetTable LoadType

Customers Bronze_Customers Full

Products Bronze_Products Full

Orders Bronze_Orders Incremental

Architecture:

             PipelineConfig
                   │
                   ▼
                Lookup
                   │
                   ▼
                ForEach
                   │
                   ▼
               Parameters
                   │
                   ▼

Copy Data

This is a common enterprise pattern.

7.3.16 Parameters + Lookup + ForEach

Suppose Lookup returns:

[

{

"SourceTable": "Customers",

"TargetTable": "Bronze_Customers",

"LoadType": "Full"

},

{

"SourceTable": "Products",

"TargetTable": "Bronze_Products",

"LoadType": "Full"

},

{

"SourceTable": "Orders",

"TargetTable": "Bronze_Orders",

"LoadType": "Incremental"

}

]

ForEach processes each configuration row.

Conceptually:

Lookup
  │
  ▼
ForEach
  │
  ├── Customers
  │      │
  │      ▼
  │   Copy Data
  │
  ├── Products
  │      │
  │      ▼
  │   Copy Data
  │
  └── Orders
         │
         ▼

Incremental

The pipeline can use expressions such as:

@item().SourceTable

and:

@item().TargetTable

where supported.

7.3.17 Pipeline Parameter Expression

The common expression for accessing a pipeline parameter is:

@pipeline().parameters.ParameterName

Examples:

@pipeline().parameters.SourceTable

@pipeline().parameters.TargetTable

@pipeline().parameters.LoadType

7.3.18 Parameter in File Path

Suppose you have:

FolderPath

as a parameter.

Value:

sales/2026/08

The destination path can be constructed dynamically using the parameter.

Conceptually:

Lakehouse/

sales/

2026/

08/

This is useful for reusable ingestion pipelines.

7.3.19 Parameter in File Name

Parameter:

FileName

Value:

orders.csv

Pipeline:

Blob
 │
 ▼

@pipeline().parameters.FileName

 │
 ▼

Lakehouse

Changing the parameter to:

customers.csv

allows the same pipeline to process another file.

7.3.20 Parameter in SQL Query

Suppose the pipeline parameter is:

CustomerID = C1001

A dynamic query can conceptually use that value:

SELECT *
FROM Customers
WHERE CustomerID = '<parameter value>';

In Fabric pipelines, the exact way you inject the parameter depends on the activity and connector. Prefer parameterized/query-expression mechanisms supported by the specific activity rather than unsafe string concatenation.

7.3.21 Parameters and Security

Parameters are not automatically a security boundary.

Do not assume that passing:

Password

APIKey

ConnectionString

as normal pipeline parameters is the best way to manage secrets.

Use appropriate secure connection mechanisms, credentials, and secret-management facilities supported by Fabric and your organization's security architecture.

Good parameters:

SourceTable

TargetTable

LoadType

FolderPath

Environment

Avoid using normal parameters as a secret store.

7.3.22 Parameters + Notebook

A pipeline can pass parameters to a notebook.

Architecture:

Pipeline
   │
   │ Parameters
   ▼
Notebook
   │
   ▼

PySpark

For example:

TableName = Bronze_Orders

The notebook can use that runtime value to determine which dataset to process, subject to the notebook activity's supported parameterization mechanism.

7.3.23 Parameters + Dataflow Gen2

Similarly, parameters can be used to make Dataflow Gen2 processing reusable.

Example:

SourceSystem

Environment

FolderPath

TableName

Pipeline:

Parameters
    │
    ▼
Dataflow Gen2
    │
    ▼

Lakehouse

7.3.24 Parameters + Switch

This is a very common pattern.

Parameter:

LoadType

Value:

Incremental

Then:

Parameter
   │
   ▼
 Switch
   │
   ├── Full
   │
   ├── Incremental ◄── selected
   │
   └── CDC

7.3.25 Parameters + If Condition

Suppose:

IsIncremental = true

Then:

IsIncremental
      │
   ┌──┴───┐
 True    False
   │       │
   ▼       ▼

Incremental Full

This is appropriate when there are only two branches.

7.3.26 Parameters + ForEach

Suppose a parameter contains a list:

Tables =

[

"Customers",

"Products",

"Orders"

]

The pipeline can use a ForEach pattern to process each item.

Tables
   │
   ▼
ForEach
   │
   ├── Customers
   ├── Products
   └── Orders

For complex enterprise metadata, however, a configuration table + Lookup is often easier to manage than maintaining large arrays manually.

7.3.27 Parameters and Triggers

A pipeline can be executed by different mechanisms, including manually or through triggers.

The execution can supply parameter values.

Example:

Pipeline
    │
    ├── DEV
    ├── TEST
    └── PROD

This allows one pipeline definition to support different runtime configurations.

7.3.28 Example: Daily Sales Pipeline

Let's design a practical pipeline.

Parameters

SourceTable

TargetTable

LoadType

Environment

Values:

SourceTable = Orders

TargetTable = Bronze_Orders

LoadType = Incremental

Environment = PROD

Pipeline:

                PARAMETERS
                    │
                    ▼
              Lookup Config
                    │
                    ▼
                 Switch
                    │
              Incremental
                    │
                    ▼
                Copy Data
                    │
                    ▼
             Bronze Lakehouse
                    │
                    ▼
                Notebook
                    │
                    ▼
             Silver Lakehouse
                    │
                    ▼
             Fabric Warehouse
                    │
                    ▼

Audit

7.3.29 Parameters vs Hardcoding

Bad design

Copy Customers

Copy Products

Copy Orders

Copy Inventory

Each pipeline contains hardcoded values.

Better design

Generic Copy Pipeline

Parameters:

SourceTable

TargetTable

LoadType

Then:

Customers → Parameters → Generic Pipeline

Products → Parameters → Generic Pipeline

Orders → Parameters → Generic Pipeline

This reduces duplicate pipeline logic.

7.3.30 Parameters vs Variables vs Configuration Table

This is an important architecture concept.

MechanismPurposeLifetime
ParameterRuntime input/configurationOne pipeline run
VariableMutable runtime stateOne pipeline run
Configuration tablePersistent metadata/configurationAcross runs

Example:

Configuration Table
        │
        ▼
     Pipeline
        │
        ├── Parameters
        │      ↓
        │   Configuration
        │
        └── Variables

Runtime State

For example:

Config Table:

SourceTable = Orders

Parameter:

TargetTable = Bronze_Orders

Variable:

BatchID = 20260822_001

7.3.31 Real-World Metadata-Driven Example

Configuration table:

+-------------+------------------+-------------+

| SourceTable | TargetTable | LoadType |

+-------------+------------------+-------------+

| Customers | Bronze_Customers | Full |

| Products | Bronze_Products | Full |

| Orders | Bronze_Orders | Incremental |

+-------------+------------------+-------------+

Pipeline:

                Lookup
                  │
                  ▼
               ForEach
                  │
                  ▼
         ┌─────────────────┐
         │ Parameter Values│
         └────────┬────────┘
                  │
                  ▼
               Switch
                  │
       ┌──────────┴──────────┐
       ▼                     ▼
      Full              Incremental
       │                     │
       └──────────┬──────────┘
                  ▼

Copy Data

This pattern can support dozens or hundreds of source tables without creating a separate pipeline for every table.

7.3.32 Best Practices

1. Parameterize reusable values

Good candidates:

SourceTable

TargetTable

LoadType

FolderPath

Environment

2. Don't over-parameterize

Don't turn every small constant into a parameter.

3. Use consistent naming

For example:

p_SourceTable

p_TargetTable

p_LoadType

p_Environment

or your organization's preferred naming convention.

4. Don't use parameters as secret storage

Use secure credential mechanisms.

5. Use configuration tables for large metadata

Instead of manually supplying hundreds of parameters.

6. Combine parameters with control flow

Especially:

Parameter

Switch / If Condition

7. Make pipelines environment-aware

Where appropriate:

DEV

TEST

PROD

7.3.33 Common Mistakes

Mistake 1 — Hardcoding table names

Customers

Products

Orders

throughout the pipeline.

Better:

SourceTable

TargetTable

Mistake 2 — Using variables instead of parameters for inputs

If the value is supposed to be supplied to the pipeline, it is generally a parameter.

Mistake 3 — Storing secrets in parameters

Use secure connection/secret mechanisms instead.

Mistake 4 — Too many parameters

A pipeline with 30–50 manually supplied parameters can become difficult to maintain.

Consider a metadata/configuration table.

Mistake 5 — Confusing pipeline parameters with notebook parameters

They are different execution contexts. The pipeline needs to explicitly pass values to downstream activities.

7.3.34 Hands-On Exercise

Create a pipeline:

PL_Generic_Ingestion

Create these parameters:

p_SourceTable

p_TargetTable

p_LoadType

p_Environment

Example values:

p_SourceTable = Orders

p_TargetTable = Bronze_Orders

p_LoadType = Incremental

p_Environment = DEV

Build:

Start
  │
  ▼
If/Switch on p_LoadType
  │
  ├── Full
  │
  └── Incremental
        │
        ▼
     Copy Data
        │
        ▼

Bronze_Orders

Then execute the same pipeline with:

p_SourceTable = Customers

p_TargetTable = Bronze_Customers

p_LoadType = Full

You should not need to modify the pipeline itself.

7.3.35 Interview Questions

1. What is a pipeline parameter?

An input value supplied to a pipeline at runtime.

2. Why use parameters?

To make pipelines reusable and configurable.

3. Parameter vs variable?

Parameter → input/configuration

Variable → mutable runtime state

4. How do you reference a parameter?

Common syntax:

@pipeline().parameters.ParameterName

5. Can parameters be changed during pipeline execution?

They should be treated as input values; use variables for mutable runtime state.

6. How do parameters support metadata-driven pipelines?

They allow generic activities to receive source, target, load type, and other runtime configuration values.

7. Should passwords be passed as normal parameters?

No. Use appropriate secure credential and secret-management mechanisms.

8. How do you implement Full vs Incremental loading?

Use a parameter such as:

LoadType

and route it through a Switch or If Condition.

9. When should you use a configuration table instead?

When the pipeline needs a large or frequently changing set of metadata.

10. Give an example of a reusable pipeline.

PL_Generic_Copy

Parameters:

SourceTable

TargetTable

LoadType

The same pipeline can load:

Customers

Products

Orders

Inventory

7.3.36 Parameter Cheat Sheet

                 PARAMETER
                     │
                     ▼
          Runtime Pipeline Input
                     │
       ┌─────────────┼─────────────┐
       ▼             ▼             ▼
 SourceTable     TargetTable     LoadType
       │             │             │
       └─────────────┼─────────────┘
                     ▼
                 Pipeline
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
        Copy       Switch     Notebook
          │          │          │
          └──────────┼──────────┘
                     ▼
                 Lakehouse
                     │
                     ▼

Warehouse

Key Takeaway

Pipeline parameters make Fabric Data Factory pipelines reusable by allowing runtime inputs such as source table, target table, load type, folder path, and environment to be supplied without changing the pipeline's underlying design.

The most important pattern to remember is:

Configuration

Lookup

Parameters / Dynamic Content

ForEach / Switch

Generic Activities

Reusable Pipeline

↑ Back to top
Module 7 · Lesson 7.6

Module 7 · Lesson 7.4

ForEach

7.4 ForEach

7.4.1 What is ForEach?

ForEach is a control-flow activity in Microsoft Fabric Data Factory that allows a pipeline to execute the same set of activities for every item in a collection.

In simple terms:

ForEach = Repeat these activities for each item in a list.

For example, if you have:

Customers

Products

Orders

Inventory

you can use one ForEach instead of creating four separate copies of the same pipeline logic.

                 ForEach
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
    Customers    Products      Orders
        │           │           │
        ▼           ▼           ▼

Copy Copy Copy

7.4.2 Why Use ForEach?

Without ForEach:

Pipeline
│
├── Copy Customers
├── Copy Products
├── Copy Orders
├── Copy Inventory
├── Copy Suppliers
└── Copy Employees

This becomes difficult to maintain.

With ForEach:

Pipeline
   │
   ▼
ForEach
   │
   └── Copy Data

The same Copy activity is executed for every item.

Benefits

Reduces duplicate pipeline logic

Supports metadata-driven ingestion

Makes pipelines easier to maintain

Can process multiple tables/files

Supports sequential or concurrent processing

Works well with Lookup and dynamic content

7.4.3 Basic ForEach Structure

Suppose the input array is:

[

"Customers",

"Products",

"Orders"

]

The pipeline executes:

ForEach
   │
   ├── Item 1 → Customers → Copy
   │
   ├── Item 2 → Products  → Copy
   │
   └── Item 3 → Orders    → Copy

The activities inside the ForEach container execute once for each item.

7.4.4 ForEach Components

A ForEach generally has:

ForEach
│
├── Items
│
└── Activities

Items

The collection to iterate over.

Example:

[

"Customers",

"Products",

"Orders"

]

Activities

The actions performed for each item.

Example:

Copy Data

So:

Items

ForEach

Copy Data

7.4.5 Simple Example

Suppose you have:

Tables =

[

"Customers",

"Products",

"Orders"

]

ForEach:

For each Table

Copy Table

Execution:

Customers

Copy Customers

Products

Copy Products

Orders

Copy Orders

7.4.6 Using item()

Inside ForEach, the current item can be referenced using:

@item()

For example:

@item()

could return:

Customers

during the first iteration.

Then:

@item()

could return:

Products

during the second iteration.

And:

@item()

could return:

Orders

during the third iteration.

7.4.7 ForEach with Objects

For real-world pipelines, the items are often objects, rather than simple strings.

Example:

[

{

"SourceTable": "Customers",

"TargetTable": "Bronze_Customers",

"LoadType": "Full"

},

{

"SourceTable": "Products",

"TargetTable": "Bronze_Products",

"LoadType": "Full"

},

{

"SourceTable": "Orders",

"TargetTable": "Bronze_Orders",

"LoadType": "Incremental"

}

]

Now you can access properties such as:

@item().SourceTable

@item().TargetTable

@item().LoadType

This is extremely useful for metadata-driven pipelines.

7.4.8 ForEach + Lookup

This is one of the most important Fabric Data Factory patterns.

Suppose we have a configuration table:

PipelineConfig
---------------------------------------------------

SourceTable TargetTable LoadType IsActive

Customers Bronze_Customers Full 1

Products Bronze_Products Full 1

Orders Bronze_Orders Incremental 1

Pipeline:

              Configuration Table
                       │
                       ▼
                    Lookup
                       │
                       ▼
                    ForEach
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
      Customers     Products      Orders
          │            │            │
          ▼            ▼            ▼

Copy Copy Copy

This is called a metadata-driven pipeline.

7.4.9 Step-by-Step Example

Step 1 — Configuration

Create:

PipelineConfig

with:

SourceTableTargetTableLoadTypeIsActive
CustomersBronze_CustomersFull1
ProductsBronze_ProductsFull1
OrdersBronze_OrdersIncremental1

Step 2 — Lookup

Lookup retrieves:

SourceTable

TargetTable

LoadType

for active records.

Conceptually:

SELECT
    SourceTable,
    TargetTable,
    LoadType
FROM PipelineConfig
WHERE IsActive = 1;

Step 3 — ForEach

Set the ForEach items to the Lookup result.

Conceptually:

Lookup output

ForEach

Step 4 — Copy Data

Inside ForEach, configure Copy Data dynamically.

Source:

@item().SourceTable

Target:

@item().TargetTable

Now the same Copy activity can process all configured tables.

7.4.10 Execution

The pipeline effectively performs:

Iteration 1

Customers

Bronze_Customers

Iteration 2

Products

Bronze_Products

Iteration 3

Orders

Bronze_Orders

No separate Copy activity is needed for every table.

7.4.11 ForEach + Switch

You can combine ForEach with Switch.

Example:

                    Lookup
                       │
                       ▼
                    ForEach
                       │
                       ▼
                    Switch
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
        Full       Incremental      CDC
          │            │            │
          ▼            ▼            ▼

Full Copy Delta Copy CDC Logic

For example:

Customers → Full

Products → Full

Orders → Incremental

The same pipeline automatically chooses the appropriate processing method.

7.4.12 Sequential Processing

ForEach can process items sequentially when required.

Example:

Customer
   │
   ▼
Product
   │
   ▼

Order

This means:

Customer completes

Product starts

Product completes

Order starts

Use sequential processing when:

Tables have dependencies

Source system has limited capacity

Destination has limited capacity

Order of processing matters

7.4.13 Parallel Processing

Independent items can potentially be processed concurrently.

Example:

             ForEach
                │
       ┌────────┼────────┐
       ▼        ▼        ▼
   Customers Products  Orders
       │        │        │
       ▼        ▼        ▼

Copy Copy Copy

Instead of:

Customer → Product → Order

they can run at the same time.

This can significantly reduce execution time.

7.4.14 Sequential vs Parallel

SequentialParallel
One item at a timeMultiple items
SlowerFaster
Easier to controlBetter throughput
Useful for dependenciesUseful for independent tables
Lower source loadHigher source load

Example

If each table takes 5 minutes:

Sequential:

5 + 5 + 5 = 15 minutes

Parallel could approach:

~5 minutes

subject to concurrency limits, source/destination performance, and scheduling overhead.

7.4.15 When NOT to Use Parallel ForEach

Be careful if:

Source database is sensitive to load

SQL Server

10 simultaneous queries

could create unnecessary pressure.

API has rate limits

REST API

100 concurrent requests

may result in throttling.

Tables have dependencies

DimCustomer

FactSales

FactSales shouldn't necessarily load before the required customer dimension data.

Destination capacity is limited

Too much parallelism can create resource contention.

7.4.16 ForEach with Files

ForEach is not limited to database tables.

Suppose a folder contains:

sales_jan.csv

sales_feb.csv

sales_mar.csv

sales_apr.csv

Pipeline:

Get File List
      │
      ▼
    ForEach
      │
      ├── sales_jan.csv → Copy
      ├── sales_feb.csv → Copy
      ├── sales_mar.csv → Copy
      └── sales_apr.csv → Copy

This is useful for file ingestion.

7.4.17 ForEach with REST APIs

Suppose you need to call an API for multiple customers:

Customer IDs

[

C001,

C002,

C003,

C004

]

Pipeline:

ForEach
   │
   ├── C001 → REST API
   ├── C002 → REST API
   ├── C003 → REST API
   └── C004 → REST API

Be careful with API rate limits and concurrency.

7.4.18 ForEach + Parameters

Suppose the pipeline has:

p_SourceSystem

p_TargetLakehouse

Inside ForEach, each item provides:

SourceTable

TargetTable

So the effective configuration becomes:

Pipeline Parameters
        │
        ▼
ForEach Item
        │
        ▼

Dynamic Copy

Example:

@pipeline().parameters.p_SourceSystem

and:

@item().SourceTable

7.4.19 ForEach + Variables

You can use variables around ForEach, but shared mutable variables require care.

Example:

Variable:

ProcessedCount = 0

Then process:

Customers

Products

Orders

Conceptually:

ProcessedCount
     │
     ├── Customers
     ├── Products
     └── Orders

However, if iterations run concurrently, don't rely on shared variable updates as if they were transactional counters.

For parallel processing, prefer activity outputs, audit records, or other concurrency-safe patterns.

7.4.20 ForEach + Error Handling

A production ForEach should consider what happens when one item fails.

Example:

ForEach
   │
   ├── Customers → SUCCESS
   │
   ├── Products  → SUCCESS
   │
   └── Orders    → FAILURE
                     │
                     ▼

Logging

You may want to capture:

TableName

Status

ErrorMessage

RunID

For example:

Orders → FAILED

Reason → Source timeout

7.4.21 Dependency Between Items

ForEach itself is best suited to processing a collection of similar independent items.

If you have:

Customer

Product

Order

and strict dependencies, a normal sequential pipeline may be more appropriate:

Copy Customers

Copy Products

Copy Orders

Alternatively, use multiple stages rather than trying to encode complex dependencies inside one ForEach.

7.4.22 ForEach for a Medallion Architecture

For example, process multiple Bronze tables:

Bronze
│
├── Customers
├── Products
├── Orders
└── Inventory
      │
      ▼
   ForEach
      │
      ▼
  Transformation
      │
      ▼

Silver

The same notebook or transformation pattern can potentially process each dataset dynamically.

7.4.23 ForEach in an End-to-End Fabric Pipeline

A realistic architecture:

                    SQL Server
                         │
                         ▼
                Lookup Configuration
                         │
                         ▼
                      ForEach
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
        Customers     Products      Orders
            │            │            │
            └────────────┼────────────┘
                         ▼
                     Copy Data
                         │
                         ▼
                  Bronze Lakehouse
                         │
                         ▼
                      Notebook
                         │
                         ▼
                  Silver Lakehouse
                         │
                         ▼

Fabric Warehouse

7.4.24 Real-World Example

Imagine you have 50 source tables.

Without ForEach:

50 tables

50 Copy activities

Maintenance becomes difficult.

With metadata:

Configuration Table
       │
       ▼
     Lookup
       │
       ▼
     ForEach
       │
       ▼

Generic Copy

Now:

50 tables

1 ForEach

1 reusable Copy pattern

This is much easier to scale.

7.4.25 Configuration Table Example

A production configuration table might contain:

+----+-------------+------------------+-------------+----------+

| ID | SourceTable | TargetTable | LoadType | IsActive |

+----+-------------+------------------+-------------+----------+

| 1 | Customers | Bronze_Customers | Full | 1 |

| 2 | Products | Bronze_Products | Full | 1 |

| 3 | Orders | Bronze_Orders | Incremental | 1 |

| 4 | Inventory | Bronze_Inventory | Incremental | 1 |

+----+-------------+------------------+-------------+----------+

Pipeline:

              PipelineConfig
                    │
                    ▼
                  Lookup
                    │
                    ▼
                  ForEach
                    │
                    ▼
                  Switch
                    │
           ┌────────┴────────┐
           ▼                 ▼
          Full          Incremental
           │                 │
           └────────┬────────┘
                    ▼

Copy Data

This is a strong metadata-driven ingestion architecture.

7.4.26 Example Dynamic Expressions

Inside ForEach, common expressions include:

Current item

@item()

Source table

@item().SourceTable

Target table

@item().TargetTable

Load type

@item().LoadType

Pipeline parameter

@pipeline().parameters.SourceSystem

The exact expression path depends on the structure returned by the upstream activity.

7.4.27 ForEach vs Switch

These two activities solve different problems.

ForEach

Answers:

"How many times should I execute this?"

Example:

Customers

Products

Orders

Switch

Answers:

"Which branch should I execute?"

Example:

Full

Incremental

CDC

Together:

ForEach
   │
   ▼
Switch
   │
   ├── Full
   ├── Incremental
   └── CDC

7.4.28 ForEach vs Until

ForEachUntil
Iterates over a collectionRepeats until a condition
Known set of itemsCondition-driven
Tables/files/recordsPolling/retry patterns
Customers, Products, OrdersUntil file available

Example:

ForEach:

Customers → Products → Orders

versus:

Until:

FileAvailable = TRUE

7.4.29 Best Practices

1. Use ForEach for repetitive work

Don't create duplicate activities unnecessarily.

2. Use metadata-driven configuration

Prefer:

Lookup → ForEach → Dynamic Activity

for large ingestion frameworks.

3. Control concurrency

Don't automatically run everything in parallel.

4. Handle failures

Record which item failed.

5. Use meaningful metadata

Include:

SourceTable

TargetTable

LoadType

IsActive

Priority

where appropriate.

6. Avoid unnecessary shared variables

Especially when running iterations concurrently.

7. Keep the inner workflow simple

For example:

ForEach

Copy

Validate

is easier to maintain than deeply nested control-flow logic.

7.4.30 Hands-On Exercise

Build:

PL_Metadata_Ingestion

Configuration

Create:

SourceTableTargetTableLoadType
CustomersBronze_CustomersFull
ProductsBronze_ProductsFull
OrdersBronze_OrdersIncremental

Pipeline

Start
  │
  ▼
Lookup Configuration
  │
  ▼
ForEach
  │
  ▼
Switch LoadType
  │
  ├── Full
  │    ↓
  │  Copy Data
  │
  └── Incremental

Copy Data

Inside ForEach

Use:

@item().SourceTable

for the source and:

@item().TargetTable

for the destination.

7.4.31 Interview Questions

1. What is ForEach?

A control-flow activity that executes activities for each item in a collection.

2. Why use ForEach?

To avoid duplicate pipeline activities and implement reusable repetitive processing.

3. What is item()?

It represents the current item being processed by the ForEach iteration.

4. How do you access a property of the current item?

For an object:

@item().SourceTable

5. Can ForEach run in parallel?

Yes, where supported/configured, but concurrency should be controlled based on workload requirements.

6. When should you use sequential processing?

When processing order matters or source/destination systems cannot handle high concurrency.

7. What is a metadata-driven ForEach?

A ForEach whose items come from configuration metadata, commonly:

Lookup → ForEach → Dynamic Copy

8. Can ForEach process files?

Yes. It can be used to process collections of files when the upstream activity provides the required collection.

9. Can ForEach process API requests?

Yes, subject to connector capabilities and API rate/concurrency limits.

10. What is a common ForEach pattern in Fabric?

Configuration

Lookup

ForEach

Copy Data

7.4.32 Final Cheat Sheet

                 ForEach
                    │
                    ▼
              Collection
                    │
        ┌───────────┼───────────┐
        ▼           ▼           ▼
      Item 1      Item 2      Item 3
        │           │           │
        ▼           ▼           ▼

Activity Activity Activity

Most important expressions

@item()

Current item.

@item().SourceTable

Current item's source table.

@item().TargetTable

Current item's target table.

@pipeline().parameters.ParameterName

Pipeline parameter.

Most important enterprise pattern

Configuration Table
│
▼
Lookup
│
▼
ForEach
│
▼
Dynamic Activities
│
▼

Lakehouse / Warehouse

ForEach is the key control-flow activity for building scalable, reusable Fabric pipelines that perform the same processing logic across multiple tables, files, API objects, or configuration records.

↑ Back to top
Module 7 · Lesson 7.8

Module 7 · Lesson 7.5

Switch

7.5 Switch

7.5.1 What is Switch?

Switch is a control-flow activity in Microsoft Fabric Data Factory that allows a pipeline to choose one execution path from multiple possible cases based on the value of an expression.

In simple terms:

Switch = If the value is X, do X; if it is Y, do Y; otherwise, do the default action.

For example:

LoadType
   │
   ▼
 Switch
   │
   ├── Full        → Full Load
   ├── Incremental → Incremental Load
   ├── CDC         → CDC Load
   └── Default     → Error

7.5.2 Why Use Switch?

Suppose a pipeline supports three types of data loading:

Full

Incremental

CDC

Without Switch, you might create complicated nested conditions.

With Switch:

                 LoadType
                    │
                    ▼
                 Switch
          ┌─────────┼─────────┐
          ▼         ▼         ▼
        Full   Incremental    CDC
          │         │         │
          ▼         ▼         ▼

Full Load Delta Load CDC

This makes the pipeline easier to understand and maintain.

7.5.3 Switch vs If Condition

This is a common interview question.

If Condition

Used when there are essentially two outcomes:

IsIncremental?

     │
  ┌──┴──┐
 Yes    No
  │      │
  ▼      ▼

Incremental Full

Switch

Used when there are multiple possible values:

LoadType
   │
   ├── Full
   ├── Incremental
   ├── CDC
   └── Snapshot

Simple rule

2 paths → If Condition

3+ cases → Switch

7.5.4 Switch Structure

A Switch typically contains:

Switch
│
├── Expression
│
├── Case 1
│
├── Case 2
│
├── Case 3
│
└── Default

Example:

Expression:

LoadType

Cases:

Full

Incremental

CDC

Default:

Unsupported Load Type

7.5.5 Example: Load Type

Suppose the pipeline receives:

LoadType = Incremental

Switch evaluates:

LoadType
   │
   ▼

"Incremental"

Then selects:

Incremental case

Execution:

Switch
   │
   ├── Full             ✗
   │
   ├── Incremental     ✓
   │       │
   │       ▼
   │   Incremental Load
   │
   └── CDC              ✗

Only the matching case is selected.

7.5.6 Basic Example

Create a pipeline parameter:

p_LoadType

Example value:

Incremental

Switch expression:

@pipeline().parameters.p_LoadType

Cases:

Full

Incremental

CDC

Architecture:

                p_LoadType
                    │
                    ▼
                 Switch
                    │
       ┌────────────┼────────────┐
       ▼            ▼            ▼
      Full     Incremental       CDC
       │            │            │
       ▼            ▼            ▼

Full Copy Delta Copy CDC Logic

7.5.7 Case Matching

Suppose:

Expression = "Full"

Cases:

Case: Full

Case: Incremental

Case: CDC

Result:

Full → Selected

If:

Expression = "CDC"

then:

CDC → Selected

The case values must match the expected values according to the activity's expression/matching behavior.

7.5.8 Default Case

A Default path is useful when the input doesn't match any expected case.

Example:

LoadType = "Unknown"

Switch:

Switch
│
├── Full
├── Incremental
├── CDC
└── Default → Error

Execution:

Unknown
   │
   ▼
Default
   │
   ▼

Log Invalid Load Type

This is much safer than silently doing nothing.

7.5.9 Switch with Parameters

A very common pattern is:

Pipeline Parameter
        │
        ▼

Switch

Example parameters:

p_LoadType

p_SourceSystem

p_LoadType:

Full

Incremental

CDC

Switch expression:

@pipeline().parameters.p_LoadType

7.5.10 Switch with ForEach

This is one of the most important enterprise patterns.

Suppose configuration contains:

SourceTableTargetTableLoadType
CustomersBronze_CustomersFull
ProductsBronze_ProductsFull
OrdersBronze_OrdersIncremental
InventoryBronze_InventoryCDC

Pipeline:

             Lookup
                │
                ▼
             ForEach
                │
                ▼
             Switch
                │
      ┌─────────┼─────────┐
      ▼         ▼         ▼
    Full   Incremental    CDC
      │         │         │
      ▼         ▼         ▼

Copy Delta CDC

For each item:

Customers → Full

Products → Full

Orders → Incremental

Inventory → CDC

This is a metadata-driven ingestion framework.

7.5.11 Step-by-Step Example

Step 1 — Configuration Table

Create:

CREATE TABLE dbo.PipelineConfig

(

SourceTable VARCHAR(200),

TargetTable VARCHAR(200),

LoadType VARCHAR(50),

IsActive BIT

);

Example:

Customers | Bronze_Customers | Full | 1

Products | Bronze_Products | Full | 1

Orders | Bronze_Orders | Incremental | 1

Inventory | Bronze_Inventory | CDC | 1

Step 2 — Lookup

Lookup active records:

SELECT
    SourceTable,
    TargetTable,
    LoadType
FROM dbo.PipelineConfig
WHERE IsActive = 1;

Step 3 — ForEach

ForEach processes each configuration record.

Current item:

@item()

Load type:

@item().LoadType

Step 4 — Switch

Switch expression:

@item().LoadType

Cases:

Full

Incremental

CDC

7.5.12 Full Case

For:

LoadType = Full

execute:

Source
  │
  ▼
Copy Data
  │
  ▼

Bronze

Example:

Customers

Bronze_Customers

7.5.13 Incremental Case

For:

LoadType = Incremental

execute:

Get Watermark
      │
      ▼
Read Changed Records
      │
      ▼
Copy Data
      │
      ▼

Update Watermark

Example:

Orders
  │
  ▼

ModifiedDate > LastWatermark

  │
  ▼

Bronze_Orders

7.5.14 CDC Case

For:

LoadType = CDC

execute the appropriate change-data-capture processing.

Conceptually:

Source CDC
    │
    ▼

Identify Inserts

Updates

Deletes
    │
    ▼
Apply Changes
    │
    ▼

Target

The exact CDC implementation depends on the source system and Fabric capabilities.

7.5.15 Default Case

If:

LoadType = Unknown

then:

Default
   │
   ▼
Log Error
   │
   ▼

Fail / Alert

Example error:

Unsupported LoadType: Unknown

7.5.16 Switch Based on Source System

Switch doesn't have to be based on LoadType.

You could use:

SourceSystem

Cases:

SQLServer

Oracle

REST

Blob

Architecture:

                  SourceSystem
                       │
                       ▼
                    Switch
                       │
       ┌───────────────┼───────────────┐
       ▼               ▼               ▼
   SQLServer         Oracle           REST
       │               │               │
       ▼               ▼               ▼

SQL Copy Oracle Copy API Call

This is useful in multi-source ingestion pipelines.

7.5.17 Switch Based on File Type

You could also route files based on type:

FileType
   │
   ▼
Switch
   │
   ├── CSV → CSV Processing
   ├── JSON → JSON Processing
   ├── Parquet → Parquet Processing
   └── XML → XML Processing

Example:

FileName = sales.json

FileType = JSON

Then:

JSON Processing

7.5.18 Switch Based on Environment

Another example:

Environment
    │
    ▼
  Switch
    │
    ├── DEV
    ├── TEST
    └── PROD

Possible use:

DEV

Development configuration

TEST

Test configuration

PROD

Production configuration

For environment deployment, however, use deployment/configuration mechanisms where appropriate rather than embedding environment logic everywhere.

7.5.19 Switch Based on Business Process

Example:

ProcessType
    │
    ▼
  Switch
    │
    ├── Customer
    ├── Sales
    ├── Finance
    └── Inventory

Each case can execute a different processing workflow.

7.5.20 Switch with Multiple Activities

A case can contain multiple activities.

For example:

Incremental Case
      │
      ▼
Lookup Watermark
      │
      ▼
Copy Changed Records
      │
      ▼
Transform
      │
      ▼

Update Watermark

So Switch doesn't merely select one activity. It can select an entire workflow branch.

7.5.21 Complete Example

                         START
                           │
                           ▼
                    Lookup Config
                           │
                           ▼
                        ForEach
                           │
                           ▼
                     Switch LoadType
                           │
           ┌───────────────┼────────────────┐
           ▼               ▼                ▼
         Full        Incremental            CDC
           │               │                │
           ▼               ▼                ▼
      Copy Data       Get Watermark     Read Changes
           │               │                │
           ▼               ▼                ▼
        Bronze         Copy Changes       Apply CDC
           │               │                │
           │               ▼                ▼
           │         Update Watermark      Bronze
           │
           └───────────────┬────────────────┘
                           ▼

Audit / Logging

7.5.22 Switch vs ForEach vs If Condition

ActivityPurposeExample
ForEachRepeat for each itemCustomers, Products, Orders
If ConditionTwo-way decisionValid / Invalid
SwitchMultiple-value decisionFull / Incremental / CDC

Think of them as:

ForEach

"What should I repeat?"

If Condition

"Is this true or false?"

Switch

"Which case applies?"

7.5.23 Switch vs Nested If Conditions

Complicated approach

IF Full?

   │
   └── No
       │
       ▼

IF Incremental?

       │
       └── No
           │
           ▼

IF CDC?

This becomes difficult to read.

Better approach

Switch
│
├── Full
├── Incremental
└── CDC

For multiple mutually exclusive cases, Switch is generally much cleaner.

7.5.24 Error Handling

A production Switch should have a default/error path.

Switch
│
├── Full
├── Incremental
├── CDC
└── Default
      │
      ▼
  Log Error
      │
      ▼

Alert

Example:

Invalid LoadType = "Delta"

Default:

Unsupported LoadType: Delta

This prevents invalid configuration from being silently ignored.

7.5.25 Dynamic Switch Expression

A typical expression could be:

@pipeline().parameters.p_LoadType

Or, inside a ForEach:

@item().LoadType

For example:

@item().LoadType

returns:

Incremental

The Switch selects:

Incremental

7.5.26 Practical Fabric Example

Imagine you have:

PipelineConfig

with:

SourceTable     LoadType
--------------------------

Customers Full

Products Full

Orders Incremental

Payments CDC

Pipeline:

Lookup
  │
  ▼
ForEach
  │
  ▼
Switch
  │
  ├── Full
  │    └── Copy Data
  │
  ├── Incremental
  │    ├── Lookup Watermark
  │    ├── Copy Changes
  │    └── Update Watermark
  │
  └── CDC
       ├── Read Changes
       └── Apply Changes

This can support many tables with different ingestion strategies.

7.5.27 Best Practices

1. Use meaningful case values

Good:

Full

Incremental

CDC

Avoid cryptic values such as:

1

2

3

unless there is a strong reason.

2. Always consider a Default case

Handle unexpected values:

Unknown

NULL

Unsupported

appropriately.

3. Keep case workflows organized

Don't put excessive nested control flow inside each case.

4. Combine Switch with metadata

A powerful pattern is:

Configuration

Lookup

ForEach

Switch

5. Use Switch for mutually exclusive branches

If several conditions can independently be true, consider whether multiple If Conditions are more appropriate.

6. Validate configuration

Before executing:

LoadType

ensure it is one of:

Full

Incremental

CDC

7.5.28 Hands-On Exercise

Create a pipeline:

PL_Dynamic_Load

Step 1 — Create Parameter

Name:

p_LoadType

Type:

String

Possible values:

Full

Incremental

CDC

Step 2 — Add Switch

Expression:

@pipeline().parameters.p_LoadType

Step 3 — Create Cases

Case: Full

Case: Incremental

Case: CDC

Step 4 — Configure Full

Full

Copy Data

Bronze

Step 5 — Configure Incremental

Incremental

Lookup Watermark

Copy Changed Records

Update Watermark

Step 6 — Configure CDC

CDC

Read Changes

Apply Inserts/Updates/Deletes

Step 7 — Default

Default

Log Invalid Load Type

Fail Pipeline

7.5.29 Interview Questions

1. What is Switch in Fabric Data Factory?

A control-flow activity that selects an execution branch based on an expression's value.

2. When would you use Switch instead of If Condition?

When there are multiple possible cases rather than a simple true/false decision.

3. Give an example.

LoadType
├── Full
├── Incremental
└── CDC

4. Can Switch be used inside ForEach?

Yes. This is a common metadata-driven pipeline pattern.

5. What is a Default case?

The branch used when the expression doesn't match any configured case.

6. How can you dynamically determine the Switch value?

Using pipeline parameters, variables, activity outputs, or the current ForEach item.

Example:

@item().LoadType

7. Why is Switch useful in ETL?

It allows one pipeline to support multiple processing strategies.

8. Give an enterprise example.

Lookup Configuration

ForEach

Switch LoadType

Full / Incremental / CDC

7.5.30 Final Cheat Sheet

                  SWITCH
                     │
             Evaluate Expression
                     │
        ┌────────────┼────────────┐
        ▼            ▼            ▼
       CASE 1       CASE 2       CASE 3
        │            │            │
        ▼            ▼            ▼
      Workflow     Workflow     Workflow
        │            │            │
        └────────────┼────────────┘
                     │
                  DEFAULT
                     │
                     ▼

ERROR

Most important pattern

       CONFIGURATION
             │
             ▼
           LOOKUP
             │
             ▼
          FOREACH
             │
             ▼
           SWITCH
             │
     ┌───────┼────────┐
     ▼       ▼        ▼
   FULL  INCREMENTAL  CDC
     │       │        │
     ▼       ▼        ▼
   COPY    WATERMARK  CHANGES
     │       │        │
     └───────┼────────┘
             ▼

BRONZE

ForEach answers "which items should I process?", while Switch answers "which processing logic should I use for this item?"

This combination is especially important for building metadata-driven Microsoft Fabric ingestion pipelines.

↑ Back to top
Module 7 · Lesson 7.10

Module 7 · Lesson 7.6

Lookup

7.6 Lookup

7.6.1 What is Lookup?

Lookup is a control-flow activity in Microsoft Fabric Data Factory used to retrieve data or configuration information during pipeline execution.

In simple terms:

Lookup = Read something first, then use the result to decide what the pipeline should do.

A common example is reading a configuration table:

Configuration Table
        │
        ▼
      Lookup
        │
        ▼

Pipeline Logic

For example:

PipelineConfig
------------------------------------------------

SourceTable TargetTable LoadType

Customers Bronze_Customers Full

Products Bronze_Products Full

Orders Bronze_Orders Incremental

The Lookup reads this configuration, and the pipeline uses it to determine what to process.

7.6.2 Why Use Lookup?

Without Lookup, you may hardcode everything:

Copy Customers

Copy Products

Copy Orders

Copy Inventory

With Lookup:

Configuration

Lookup

ForEach

Copy Data

This makes the pipeline:

Reusable

Metadata-driven

Easier to maintain

Easier to scale

Less dependent on hardcoded values

7.6.3 Typical Lookup Architecture

                    Configuration
                         │
                         ▼
                      LOOKUP
                         │
                         ▼
                     ForEach
                         │
                         ▼
                    Copy Data
                         │
                         ▼

Bronze

For example:

SQL Server
   │
   ├── Customers
   ├── Products
   └── Orders
        │
        ▼
   Pipeline Config
        │
        ▼
      Lookup
        │
        ▼
     ForEach
        │
        ▼
    Copy Data
        │
        ▼

Lakehouse

7.6.4 Lookup Input

A Lookup needs a source from which it retrieves information.

Common scenarios include reading:

Configuration tables

Control tables

Watermarks

Reference data

File metadata

Processing status

For example:

SELECT *
FROM dbo.PipelineConfig
WHERE IsActive = 1;

The Lookup executes the configured source operation and produces an output.

7.6.5 Example Configuration Table

Suppose we create:

CREATE TABLE dbo.PipelineConfig

(

ConfigID INT,

SourceTable VARCHAR(200),

TargetTable VARCHAR(200),

LoadType VARCHAR(50),

IsActive BIT

);

Data:

ConfigIDSourceTableTargetTableLoadTypeIsActive
1CustomersBronze_CustomersFull1
2ProductsBronze_ProductsFull1
3OrdersBronze_OrdersIncremental1
4InventoryBronze_InventoryIncremental0

The Lookup can retrieve only active records:

SELECT
    ConfigID,
    SourceTable,
    TargetTable,
    LoadType
FROM dbo.PipelineConfig
WHERE IsActive = 1;

Result:

Customers

Products

Orders

Inventory is excluded.

7.6.6 Lookup Output

Conceptually, the Lookup may return something like:

[

{

"ConfigID": 1,

"SourceTable": "Customers",

"TargetTable": "Bronze_Customers",

"LoadType": "Full"

},

{

"ConfigID": 2,

"SourceTable": "Products",

"TargetTable": "Bronze_Products",

"LoadType": "Full"

},

{

"ConfigID": 3,

"SourceTable": "Orders",

"TargetTable": "Bronze_Orders",

"LoadType": "Incremental"

}

]

The output can then be consumed by downstream activities.

7.6.7 Lookup + ForEach

This is one of the most important Fabric pipeline patterns.

             Configuration Table
                     │
                     ▼
                   Lookup
                     │
                     ▼
                  ForEach
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
      Customers   Products     Orders
          │          │          │
          ▼          ▼          ▼
        Copy       Copy        Copy
          │          │          │
          ▼          ▼          ▼

Bronze Bronze Bronze

The Lookup determines what should be processed.

The ForEach determines how many times to execute the processing logic.

7.6.8 Lookup + ForEach + Switch

This is an even more powerful pattern.

                  Lookup
                    │
                    ▼
                  ForEach
                    │
                    ▼
                  Switch
                    │
          ┌─────────┼─────────┐
          ▼         ▼         ▼
        Full    Incremental   CDC
          │         │         │
          ▼         ▼         ▼

Copy Delta CDC

Example:

Customers → Full

Products → Full

Orders → Incremental

Payments → CDC

One pipeline can process all four according to configuration.

7.6.9 Single Row vs Multiple Rows

Lookup scenarios can broadly be thought of as:

Single-row lookup

Use when you expect one configuration/value.

Example:

LastWatermark

Result:

2026-08-21 23:59:59

Multiple-row lookup

Use when you want a collection.

Example:

Customers

Products

Orders

Inventory

Result:

[

Customers,

Products,

Orders,

Inventory

]

The exact configuration and output shape should be checked in the current Fabric UI.

7.6.10 Lookup for Watermark

Lookup is very useful for incremental loading.

Suppose we have a control table:

PipelineWatermark
-----------------------------------------------

TableName LastWatermark

Orders 2026-08-21 23:59:59

Customers 2026-08-21 22:30:00

Lookup:

SELECT LastWatermark
FROM dbo.PipelineWatermark
WHERE TableName = 'Orders';

Result:

2026-08-21 23:59:59

Then:

Lookup
   │
   ▼
LastWatermark
   │
   ▼

Copy changed records

7.6.11 Incremental Load Example

Source:

Orders

Columns:

OrderID

CustomerID

Amount

ModifiedDate

Lookup retrieves:

LastWatermark =

2026-08-21 23:59:59

Then the source query conceptually becomes:

SELECT *

FROM Orders

WHERE ModifiedDate > @LastWatermark;

Only changed records are processed.

Architecture:

Control Table
     │
     ▼
   Lookup
     │
     ▼
Watermark
     │
     ▼
SQL Server
     │
     ▼
Changed Records
     │
     ▼

Lakehouse

7.6.12 Lookup for File Metadata

Lookup can also be used to retrieve information that controls file processing.

Example configuration:

FileNameTargetTableIsActive
customers.csvBronze_Customers1
products.csvBronze_Products1
orders.csvBronze_Orders1

Pipeline:

File Configuration
       │
       ▼
     Lookup
       │
       ▼
    ForEach
       │
       ▼

Copy Files

7.6.13 Lookup for API Configuration

Suppose an API integration requires different endpoints:

APINameEndpointTargetTable
Customers/customersBronze_Customers
Products/productsBronze_Products
Orders/ordersBronze_Orders

Lookup retrieves the configuration.

API Config
    │
    ▼
  Lookup
    │
    ▼
  ForEach
    │
    ▼
 Web/API Activity
    │
    ▼

Lakehouse

This makes the API ingestion pipeline reusable.

7.6.14 Lookup Output in ForEach

Suppose Lookup returns:

[

{

"SourceTable": "Customers",

"TargetTable": "Bronze_Customers"

},

{

"SourceTable": "Products",

"TargetTable": "Bronze_Products"

}

]

Inside ForEach, you can access:

@item().SourceTable

and:

@item().TargetTable

Therefore:

Iteration 1:

Source = Customers

Target = Bronze_Customers

Iteration 2:

Source = Products

Target = Bronze_Products

7.6.15 Lookup Output and Dynamic Content

A common dynamic-content concept for accessing a Lookup activity's output is:

@activity('Lookup_Config').output

For example, if the activity is named:

Lookup_Config

then:

@activity('Lookup_Config').output

refers to its output object.

When using specific properties, the exact path depends on the output structure returned by the configured Lookup.

7.6.16 Lookup + Parameters

Lookup and pipeline parameters can work together.

Example parameter:

p_Environment

Value:

PROD

Lookup query:

SELECT *
FROM PipelineConfig
WHERE Environment = @Environment

AND IsActive = 1;

Conceptually:

Pipeline Parameter
        │
        ▼
      Lookup
        │
        ▼

Production Configuration

This allows the same pipeline to operate in different environments.

7.6.17 Lookup + Variables

A Lookup can provide a value that is then stored or used during execution.

Example:

Lookup
  │
  ▼
LastWatermark
  │
  ▼

Variable

For example:

v_LastWatermark =

2026-08-21 23:59:59

Then the variable can be referenced by downstream activities where appropriate.

7.6.18 Lookup for Validation

Lookup can also help validate whether data/configuration exists.

Example:

Lookup Customer
      │
      ▼

Record Found?

      │
  ┌───┴───┐
 Yes      No
  │        │
  ▼        ▼

Continue Error

For example:

SELECT CustomerID
FROM Customers
WHERE CustomerID = 'C1001';

If no record exists, the pipeline can take an alternate path.

7.6.19 Lookup for Control Tables

A typical enterprise Fabric pipeline may maintain:

PipelineConfig

PipelineWatermark

PipelineAudit

PipelineError

Lookup can read these tables.

Example:

             Control Tables
                  │
        ┌─────────┼─────────┐
        ▼         ▼         ▼
     Config   Watermark   Status
        │         │         │
        └─────────┼─────────┘
                  ▼

Pipeline

7.6.20 Lookup + Metadata-Driven Architecture

This is the architecture you should remember.

                 CONTROL TABLE
                       │
                       ▼
                    LOOKUP
                       │
                       ▼
                    FOREACH
                       │
                       ▼
                    SWITCH
                       │
              ┌────────┼────────┐
              ▼        ▼        ▼
            Full   Incremental   CDC
              │        │        │
              └────────┼────────┘
                       ▼
                   COPY DATA
                       │
                       ▼

BRONZE

This allows you to build a generic ingestion framework.

7.6.21 Example: 100 Tables

Suppose your organization has:

100 source tables

Poor approach

Create:

100 pipelines

or one pipeline containing:

100 hardcoded Copy activities

Better approach

Create:

PipelineConfig
       │
       ▼
     Lookup
       │
       ▼
    ForEach
       │
       ▼

Dynamic Copy

Now the configuration table controls what is processed.

Adding a new table may only require adding configuration:

Orders

Invoices

Payments

rather than redesigning the pipeline.

7.6.22 Example Configuration

+-------------+------------------+-------------+----------+

| SourceTable | TargetTable | LoadType | IsActive |

+-------------+------------------+-------------+----------+

| Customers | Bronze_Customers | Full | 1 |

| Products | Bronze_Products | Full | 1 |

| Orders | Bronze_Orders | Incremental | 1 |

| Payments | Bronze_Payments | Incremental | 1 |

| Employees | Bronze_Employees | Full | 0 |

+-------------+------------------+-------------+----------+

Lookup:

SELECT
    SourceTable,
    TargetTable,
    LoadType
FROM dbo.PipelineConfig
WHERE IsActive = 1;

Returns:

Customers

Products

Orders

Payments

Employees is skipped.

7.6.23 Lookup + Switch Example

For each returned row:

SourceTable = Orders

LoadType = Incremental

Switch receives:

Incremental

and selects:

Incremental processing

Therefore:

Lookup

ForEach Orders

Switch = Incremental

Incremental Copy

7.6.24 Lookup for Last Successful Run

Another useful control-table pattern:

PipelineAudit
------------------------------------------------

PipelineName LastSuccessfulRun

SalesPipeline 2026-08-22 01:00:00

Lookup:

SELECT LastSuccessfulRun
FROM dbo.PipelineAudit
WHERE PipelineName = 'SalesPipeline';

Then the pipeline can use this information for recovery or incremental processing, depending on the design.

7.6.25 Lookup for Data Quality Rules

You can also store validation rules:

TableNameColumnNameRuleIsActive
CustomersCustomerIDNOT_NULL1
OrdersOrderIDNOT_NULL1
OrdersAmountPOSITIVE1

Lookup:

Rules
  │
  ▼
Lookup
  │
  ▼
ForEach
  │
  ▼

Validation

This creates a metadata-driven data-quality framework.

7.6.26 Lookup vs Copy Data

These activities have different purposes.

Lookup

Read information

Example:

Read PipelineConfig

Copy Data

Move data

Example:

SQL Server → Lakehouse

Typical combination:

Lookup

Determine what to copy

Copy Data

7.6.27 Lookup vs ForEach

Lookup

Answers:

"What records/configuration should I retrieve?"

ForEach

Answers:

"What should I do for each retrieved item?"

Together:

Lookup

Customers

Products

Orders

ForEach

Process each item

7.6.28 Lookup vs Switch

Lookup

Retrieves information:

LoadType = Incremental

Switch

Uses that information to choose logic:

Incremental

Incremental Processing

Together:

Lookup

LoadType

Switch

Processing Branch

7.6.29 Error Handling

Lookup should be handled carefully.

Possible problems:

No record

Multiple unexpected records

Connection failure

Invalid query

Invalid configuration

Example:

Lookup
  │
  ├── Success → Continue
  │
  └── Failure → Log Error → Alert

For a configuration-driven pipeline, also validate that required metadata exists.

7.6.30 Best Practices

1. Use Lookup for metadata

Good examples:

PipelineConfig

Watermark

Control information

Reference configuration

2. Keep configuration outside the pipeline

Avoid hardcoding dozens of tables.

3. Use Lookup + ForEach

This is one of the strongest patterns for scalable ingestion.

4. Validate Lookup results

Don't assume configuration is always correct.

5. Use parameters for dynamic filtering

For example:

Environment

PipelineName

TableName

6. Keep configuration manageable

A good configuration table might contain:

Source

Target

LoadType

Schedule

IsActive

Priority

WatermarkColumn

depending on requirements.

7.6.31 Hands-On Exercise

Create:

PL_Metadata_Ingestion

Configuration Table

CREATE TABLE dbo.PipelineConfig

(

SourceTable VARCHAR(100),

TargetTable VARCHAR(100),

LoadType VARCHAR(50),

IsActive BIT

);

Insert:

INSERT INTO dbo.PipelineConfig

VALUES

('Customers', 'Bronze_Customers', 'Full', 1),

('Products', 'Bronze_Products', 'Full', 1),

('Orders', 'Bronze_Orders', 'Incremental', 1);

Pipeline

Create:

Lookup_Config

Query:

SELECT
    SourceTable,
    TargetTable,
    LoadType
FROM dbo.PipelineConfig
WHERE IsActive = 1;

Then:

Lookup_Config
      │
      ▼
    ForEach
      │
      ▼
    Switch
      │
      ├── Full
      │
      └── Incremental

Inside ForEach:

@item().SourceTable

and:

@item().TargetTable

7.6.32 Interview Questions

1. What is Lookup?

An activity that retrieves data/configuration for use by subsequent pipeline activities.

2. Why is Lookup important?

It enables metadata-driven and dynamic pipelines.

3. What is Lookup + ForEach?

Lookup configuration

ForEach each record

Process each item

4. What is Lookup + Switch?

Lookup LoadType

Switch

Full / Incremental / CDC

5. What is a common use of Lookup in incremental loading?

Retrieve the last processed watermark.

6. Can Lookup return multiple records?

Yes, it can be configured for scenarios where a collection of records is required, subject to the activity's current limits and behavior.

7. How do you access the current Lookup item inside ForEach?

Typically:

@item()

and for a property:

@item().SourceTable

8. What is a metadata-driven pipeline?

A pipeline whose behavior is controlled by configuration/metadata rather than hardcoded activities and values.

9. What is a common enterprise pattern?

Lookup → ForEach → Switch → Copy

10. Can Lookup retrieve a watermark?

Yes. A control table can store the last successful processing timestamp or other watermark.

7.6.33 Final Cheat Sheet

                    LOOKUP
                       │
                       ▼
              Read Configuration
                       │
             ┌─────────┴─────────┐
             ▼                   ▼
        Single Value        Multiple Rows
             │                   │
             ▼                   ▼
       Use Directly            ForEach
                                 │
                                 ▼
                              Switch
                                 │
                    ┌────────────┼────────────┐
                    ▼            ▼            ▼
                  Full      Incremental       CDC
                    │            │            │
                    ▼            ▼            ▼
                  Copy         Copy         Apply
                    │            │            │
                    └────────────┼────────────┘
                                 ▼

Lakehouse

Most important pattern

Lookup → ForEach → Switch → Copy Data

This is the foundation of a metadata-driven Fabric ingestion pipeline.

For example:

PipelineConfig

Lookup

ForEach 100 tables

Switch LoadType

Full / Incremental / CDC

Bronze Lakehouse

Once you understand this pattern, you can build highly reusable ingestion frameworks instead of maintaining separate pipelines for every table.

↑ Back to top
Module 7 · Lesson 7.12

Module 7 · Lesson 7.7

Stored Procedure

7.7 Stored Procedure

7.7.1 What is a Stored Procedure?

A stored procedure is a named collection of SQL statements stored in a database and executed when required.

In a Fabric pipeline, a stored-procedure activity is used to orchestrate reusable database-side SQL logic as part of a data workflow, where supported by the selected Fabric Data Factory connector/activity.

Think of it as:

Pipeline
   │
   ▼
Stored Procedure
   │
   ▼

Execute SQL Logic

For example:

Copy Data
    │
    ▼
Stored Procedure
    │
    ├── Update Audit
    ├── Transform Data
    └── Reconcile Counts

7.7.2 Why Use Stored Procedures?

Stored procedures are useful when SQL logic belongs naturally in the database/warehouse layer.

Common uses include:

Updating audit tables

Loading warehouse tables

Applying business rules

Data reconciliation

Aggregations

Updating control tables

Managing ETL metadata

Executing reusable SQL transformations

7.7.3 Basic Stored Procedure

Example:

CREATE PROCEDURE dbo.usp_UpdateSalesSummary

AS

BEGIN

    UPDATE dbo.SalesSummary
    SET LastUpdated = CURRENT_TIMESTAMP;

END;

The procedure can then be invoked by a pipeline.

Conceptually:

Fabric Pipeline
       │
       ▼
Stored Procedure Activity
       │
       ▼
dbo.usp_UpdateSalesSummary
       │
       ▼

SalesSummary updated

The exact T-SQL syntax and stored-procedure support depend on the Fabric item/SQL endpoint you're targeting, so validate the procedure against your current Fabric Warehouse capabilities.

7.7.4 Stored Procedure in an ETL Pipeline

A common pattern is:

Source
   │
   ▼
Copy Data
   │
   ▼
Staging/Bronze
   │
   ▼
Stored Procedure
   │
   ▼

Warehouse

For example:

SQL Server
    │
    ▼
Copy Orders
    │
    ▼
Stage_Orders
    │
    ▼
usp_Load_FactOrders
    │
    ▼

FactOrders

The Copy activity handles data movement.

The stored procedure handles SQL processing.

7.7.5 Stored Procedure with Parameters

Stored procedures become much more reusable when they accept parameters.

Example:

CREATE PROCEDURE dbo.usp_LoadSales

@BatchID VARCHAR(100)

AS

BEGIN

    INSERT INTO dbo.FactSales
    (
        OrderID,
        SalesAmount,
        BatchID
    )
    SELECT
        OrderID,
        SalesAmount,
        @BatchID
    FROM dbo.StageSales;

END;

The pipeline can supply:

BatchID = 20260822_001

Architecture:

Pipeline
   │
   ├── BatchID
   │
   ▼
Stored Procedure
   │
   ▼

Load FactSales

7.7.6 Pipeline Parameters → Stored Procedure Parameters

This is a very useful pattern.

Pipeline parameter:

p_BatchID

Pipeline:

@pipeline().parameters.p_BatchID

Stored procedure:

@BatchID

Flow:

Pipeline Parameter
       │
       ▼
Stored Procedure Activity
       │
       ▼

@BatchID

       │
       ▼

SQL Processing

For example:

p_BatchID

20260822_001

usp_LoadSales

@BatchID = 20260822_001

7.7.7 Stored Procedure for Audit Logging

A very common enterprise scenario is pipeline auditing.

Stored procedure:

CREATE PROCEDURE dbo.usp_LogPipelineRun

@PipelineName VARCHAR(200),

@RunID VARCHAR(200),

@Status VARCHAR(50)

AS

BEGIN

    INSERT INTO dbo.PipelineAudit
    (
        PipelineName,
        RunID,
        Status
    )
    VALUES
    (
        @PipelineName,
        @RunID,
        @Status
    );

END;

Pipeline:

Pipeline
   │
   ▼
Copy Data
   │
   ▼
Stored Procedure
   │
   ▼

PipelineAudit

7.7.8 Stored Procedure for Start/End Audit

You can maintain execution information.

Pipeline Start
      │
      ▼
Log START
      │
      ▼
Copy Data
      │
      ▼
Transform
      │
      ▼

Log SUCCESS

Failure:

Activity Failure
      │
      ▼

Log FAILED

Example audit table:

CREATE TABLE dbo.PipelineAudit

(

PipelineName VARCHAR(200),

RunID VARCHAR(200),

Status VARCHAR(50),

StartTime DATETIME2,

EndTime DATETIME2,

RowsRead BIGINT,

RowsWritten BIGINT,

ErrorMessage VARCHAR(4000)

);

7.7.9 Stored Procedure for Warehouse Loading

Suppose Bronze data has already been loaded.

Bronze
  │
  ▼
Silver
  │
  ▼
Stage
  │
  ▼
Stored Procedure
  │
  ▼

Warehouse

Procedure:

CREATE PROCEDURE dbo.usp_LoadFactSales

AS

BEGIN

    INSERT INTO dbo.FactSales
    (
        OrderID,
        CustomerID,
        SalesAmount
    )
    SELECT
        OrderID,
        CustomerID,
        SalesAmount
    FROM dbo.StageSales;

END;

Pipeline:

Load Stage
    │
    ▼
usp_LoadFactSales
    │
    ▼

FactSales

7.7.10 Stored Procedure for Data Transformation

Example:

CREATE PROCEDURE dbo.usp_CleanCustomers

AS

BEGIN

    UPDATE dbo.StageCustomers
    SET CustomerName = LTRIM(RTRIM(CustomerName))
    WHERE CustomerName IS NOT NULL;

END;

Pipeline:

Copy Customers
      │
      ▼
Stage Customers
      │
      ▼
usp_CleanCustomers
      │
      ▼

Clean Customers

For large-scale Spark transformations, however, use notebooks/Spark where that is a better fit.

7.7.11 Stored Procedure for Reconciliation

Suppose you want to compare source and destination row counts.

Procedure:

CREATE PROCEDURE dbo.usp_ReconcileOrders

AS

BEGIN

-- Reconciliation logic

-- Compare source/staging/target counts

END;

Pipeline:

Source Load
    │
    ▼
Warehouse Load
    │
    ▼
Reconciliation Procedure
    │
    ▼

PASS / FAIL

Example:

Source Rows = 1,000,000

Target Rows = 1,000,000

Difference = 0

Status = PASS

7.7.12 Stored Procedure for Incremental Loading

Stored procedures can also implement incremental warehouse logic.

Suppose:

StageOrders

contains new/changed records.

Procedure:

CREATE PROCEDURE dbo.usp_MergeOrders

AS

BEGIN

-- Apply inserts/updates from staging

-- to the target warehouse table

END;

Pipeline:

Get Watermark
      │
      ▼
Copy Changed Orders
      │
      ▼
StageOrders
      │
      ▼
usp_MergeOrders
      │
      ▼

FactOrders

7.7.13 Stored Procedure + Lookup

Lookup can retrieve a configuration value and pass it to a stored procedure.

Configuration
      │
      ▼
Lookup
      │
      ▼
Stored Procedure
      │
      ▼

Warehouse

Example:

Lookup

BatchID = 20260822_001

usp_LoadSales(@BatchID)

7.7.14 Stored Procedure + ForEach

You can execute a stored procedure for each configuration item.

Lookup
  │
  ▼
ForEach
  │
  ├── Customers → Stored Procedure
  ├── Products  → Stored Procedure
  └── Orders    → Stored Procedure

For example:

@item().TargetTable

could be supplied to a procedure where supported.

7.7.15 Stored Procedure + Switch

This is useful when different processing procedures are required.

                LoadType
                   │
                   ▼
                 Switch
                   │
        ┌──────────┼──────────┐
        ▼          ▼          ▼
       Full   Incremental     CDC
        │          │          │
        ▼          ▼          ▼

usp_Full usp_Incremental usp_CDC

Example:

Orders

LoadType = Incremental

        │
        ▼

usp_LoadOrdersIncremental

7.7.16 End-to-End Example

Let's build a sales pipeline.

SQL Server
    │
    ▼
Copy Data
    │
    ▼
Bronze_Orders
    │
    ▼
Notebook
    │
    ▼
Silver_Orders
    │
    ▼
Stored Procedure
    │
    ▼
FactOrders
    │
    ▼

Audit

Stored procedure:

CREATE PROCEDURE dbo.usp_LoadFactOrders

@BatchID VARCHAR(100)

AS

BEGIN

    INSERT INTO dbo.FactOrders
    (
        OrderID,
        CustomerID,
        Amount,
        BatchID
    )
    SELECT
        OrderID,
        CustomerID,
        Amount,
        @BatchID
    FROM dbo.StageOrders;

END;

Pipeline supplies:

BatchID = 20260822_001

7.7.17 Stored Procedure with Multiple Parameters

Example:

CREATE PROCEDURE dbo.usp_LoadOrders

@BatchID VARCHAR(100),

@LoadDate DATE,

@LoadType VARCHAR(50)

AS

BEGIN

-- Processing logic

END;

Pipeline:

Parameters
│
├── BatchID
├── LoadDate
└── LoadType
        │
        ▼

Stored Procedure

Example:

BatchID = 20260822_001

LoadDate = 2026-08-22

LoadType = Incremental

7.7.18 Stored Procedure and Transactions

Stored procedures can be useful when multiple database operations need coordinated transactional behavior, subject to the transaction support of the target Fabric SQL engine.

Conceptually:

Start Transaction
      │
      ▼
Update Dimension
      │
      ▼
Load Fact
      │
      ▼
Update Audit
      │
      ▼

Commit

If an operation fails:

Failure
   │
   ▼

Rollback

Always verify the exact transaction semantics supported by the Fabric Warehouse/SQL endpoint you're using.

7.7.19 Stored Procedure vs Notebook

This is an important Fabric architecture decision.

Stored ProcedureNotebook
SQL-basedPython/PySpark/Scala/Spark
Best for relational processingBest for large-scale Spark processing
Warehouse transformationsLakehouse transformations
SQL business logicComplex data engineering
Audit/control logicData cleansing/ML
Relational operationsDistributed processing

A common architecture is:

Lakehouse
   │
   ▼
Notebook
   │
   ▼
Silver
   │
   ▼
Stored Procedure
   │
   ▼

Warehouse

7.7.20 Stored Procedure vs Copy Data

They solve different problems.

Copy Data

Source → Destination

Moves data.

Stored Procedure

Database

SQL logic

Processes data.

Typical combination:

SQL Server
    │
    ▼
Copy Data
    │
    ▼
Stage Table
    │
    ▼
Stored Procedure
    │
    ▼

Fact Table

7.7.21 Stored Procedure vs Dataflow Gen2

Dataflow Gen2

Good for:

Low-code transformations

Visual transformations

Data cleansing

Stored Procedure

Good for:

SQL transformations

Warehouse logic

Audit

Reconciliation

Reusable SQL business logic

Example:

Dataflow Gen2

Silver

Stored Procedure

Warehouse

7.7.22 Error Handling

Stored procedures can fail.

Pipeline:

Copy Data
    │
    ▼
Stored Procedure
    │
 ┌──┴──────┐
 ▼         ▼
Success   Failure
 │         │
 ▼         ▼
Continue  Log Error
            │
            ▼

Alert

Capture useful information:

PipelineName

RunID

ProcedureName

ErrorMessage

StartTime

EndTime

7.7.23 Retry Considerations

Do not automatically retry every stored procedure failure.

Potentially retryable

Transient connection issue

Temporary service issue

Usually not retryable

Invalid SQL

Missing table

Invalid column

Data type error

Business rule failure

Retrying a non-idempotent procedure can also create duplicate effects.

7.7.24 Idempotency

This is an important production concept.

Suppose a procedure inserts:

OrderID = 1001

If the pipeline retries and inserts it again:

1001

1001

you may get duplicates.

A better procedure should be designed so that rerunning the same batch doesn't corrupt the target.

For example, use appropriate keys and merge/upsert logic where supported.

Conceptually:

Run 1

Order 1001 → Insert

Retry

Order 1001 → Already exists → Update/Skip

7.7.25 Stored Procedure for Audit

A good pattern is:

                Pipeline
                   │
          ┌────────┴────────┐
          ▼                 ▼
       Success            Failure
          │                 │
          ▼                 ▼
usp_LogSuccess        usp_LogFailure
          │                 │
          └────────┬────────┘
                   ▼

Audit Table

This centralizes audit logic.

7.7.26 Complete Enterprise Pattern

                     START
                       │
                       ▼
                Lookup Config
                       │
                       ▼
                    ForEach
                       │
                       ▼
                    Switch
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
        Full       Incremental      CDC
          │            │            │
          ▼            ▼            ▼
      Copy Data    Copy Changes   CDC Load
          │            │            │
          └────────────┼────────────┘
                       ▼
                    Bronze
                       │
                       ▼
                   Notebook
                       │
                       ▼
                    Silver
                       │
                       ▼
               Stored Procedure
                       │
                       ▼
                  Warehouse
                       │
                       ▼

Audit Log

This combines the concepts from Sections 7.1–7.7.

7.7.27 Best Practices

1. Keep SQL logic in SQL

If the transformation is naturally relational, a stored procedure can be a good choice.

2. Use parameters

Avoid hardcoding:

BatchID

LoadDate

Table names

where appropriate.

3. Make procedures reusable

Example:

usp_LoadFactSales

usp_LoadFactOrders

usp_LogPipelineRun

4. Make procedures idempotent when possible

This helps with retries and recovery.

5. Keep audit logic consistent

Use a standard audit table/procedure.

6. Don't put everything into stored procedures

Use:

Pipeline → Orchestration

Notebook → Spark

Stored Procedure → SQL/Warehouse

Copy → Data movement

7. Handle errors properly

Capture:

Procedure

Error

BatchID

Pipeline RunID

Timestamp

7.7.28 Hands-On Exercise

Create a simple Warehouse load.

Step 1 — Create staging table

CREATE TABLE dbo.StageOrders

(

OrderID INT,

CustomerID INT,

Amount DECIMAL(18,2)

);

Step 2 — Create target table

CREATE TABLE dbo.FactOrders

(

OrderID INT,

CustomerID INT,

Amount DECIMAL(18,2),

BatchID VARCHAR(100)

);

Step 3 — Create procedure

CREATE PROCEDURE dbo.usp_LoadFactOrders

@BatchID VARCHAR(100)

AS

BEGIN

    INSERT INTO dbo.FactOrders
    (
        OrderID,
        CustomerID,
        Amount,
        BatchID
    )
    SELECT
        OrderID,
        CustomerID,
        Amount,
        @BatchID
    FROM dbo.StageOrders;

END;

Step 4 — Pipeline

Build:

SQL Server
    │
    ▼
Copy Data
    │
    ▼
StageOrders
    │
    ▼
Stored Procedure
    │
    ▼

FactOrders

Pass:

BatchID = 20260822_001

7.7.29 Interview Questions

1. What is a stored procedure?

A reusable set of SQL statements stored in the database and executed when called.

2. Why use stored procedures in Fabric pipelines?

To execute reusable SQL/database logic as part of an orchestrated workflow, where supported.

3. Can stored procedures accept parameters?

Yes.

Example:

@BatchID

@LoadDate

@LoadType

4. How do you pass pipeline values to a procedure?

Use the stored-procedure activity's parameter mapping and dynamic expressions where supported.

Example:

@pipeline().parameters.p_BatchID

5. Stored Procedure vs Notebook?

Stored Procedure → SQL/relational processing

Notebook → Spark/PySpark/distributed processing

6. Give a common use case.

Stage → Stored Procedure → Fact Table

7. How can stored procedures help with auditing?

A procedure can centralize insertion/update logic for pipeline audit records.

8. Why is idempotency important?

It prevents duplicate or incorrect results when a pipeline or activity is retried.

9. Can a stored procedure be used inside a ForEach?

Yes, where the activity/connector supports it.

10. What is a good Fabric architecture?

Pipeline

Copy

Lakehouse

Notebook

Silver

Stored Procedure

Warehouse

7.7.30 Final Cheat Sheet

             STORED PROCEDURE
                     │
                     ▼
              Reusable SQL Logic
                     │
       ┌─────────────┼─────────────┐
       ▼             ▼             ▼
    Transform      Audit       Reconcile
       │             │             │
       ▼             ▼             ▼

Warehouse Control Validation

Key pattern

Pipeline
   │
   ▼
Copy Data
   │
   ▼
Stage
   │
   ▼
Stored Procedure
   │
   ▼

Warehouse

With parameters

Pipeline Parameter
       │
       ▼
Stored Procedure Parameter
       │
       ▼

SQL Processing

With error handling

Stored Procedure
       │
   ┌───┴────┐
   ▼        ▼
Success   Failure
   │        │
   ▼        ▼

Continue Audit + Alert

The key idea is to use the pipeline for orchestration and the stored procedure for reusable SQL logic. This separation makes Fabric ETL solutions easier to maintain, test, monitor, and scale.

↑ Back to top
Module 7 · Lesson 7.14

Module 7 · Lesson 7.8

Error Handling

7.8 Error Handling

7.8.1 What is Error Handling?

Error handling in Microsoft Fabric Data Factory is the process of detecting, capturing, managing, logging, and responding to failures during pipeline execution.

A production pipeline should not simply stop when an activity fails.

Instead:

Pipeline
   │
   ▼
Activity
   │
 ┌─┴─────────┐
 ▼           ▼
Success     Failure
 │           │
 ▼           ▼
Continue    Handle Error
             │
       ┌─────┼─────┐
       ▼     ▼     ▼

Log Retry Alert

7.8.2 Why is Error Handling Important?

Real-world pipelines can fail because of:

Source database unavailable

Network failure

API timeout

Invalid credentials

Missing file

Invalid SQL

Schema changes

Duplicate records

Data quality issues

Destination capacity/resource problems

Service/transient errors

A production pipeline should be able to:

Detect the error

Capture the error details

Log the failure

Retry when appropriate

Skip or quarantine bad data when appropriate

Alert support teams

Update audit/control tables

Prevent downstream processing when required

7.8.3 Basic Error Handling Pattern

The simplest pattern is:

             Copy Data
                │
          ┌─────┴─────┐
          ▼           ▼
       Success      Failure
          │           │
          ▼           ▼
      Continue      Log Error
                      │
                      ▼

Alert

In Fabric pipelines, activity dependencies can be configured based on outcomes such as successful completion or failure.

7.8.4 Success and Failure Dependencies

Suppose you have:

Copy Data
    │
    ▼

Transform

Normally:

Copy Data
    │
 Success
    │
    ▼

Transform

But you can also create a failure path:

Copy Data
    │
 Failure
    │
    ▼

Log Error

So:

Copy Data

/ \

/ \

          Success          Failure
             │                │
             ▼                ▼

Transform Log Error

7.8.5 Four Important Outcomes

In pipeline orchestration, you commonly design around:

Success

Failure

Skipped

Completion

Success

The activity completed successfully.

Copy → Success → Transform

Failure

The activity failed.

Copy → Failure → Error Handler

Skipped

The activity didn't execute because its dependency conditions were not satisfied.

Completion

Run the next activity regardless of whether the previous activity succeeded or failed, depending on the dependency configuration.

7.8.6 Success Path

Example:

Copy Customers
      │
      ▼
   SUCCESS
      │
      ▼
Transform Customers
      │
      ▼

Load Warehouse

7.8.7 Failure Path

Copy Customers
      │
      ▼
    FAILURE
      │
      ▼
Log Error
      │
      ▼
Update Audit
      │
      ▼

Send Alert

The main processing branch should not continue if the failed activity is a prerequisite.

7.8.8 Completion Path

Sometimes you want cleanup to happen regardless of the outcome.

Example:

Copy Data
   │
   ├── Success
   │
   └── Failure
          │
          ▼

Cleanup

The cleanup activity can use a completion dependency.

Typical uses:

Close resources

Write final audit status

Update monitoring state

Remove temporary files

7.8.9 Retry Logic

Some failures are temporary.

Example:

API Call
   │
   ▼
Timeout
   │
   ▼
Retry
   │
   ▼

Success

For example:

Attempt 1 → Timeout

Attempt 2 → Timeout

Attempt 3 → Success

Retries are useful for transient failures.

7.8.10 When Should You Retry?

Good candidates

Temporary network failure

API timeout

Transient service error

Temporary connection issue

Short-lived resource contention

Poor candidates

Invalid SQL

Missing table

Invalid column

Invalid credentials

Bad configuration

Data type mismatch

Permanent business-rule failure

For permanent errors, retrying normally just wastes time.

7.8.11 Retry Example

Copy Data
   │
   ▼
Failure
   │
   ▼
Retry
   │
   ├── Success → Continue
   │
   └── Failure → Error Handler

Conceptually:

Attempt 1

Failure

Wait

Attempt 2

Failure

Wait

Attempt 3

Failure

Log + Alert

The actual retry count/delay should be configured based on the activity and connector capabilities.

7.8.12 Error Logging

A production pipeline should record errors.

Example table:

CREATE TABLE dbo.PipelineErrorLog

(

PipelineName VARCHAR(200),

RunID VARCHAR(200),

ActivityName VARCHAR(200),

ErrorMessage VARCHAR(4000),

ErrorTime DATETIME2

);

When an activity fails:

Pipeline
   │
   ▼
Copy Data
   │
   ▼
Failure
   │
   ▼
Error Handler
   │
   ▼

PipelineErrorLog

7.8.13 What Should Be Logged?

At minimum:

PipelineName

RunID

ActivityName

Status

ErrorMessage

StartTime

EndTime

For data pipelines, also consider:

SourceTable

TargetTable

BatchID

LoadType

RowsRead

RowsWritten

Environment

Example:

PipelineActivityStatusError
PL_OrdersCopy OrdersFAILEDSQL timeout

7.8.14 Batch ID

A BatchID is extremely useful for error tracking.

Example:

BatchID = 20260822_001

Then:

BatchID
   │
   ├── Copy
   ├── Transform
   ├── Load
   └── Audit

If something fails, you can search all related records using the BatchID.

7.8.15 Pipeline Run ID

Each pipeline execution has a run identifier.

Conceptually:

Pipeline
   │
   ▼
RunID
   │
   ├── Activity 1
   ├── Activity 2
   └── Activity 3

Store the RunID in your audit/error table.

This makes troubleshooting much easier.

7.8.16 Error Handling with Stored Procedure

You can use a stored procedure to centralize logging.

Activity
   │
 Failure
   │
 ▼
Stored Procedure
   │
 ▼
usp_LogPipelineError
   │
 ▼

PipelineErrorLog

Procedure concept:

CREATE PROCEDURE dbo.usp_LogPipelineError

@PipelineName VARCHAR(200),

@RunID VARCHAR(200),

@ActivityName VARCHAR(200),

@ErrorMessage VARCHAR(4000)

AS

BEGIN

    INSERT INTO dbo.PipelineErrorLog
    (
        PipelineName,
        RunID,
        ActivityName,
        ErrorMessage,
        ErrorTime
    )
    VALUES
    (
        @PipelineName,
        @RunID,
        @ActivityName,
        @ErrorMessage,
        CURRENT_TIMESTAMP
    );

END;

7.8.17 Error Handling with Variables

You can maintain a runtime status.

Variables:

PipelineStatus

ErrorMessage

Initial:

PipelineStatus = RUNNING

Success:

PipelineStatus = SUCCESS

Failure:

PipelineStatus = FAILED

Architecture:

Start
 │
 ▼

Status = RUNNING

 │
 ▼
Processing
 │
 ├── Success → SUCCESS
 │
 └── Failure → FAILED

Remember that variables are runtime state; persistent audit information should be stored externally.

7.8.18 Error Handling with Lookup

Lookup can retrieve retry or configuration settings.

Example configuration:

PipelineMaxRetryIsActive
Orders31
Customers21

Pipeline:

Lookup Retry Configuration
          │
          ▼
      Processing
          │
          ▼

Retry

This allows retry behavior to be metadata-driven.

7.8.19 Error Handling with Switch

Suppose the pipeline identifies error types:

ErrorType
   │
   ▼
 Switch
   │
   ├── TRANSIENT
   │       ↓
   │     Retry
   │
   ├── DATA
   │       ↓
   │     Quarantine
   │
   └── CONFIG

Stop + Alert

This allows different errors to receive different treatment.

7.8.20 Data Error vs System Error

This distinction is important.

System error

Example:

SQL Server connection timeout

Possible action:

Retry

Data error

Example:

Amount = "ABC"

Possible action:

Reject/Quarantine

Configuration error

Example:

Target table doesn't exist

Possible action:

Stop + Alert

7.8.21 Data Quarantine

Not every bad record should cause the entire pipeline to fail.

Suppose:

1,000,000 records

and:

500 invalid records

You could design:

Valid Records
     │
     ▼

Lakehouse

Invalid Records
     │
     ▼

Quarantine

Architecture:

Source
  │
  ▼
Validation
  │
 ┌┴─────────────┐
 ▼              ▼
Valid          Invalid
 │                │
 ▼                ▼

Bronze Quarantine

This is useful for data-quality pipelines.

7.8.22 Error Handling in ForEach

Suppose:

ForEach
│
├── Customers → SUCCESS
├── Products  → SUCCESS
├── Orders    → FAILURE
└── Payments  → SUCCESS

You should be able to identify:

Failed Item = Orders

and record:

Table = Orders

Status = FAILED

Error = Timeout

Avoid assuming that one shared mutable variable is a safe way to collect errors when iterations run concurrently.

7.8.23 ForEach Failure Strategy

There are several architectural choices.

Strategy 1 — Fail entire pipeline

Customers → Success

Products → Success

Orders    → Failure
              │
              ▼

Pipeline Failed

Good when all tables are mandatory.

Strategy 2 — Continue processing other items

Customers → Success

Products → Success

Orders → Failure

Payments → Success

At the end:

Pipeline Status = PARTIAL FAILURE

Useful when tables are independent.

Strategy 3 — Quarantine failed items

Orders → Failure
   │
   ▼

Error Queue / Control Table

Then retry only Orders later.

7.8.24 Error Handling with REST APIs

REST APIs introduce additional failure types:

200 → Success

400 → Bad Request

401 → Unauthorized

403 → Forbidden

404 → Not Found

429 → Rate Limited

500 → Server Error

503 → Service Unavailable

Possible strategy:

429 / 5xx

Retry

400

Fix request / Quarantine

401 / 403

Credential / Permission Alert

This is a strong real-world example of intelligent error handling.

7.8.25 Error Handling for SQL Server

Possible failures:

Connection Timeout

Deadlock

Permission Error

Invalid Object

Syntax Error

Data Conversion Error

Possible strategy:

Transient Timeout

Retry

Invalid Object

Stop + Alert

Data Conversion

Data Quality Handling

7.8.26 Error Handling for File Ingestion

Example:

Expected:

sales_20260822.csv

But file is missing.

Pipeline:

Check File
    │
    ▼
File Missing
    │
    ▼
Log
    │
    ▼

Alert

You may also choose to wait/retry if the file is expected to arrive later.

7.8.27 Error Handling with Notifications

A typical production flow:

Pipeline Failure
      │
      ▼
Capture Error
      │
      ▼
Write Audit
      │
      ▼
Notification
      │
      ▼

Support Team

The notification mechanism can be implemented using the organization's available Fabric/Azure/enterprise integration tools.

7.8.28 End-to-End Error Handling

A mature pipeline could look like:

                         START
                           │
                           ▼
                    Lookup Configuration
                           │
                           ▼
                        ForEach
                           │
                           ▼
                         Switch
                           │
            ┌──────────────┼──────────────┐
            ▼              ▼              ▼
           Full       Incremental          CDC
            │              │              │
            ▼              ▼              ▼
         Copy Data      Copy Data       Copy Data
            │              │              │
            └──────────────┼──────────────┘
                           ▼
                       Validation
                           │
                     ┌─────┴─────┐
                     ▼           ▼
                   Valid       Invalid
                     │           │
                     ▼           ▼
                  Bronze     Quarantine
                     │
                     ▼
                  Notebook
                     │
                     ▼
                   Silver
                     │
                     ▼
              Stored Procedure
                     │
                     ▼
                 Warehouse
                     │
                     ▼

Audit

Failure at any critical stage:

Failure
   │
   ▼
Error Handler
   │
   ├── Log
   ├── Retry if appropriate
   ├── Update Audit
   └── Alert

7.8.29 Error Handling Framework

A reusable enterprise framework can have:

Control table

CREATE TABLE dbo.PipelineAudit

(

PipelineName VARCHAR(200),

RunID VARCHAR(200),

BatchID VARCHAR(100),

Status VARCHAR(50),

StartTime DATETIME2,

EndTime DATETIME2

);

Error table

CREATE TABLE dbo.PipelineErrorLog

(

PipelineName VARCHAR(200),

RunID VARCHAR(200),

BatchID VARCHAR(100),

ActivityName VARCHAR(200),

ErrorMessage VARCHAR(4000),

ErrorTime DATETIME2

);

Then:

             Pipeline
                 │
        ┌────────┴────────┐
        ▼                 ▼
     Success            Failure
        │                 │
        ▼                 ▼
    Audit Table      Error Table
                          │
                          ▼

Alert

7.8.30 Retry vs Failure

A common mistake is to retry everything.

Example

Invalid SQL

Retry

Invalid SQL

Retry

Invalid SQL

This doesn't solve the problem.

Instead:

Invalid SQL

Stop

Log

Alert

But:

Network Timeout

Retry

Success

The goal is to distinguish transient failures from permanent failures.

7.8.31 Fail Fast

For critical prerequisites, use a fail-fast strategy.

Example:

Validate Configuration
       │
       ▼
Invalid
       │
       ▼

STOP

Don't continue to:

Copy

Transform

Warehouse

if the configuration is invalid.

7.8.32 Error Handling and Dependencies

Example:

Lookup Config
      │
      ▼
Copy Data
      │
      ▼

Transform

If Lookup fails:

Lookup Failed
      │
      ▼

Copy should NOT execute

If Copy succeeds:

Copy Success
      │
      ▼

Transform

If Copy fails:

Copy Failure
      │
      ├── Log Error
      └── Don't run Transform

This is why dependency conditions are fundamental to pipeline error handling.

7.8.33 Hands-On Exercise

Build:

PL_ErrorHandling_Demo

Pipeline:

Start
 │
 ▼
Lookup Config
 │
 ▼
Copy Data
 │
 ├────────────── Failure ──────────────┐
 │                                     ▼
 ▼                               Log Error
Transform                              │
 │                                     ▼
 ▼                                  Audit
Stored Procedure                       │
 │                                     ▼
 ▼                                   Alert

Audit

Step 1 — Add Copy Activity

Configure:

SQL Server → Lakehouse

Step 2 — Add Success Path

Copy
 │
Success
 │
 ▼

Transform

Step 3 — Add Failure Path

Copy
 │
Failure
 │
 ▼

Log Error

Step 4 — Add Audit

Record:

PipelineName

RunID

BatchID

Status

Step 5 — Test Failure

Intentionally configure an invalid source/table in a development environment.

Expected:

Copy

FAILED

Error Log

Audit

7.8.34 Interview Questions

1. What is error handling in a Fabric pipeline?

The process of detecting, managing, logging, retrying, and responding to pipeline failures.

2. What is a failure dependency?

A dependency that executes a downstream activity when the upstream activity fails.

3. When should you use retry?

For transient failures such as temporary network/service issues.

4. When should you not retry?

For permanent errors such as invalid SQL, missing tables, invalid credentials, or bad configuration.

5. How do you log pipeline errors?

Use an audit/error table, stored procedure, or an appropriate monitoring/logging mechanism.

6. What should an error log contain?

At minimum:

Pipeline

RunID

Activity

Error

Timestamp

7. What is a BatchID?

A value used to identify and correlate one logical data-processing batch across pipeline activities.

8. How do you handle errors in ForEach?

Decide whether to fail the whole pipeline, continue independent items, or capture failed items for later retry/quarantine.

9. What is data quarantine?

Separating invalid records from valid records so the valid data can continue through the pipeline.

10. What is a good production error-handling pattern?

Failure

Retry if transient

Capture error

Write audit

Alert

Stop or continue according to business criticality

7.8.35 Final Cheat Sheet

                  ACTIVITY
                     │
               ┌─────┴─────┐
               ▼           ▼
            SUCCESS      FAILURE
               │           │
               ▼           ▼

Continue Retry?

                           │
                    ┌──────┴──────┐
                    ▼             ▼
                  YES             NO
                    │             │
                    ▼             ▼
                  Retry         Log Error
                    │             │
               ┌────┴────┐        ▼
               ▼         ▼      Audit
            Success    Failure     │
               │         │        ▼
               ▼         ▼      Alert

Continue Log/Error

Enterprise pattern

Lookup

ForEach

Switch

Copy

Transform

Stored Procedure

Warehouse

Audit

At every critical stage:

Success → Next Step

Failure → Retry (if transient)

Log Error

Audit

Alert

Good error handling doesn't mean simply making the pipeline fail safely. It means knowing why it failed, deciding whether the failure is recoverable, retrying when appropriate, preserving audit information, and preventing bad or incomplete data from silently reaching downstream systems.

↑ Back to top
Module 7 · Lesson 7.16

Module 7 · Lesson 7.9

Build End-to-End ETL Pipeline

7.9 Build End-to-End ETL Pipeline

An End-to-End ETL Pipeline in Microsoft Fabric combines everything learned in Module 7:

Pipeline Activities

Variables

Parameters

ForEach

Switch

Lookup

Stored Procedure

Error Handling

The goal is to build a production-style pipeline that extracts data from a source, loads it into the Lakehouse, transforms it, loads the Warehouse, and records the execution status.

7.9.1 What is an End-to-End ETL Pipeline?

ETL stands for:

E → Extract

T → Transform

L → Load

In Fabric:

Source Systems
     │
     ▼
   Extract
     │
     ▼
   Bronze
     │
     ▼
  Transform
     │
     ▼
   Silver
     │
     ▼
    Gold
     │
     ▼

Fabric Warehouse

A complete pipeline can look like:

SQL Server
    │
    ▼
Lookup Configuration
    │
    ▼
ForEach
    │
    ▼
Switch LoadType
    │
    ▼
Copy Data
    │
    ▼
Bronze Lakehouse
    │
    ▼
Notebook / Transformation
    │
    ▼
Silver Lakehouse
    │
    ▼
Stored Procedure
    │
    ▼
Fabric Warehouse
    │
    ▼

Audit + Monitoring

7.9.2 Project Scenario

Let's build a realistic Sales ETL Pipeline.

Source

SQL Server:

RetailSourceDB

Tables:

Customers

Products

Orders

OrderItems

Fabric destination

Lakehouse:

RetailLakehouse

Layers:

Bronze

Silver

Gold

Warehouse

RetailWarehouse

7.9.3 Overall Architecture

                    SQL Server
                        │
              ┌─────────┴─────────┐
              │                   │
         Configuration        Business Data
              │                   │
              ▼                   ▼
           Lookup              Copy Data
              │                   │
              ▼                   ▼
           ForEach             Bronze
              │                   │
              ▼                   ▼
            Switch             Notebook
              │                   │
      ┌───────┼────────┐          ▼
      ▼       ▼        ▼        Silver
    Full  Incremental  CDC        │
      │       │        │          ▼
      └───────┼────────┘         Gold
              │                   │
              ▼                   ▼
          Data Load          Stored Procedure
                                  │
                                  ▼
                              Warehouse
                                  │
                                  ▼

Audit / Monitor

7.9.4 Step 1 — Create the Lakehouse

Create:

RetailLakehouse

Recommended structure:

RetailLakehouse
│
├── Files
│
└── Tables
     │
     ├── Bronze
     ├── Silver
     └── Gold

For Delta tables, you may use naming conventions such as:

bronze_customers

bronze_products

bronze_orders

bronze_orderitems

7.9.5 Step 2 — Create the Warehouse

Create:

RetailWarehouse

Example tables:

DimCustomer

DimProduct

FactSales

Architecture:

Lakehouse
   │
   ▼
Silver / Gold
   │
   ▼

Warehouse

7.9.6 Step 3 — Create Configuration Table

Instead of hardcoding every source table into the pipeline, create a configuration table.

Example:

CREATE TABLE dbo.PipelineConfig

(

ConfigID INT,

SourceTable VARCHAR(200),

TargetTable VARCHAR(200),

LoadType VARCHAR(50),

WatermarkColumn VARCHAR(200),

IsActive BIT

);

Insert configuration:

INSERT INTO dbo.PipelineConfig

(

ConfigID,

SourceTable,

TargetTable,

LoadType,

WatermarkColumn,

IsActive

)

VALUES

(1, 'Customers', 'bronze_customers', 'Full', NULL, 1),

(2, 'Products', 'bronze_products', 'Full', NULL, 1),

(3, 'Orders', 'bronze_orders', 'Incremental', 'ModifiedDate', 1),

(4, 'OrderItems', 'bronze_orderitems', 'Incremental', 'ModifiedDate', 1);

Now the pipeline is metadata-driven.

7.9.7 Step 4 — Create Watermark Table

For incremental loading:

CREATE TABLE dbo.PipelineWatermark

(

SourceTable VARCHAR(200),

LastWatermark DATETIME2

);

Example:

Orders

2026-08-21 23:59:59

This allows the pipeline to remember what was processed during the previous successful run.

7.9.8 Step 5 — Create Audit Table

Create:

CREATE TABLE dbo.PipelineAudit

(

PipelineName VARCHAR(200),

RunID VARCHAR(200),

BatchID VARCHAR(100),

Status VARCHAR(50),

StartTime DATETIME2,

EndTime DATETIME2,

RowsProcessed BIGINT

);

Example:

PipelineBatchIDStatusRows
PL_Retail_ETLB20260822_001SUCCESS150000

7.9.9 Step 6 — Create Error Table

CREATE TABLE dbo.PipelineErrorLog

(

PipelineName VARCHAR(200),

RunID VARCHAR(200),

BatchID VARCHAR(100),

ActivityName VARCHAR(200),

ErrorMessage VARCHAR(4000),

ErrorTime DATETIME2

);

This becomes your centralized error repository.

7.9.10 Step 7 — Create Pipeline

Create:

PL_Retail_ETL

This will be the master ETL pipeline.

7.9.11 Step 8 — Create Pipeline Parameters

Create:

p_Environment

p_RunType

Example:

p_Environment = PROD

p_RunType = Scheduled

You could also add:

p_SourceSystem

if the same pipeline supports multiple source systems.

7.9.12 Step 9 — Create Pipeline Variables

Create:

v_BatchID

v_Status

v_RecordCount

Example initial values:

v_Status = STARTED

v_RecordCount = 0

Variables represent runtime state.

Remember:

Parameter

Input/configuration

Variable

Runtime state

7.9.13 Step 10 — Generate Batch ID

At the beginning of the pipeline:

Start
  │
  ▼

Set BatchID

Conceptually:

v_BatchID = 20260822_001

The same BatchID should be associated with all activities in that execution.

7.9.14 Step 11 — Set Pipeline Status

Set:

v_Status = RUNNING

Now:

START
  │
  ▼
BatchID
  │
  ▼

Status = RUNNING

7.9.15 Step 12 — Lookup Configuration

Add:

Lookup_Config

Query:

SELECT
    ConfigID,
    SourceTable,
    TargetTable,
    LoadType,
    WatermarkColumn
FROM dbo.PipelineConfig
WHERE IsActive = 1;

Output:

Customers → Full

Products → Full

Orders → Incremental

OrderItems → Incremental

7.9.16 Step 13 — Add ForEach

Connect:

Lookup_Config
       │
       ▼

ForEach

The ForEach processes each configuration record.

Conceptually:

ForEach
│
├── Customers
├── Products
├── Orders
└── OrderItems

7.9.17 Step 14 — Add Switch

Inside ForEach, add:

Switch

Expression:

@item().LoadType

Cases:

Full

Incremental

Architecture:

ForEach
   │
   ▼
Switch
   │
   ├── Full
   │
   └── Incremental

7.9.18 Step 15 — Build Full Load Branch

For Full:

Full
 │
 ▼
Copy Data
 │
 ▼

Bronze Table

Example:

Customers
    │
    ▼
Copy Data
    │
    ▼

bronze_customers

Products:

Products
    │
    ▼

bronze_products

7.9.19 Step 16 — Build Incremental Load Branch

For Orders:

Orders
   │
   ▼
Lookup Watermark
   │
   ▼
Copy Changed Records
   │
   ▼

Bronze Orders

Example watermark:

2026-08-21 23:59:59

Query conceptually:

SELECT *

FROM Orders

WHERE ModifiedDate > @LastWatermark;

After successful processing:

New Watermark

2026-08-22 14:30:00

7.9.20 Step 17 — Bronze Layer

The Copy activities load raw data into Bronze.

SQL Server
    │
    ▼
Copy
    │
    ▼

Bronze

Tables:

bronze_customers

bronze_products

bronze_orders

bronze_orderitems

Bronze should preserve source information with minimal transformation.

7.9.21 Step 18 — Transform Bronze → Silver

After ingestion:

Bronze
   │
   ▼
Notebook
   │
   ▼

Silver

A PySpark notebook can:

Remove duplicates

Handle nulls

Standardize data types

Clean strings

Validate records

Apply business rules

Example:

df = spark.read.table("bronze_orders")

clean_df = (

    df.dropDuplicates(["OrderID"])
      .filter("Amount >= 0")

)

clean_df.write.format("delta").mode("overwrite").saveAsTable(

"silver_orders"

)

7.9.22 Step 19 — Silver → Gold

Silver contains cleansed data.

Gold contains business-ready data.

Silver
   │
   ▼
Transformation
   │
   ▼

Gold

Example:

silver_orders
      │
      ├── Join Customers
      │
      ├── Join Products
      │
      └── Aggregate Sales
             │
             ▼

gold_sales

7.9.23 Step 20 — Stored Procedure

After Gold/staging data is ready:

Gold
 │
 ▼
Stored Procedure
 │
 ▼

Warehouse

Example:

usp_LoadFactSales

Procedure:

CREATE PROCEDURE dbo.usp_LoadFactSales

@BatchID VARCHAR(100)

AS

BEGIN

    INSERT INTO dbo.FactSales
    (
        OrderID,
        CustomerID,
        SalesAmount,
        BatchID
    )
    SELECT
        OrderID,
        CustomerID,
        SalesAmount,
        @BatchID
    FROM dbo.StageSales;

END;

The pipeline passes:

v_BatchID

to:

@BatchID

7.9.24 Step 21 — Audit Success

After successful processing:

Stored Procedure
       │
       ▼

Set Status = SUCCESS

       │
       ▼

Audit

Audit record:

PipelineName = PL_Retail_ETL

BatchID = B20260822_001

Status = SUCCESS

7.9.25 Step 22 — Error Handling

Every critical activity should have an appropriate failure path.

Example:

Copy Data
   │
   ├── Success → Transform
   │
   └── Failure → Error Handler

Error Handler:

Error
 │
 ▼
Log Error
 │
 ▼
Update Audit
 │
 ▼

Alert

7.9.26 Complete Error Flow

                  Activity
                     │
              ┌──────┴──────┐
              ▼             ▼
           Success        Failure
              │             │
              ▼             ▼

Continue Retry?

                            │
                       ┌────┴────┐
                       ▼         ▼
                     Yes         No
                       │         │
                       ▼         ▼
                     Retry     Log Error
                       │         │
                  ┌────┴────┐    ▼
                  ▼         ▼   Audit
               Success    Fail   │
                  │         │    ▼
                  ▼         ▼  Alert

Continue Error

7.9.27 Complete Pipeline

The finished pipeline looks like:

                              START
                                │
                                ▼
                         Set Batch ID
                                │
                                ▼

Status = RUNNING

                                │
                                ▼
                       Lookup Configuration
                                │
                                ▼
                             ForEach
                                │
                                ▼
                             Switch
                         ┌──────┴──────┐
                         ▼             ▼
                       Full       Incremental
                         │             │
                         ▼             ▼
                      Copy Data    Lookup Watermark
                         │             │
                         ▼             ▼
                      Bronze       Copy Changes
                         │             │
                         └──────┬──────┘
                                ▼
                            Validation
                                │
                                ▼
                            Notebook
                                │
                                ▼
                             Silver
                                │
                                ▼
                              Gold
                                │
                                ▼
                       Stored Procedure
                                │
                                ▼
                           Warehouse
                                │
                                ▼
                              Audit
                                │
                                ▼

Status=SUCCESS

Failure from a critical stage:

Activity Failure
      │
      ▼
Error Handler
      │
      ├── Retry if transient
      │
      ├── Log Error
      │
      ├── Update Audit
      │
      └── Alert

7.9.28 Complete Architecture

The overall Fabric solution can be represented as:

┌──────────────────────────────────────────────────────────────┐
│                     SOURCE SYSTEMS                           │
│                                                              │
│ SQL Server        REST API        Azure Blob        ADLS      │
└─────────┬────────────┬─────────────┬─────────────┬──────────┘
          │            │             │             │
          └────────────┴─────────────┴─────────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │   FABRIC PIPELINE   │
                    │                     │
                    │ Parameters          │
                    │ Variables           │
                    │ Lookup              │
                    │ ForEach             │
                    │ Switch              │
                    │ Copy Activity       │
                    │ Error Handling      │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │      BRONZE         │
                    │      LAKEHOUSE      │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │      NOTEBOOK       │
                    │     PySpark         │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │       SILVER        │
                    │      LAKEHOUSE      │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │        GOLD         │
                    │      LAKEHOUSE      │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │  STORED PROCEDURE   │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │      WAREHOUSE      │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │ AUDIT / ERROR LOG   │
                    └─────────────────────┘

7.9.29 Production Pipeline Design

A production-grade pipeline should include these components:

ComponentPurpose
ParametersRuntime configuration
VariablesRuntime state
LookupRead metadata
ForEachProcess multiple items
SwitchSelect processing strategy
Copy DataMove data
NotebookSpark transformations
Stored ProcedureSQL/Warehouse processing
AuditTrack execution
Error LogCapture failures
RetryRecover from transient errors
WatermarkIncremental processing

7.9.30 Metadata-Driven Design

The most important concept in this entire section is metadata-driven ETL.

Instead of:

Pipeline 1 → Customers

Pipeline 2 → Products

Pipeline 3 → Orders

Pipeline 4 → Inventory

build:

                    Configuration
                         │
                         ▼
                       Lookup
                         │
                         ▼
                      ForEach
                         │
                         ▼
                       Switch
                         │
                         ▼

Generic Logic

Adding a new table becomes largely a configuration exercise.

For example:

Invoices

can be added to:

PipelineConfig

rather than creating an entirely new pipeline.

7.9.31 Full Load Example

For Customers:

Configuration
     │
     ▼

LoadType = Full

     │
     ▼
Switch
     │
     ▼
Full Copy
     │
     ▼
bronze_customers
     │
     ▼
silver_customers
     │
     ▼
gold_customers
     │
     ▼

DimCustomer

7.9.32 Incremental Load Example

For Orders:

Configuration
     │
     ▼

LoadType = Incremental

     │
     ▼
Lookup Watermark
     │
     ▼
2026-08-21 23:59:59
     │
     ▼
SQL Server
     │
     ▼
Changed Orders
     │
     ▼
bronze_orders
     │
     ▼
silver_orders
     │
     ▼
gold_sales
     │
     ▼

FactSales

7.9.33 Failure Example

Suppose SQL Server becomes unavailable.

Copy Orders
     │
     ▼
Connection Timeout
     │
     ▼
Retry
     │
     ▼
Retry
     │
     ▼
Retry Failed
     │
     ▼
Error Handler
     │
     ├── Log Error
     ├── Update Audit
     └── Alert

Audit:

Pipeline = PL_Retail_ETL

BatchID = B20260822_001

Status = FAILED

Error:

Activity = Copy Orders

Error = Connection timeout

7.9.34 Partial Failure Example

Suppose four tables are processed:

Customers → SUCCESS

Products → SUCCESS

Orders → FAILURE

Payments → SUCCESS

You can design the pipeline to record:

Pipeline Status = PARTIAL_FAILURE

and:

Orders → Retry Required

This is preferable to losing information about which specific item failed.

7.9.35 Performance Considerations

For large workloads:

Use parallel ForEach where appropriate
Customers ─┐
Products  ─┼→ Parallel Copy
Orders     ─┤
Payments   ─┘

But control concurrency

Don't overload:

SQL Server

REST API

Lakehouse

Warehouse

Use incremental loading

Instead of:

1 billion rows

every day:

Only changed rows

Partition large datasets

For example:

Orders
 ├── 2026-08-20
 ├── 2026-08-21
 └── 2026-08-22

7.9.36 Security Considerations

A production ETL pipeline should use:

Secure connections

Appropriate workspace/item permissions

Least-privilege access

Secure credential management

No hardcoded passwords

No secrets in normal pipeline parameters

Separate DEV/TEST/PROD configuration

7.9.37 Monitoring

Monitor:

Pipeline Status

Duration

Rows Read

Rows Written

Failed Activities

Retries

Data Quality

Watermark

Example dashboard:
┌─────────────────────────────────────────────┐
│             PIPELINE MONITOR                │
├─────────────────────────────────────────────┤
│ Total Runs       : 120                      │
│ Successful       : 115                      │
│ Failed           : 3                        │
│ Partial Failure  : 2                        │
│ Avg Duration     : 18 min                   │
│ Records Loaded   : 25.4 Million             │
└─────────────────────────────────────────────┘

7.9.38 Hands-On Project

Project: Retail Sales ETL

Build:

PL_Retail_ETL

Source

SQL Server:

Customers

Products

Orders

OrderItems

Configuration

PipelineConfig

Destination

RetailLakehouse

Layers

Bronze

Silver

Gold

Warehouse

RetailWarehouse

Pipeline components

Parameters

Variables

Lookup

ForEach

Switch

Copy

Notebook

Stored Procedure

Error Handling

Audit

Expected Flow

                    PL_Retail_ETL
                          │
                          ▼
                    Batch ID
                          │
                          ▼
                 Lookup Configuration
                          │
                          ▼
                       ForEach
                          │
                          ▼

Switch

/ \

                   Full     Incremental
                    │            │
                    ▼            ▼
                  Copy       Watermark
                    │            │
                    ▼            ▼
                 Bronze       Changed Data
                    │            │
                    └─────┬──────┘
                          ▼
                     Validation
                          │
                          ▼
                       Notebook
                          │
                          ▼
                        Silver
                          │
                          ▼
                         Gold
                          │
                          ▼
                  Stored Procedure
                          │
                          ▼
                      Warehouse
                          │
                          ▼

Audit

7.9.39 Interview Questions

1. What is an end-to-end ETL pipeline?

A workflow that extracts data from source systems, transforms it, loads it into target systems, and manages monitoring/error handling throughout the process.

2. How do you make a pipeline reusable?

Use:

Parameters
+
Metadata
+
Lookup
+
ForEach
+

Dynamic content

3. Why use Lookup?

To retrieve configuration or runtime information dynamically.

4. Why use ForEach?

To process multiple tables/files/items using the same logic.

5. Why use Switch?

To select different processing logic based on values such as:

Full

Incremental

CDC

6. Why use variables?

For runtime state such as:

BatchID

Status

RecordCount

7. Why use a stored procedure?

For reusable SQL/database-side processing such as warehouse loading, audit, reconciliation, or business logic.

8. How do you implement incremental loading?

Use a persistent watermark/control table:

Lookup Watermark

Copy Changed Records

Process

Update Watermark

9. How do you handle transient errors?

Use appropriate retries and then route persistent failures to logging/alerting.

10. How do you handle bad records?

Separate valid and invalid records where appropriate:

Valid → Silver

Invalid → Quarantine

11. What is a metadata-driven pipeline?

A pipeline whose behavior is controlled by configuration data rather than hardcoded source/target logic.

12. What is the most important architecture pattern?

Lookup

ForEach

Switch

Dynamic Copy

Bronze

Transform

Silver/Gold

Warehouse

7.9.40 Module 7 Summary

You have now covered the major pipeline building blocks:

7.1 Pipeline Activities

7.2 Variables

7.3 Parameters

7.4 ForEach

7.5 Switch

7.6 Lookup

7.7 Stored Procedure

7.8 Error Handling

7.9 End-to-End ETL Pipeline

The complete mental model is:

                 ┌──────────────┐
                 │  PARAMETERS  │
                 └──────┬───────┘
                        │
                        ▼
                 ┌──────────────┐
                 │    LOOKUP    │
                 └──────┬───────┘
                        │
                        ▼
                 ┌──────────────┐
                 │   FOREACH    │
                 └──────┬───────┘
                        │
                        ▼
                 ┌──────────────┐
                 │    SWITCH    │
                 └──────┬───────┘
                        │
             ┌──────────┴──────────┐
             ▼                     ▼
           FULL               INCREMENTAL
             │                     │
             └──────────┬──────────┘
                        ▼
                 ┌──────────────┐
                 │  COPY DATA   │
                 └──────┬───────┘
                        │
                        ▼
                    BRONZE
                        │
                        ▼
                   NOTEBOOK
                        │
                        ▼
                    SILVER
                        │
                        ▼
                     GOLD
                        │
                        ▼
               STORED PROCEDURE
                        │
                        ▼
                  WAREHOUSE
                        │
                        ▼
              ┌─────────────────┐
              │ AUDIT / ERRORS  │
              └─────────────────┘

A strong Fabric ETL pipeline separates responsibilities: Pipeline activities orchestrate, parameters configure, variables maintain runtime state, Lookup reads metadata, ForEach handles repetition, Switch chooses processing logic, Copy moves data, Notebooks transform data with Spark, Stored Procedures handle SQL/Warehouse logic, and error handling makes the entire process reliable.**

↑ Back to top
Module 7 · Lesson 7.18