Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Python | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Python | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA | Python |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA | Python
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Databricks | Power Apps | Power Automate |
Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables | Power Apps | Power Automate
Power BI | Power Apps | Power Automate | SQL | VBA | Python | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
Business Professionals
Power BI | Power Pivot | Power Query | DAX
Cloud Flows | RPA | AI Builder | Copilot
60+ Formulas | Data Stories | Advanced Reporting & Modeling
VB Programming | Report Automation |
MS-Office Automation
Techno-Business Professionals
Power BI | Power Query | Advanced DAX | SQL - Query &
Programming
Microsoft Fabric | Power BI | Power Query | Advanced DAX |
SQL - Query & Programming
Power BI | Power Apps | Power Automate | Copilot Studio | Power Pages | Dataverse
Microsoft Power Apps | Microsoft Power Automate
Power BI | Adv. DAX | SQL (Query & Programming) |
VBA | Web Scrapping | API Integration
Power BI | Power Apps | Power Automate |
SQL (Query & Programming)
Power BI | Adv. DAX | Power Apps | Power Automate |
SQL (Query & Programming) | VBA | Web Scrapping | API Integration
Power Apps | Power Automate | SQL | VBA |
Web Scraping | RPA | API Integration
Technology Professionals
Power BI | DAX | SQL | ETL with SSIS | SSAS | VBA
Power BI | SQL | Azure Data Lake | Synapse Analytics |
Data Factory | Azure Analysis Services
Microsoft Fabric | Power BI | SQL | Lakehouse |
Data Factory (Pipelines) | Dataflows Gen2 | KQL | Delta Tables
Power BI | Power Apps | Power Automate | SQL | VBA | API Integration
Power BI | Advanced DAX | Databricks | SQL | Lakehouse Architecture
You don’t need a full-blown ETL tool to keep your Power BI or Fabric models fresh. This guide shows how to design robust incremental loads in MS-SQL using MERGE patterns, Change Tracking, and defensive error handling so your refreshes stay fast, reliable, and auditable.
If you want to go deeper into integrating SQL with reporting tools, pairing these patterns with a solid foundation in end-to-end Power BI with SQL can be a strong next step.
Incremental load is more than “load only yesterday’s data”. For Power BI and Fabric, a robust pattern usually means:
We’ll focus on a typical pattern:
Before writing MERGE, get the table design right.
For each core table that feeds Power BI/Fabric:
ModifiedDate (datetime/datetime2), orRowVersion (rowversion) column.IsDeleted BIT.CreatedOn, CreatedByModifiedOn, ModifiedByExample core table:
CREATE TABLE dbo.Customer
(
CustomerID INT NOT NULL PRIMARY KEY,
CustomerName NVARCHAR(200) NOT NULL,
Email NVARCHAR(200) NULL,
IsDeleted BIT NOT NULL CONSTRAINT DF_Customer_IsDeleted DEFAULT (0),
ModifiedDate DATETIME2(3) NOT NULL,
CreatedDate DATETIME2(3) NOT NULL,
ModifiedBy SYSNAME NULL,
CreatedBy SYSNAME NULL
);
Staging should be:
Example staging table for deltas:
CREATE TABLE stg.Customer_Delta
(
BatchID UNIQUEIDENTIFIER NOT NULL,
CustomerID INT NOT NULL,
CustomerName NVARCHAR(200) NOT NULL,
Email NVARCHAR(200) NULL,
OperationType CHAR(1) NOT NULL, -- 'I','U','D'
SourceModifiedOn DATETIME2(3) NOT NULL,
LoadInsertedOn DATETIME2(3) NOT NULL DEFAULT (SYSUTCDATETIME())
);
OperationType is optional if you infer changes via timestamps, but it’s handy when your upstream system already flags inserts/updates/deletes.
A minimal MERGE from stg.Customer_Delta to dbo.Customer:
MERGE dbo.Customer AS tgt
USING (
SELECT CustomerID,
CustomerName,
Email,
OperationType,
SourceModifiedOn
FROM stg.Customer_Delta
WHERE BatchID = @BatchID
) AS src
ON tgt.CustomerID = src.CustomerID
WHEN MATCHED AND src.OperationType IN ('U') THEN
UPDATE SET
tgt.CustomerName = src.CustomerName,
tgt.Email = src.Email,
tgt.ModifiedDate = src.SourceModifiedOn,
tgt.ModifiedBy = @UserName
WHEN MATCHED AND src.OperationType IN ('D') THEN
UPDATE SET
tgt.IsDeleted = 1,
tgt.ModifiedDate = src.SourceModifiedOn,
tgt.ModifiedBy = @UserName
WHEN NOT MATCHED BY TARGET AND src.OperationType IN ('I','U') THEN
INSERT (CustomerID, CustomerName, Email, IsDeleted,
CreatedDate, ModifiedDate, CreatedBy, ModifiedBy)
VALUES (src.CustomerID, src.CustomerName, src.Email, 0,
src.SourceModifiedOn, src.SourceModifiedOn, @UserName, @UserName)
;
Key points:
OperationType = 'D' sets IsDeleted rather than physically deleting.MERGE has a reputation for subtle bugs when:
Defensive patterns:
;WITH src AS
(
SELECT
CustomerID,
MAX(SourceModifiedOn) AS SourceModifiedOn,
MAX(OperationType) AS OperationType,
MAX(CustomerName) AS CustomerName,
MAX(Email) AS Email
FROM stg.Customer_Delta
WHERE BatchID = @BatchID
GROUP BY CustomerID
)
MERGE dbo.Customer AS tgt
USING src
ON tgt.CustomerID = src.CustomerID
-- ... rest as before
;
SourceModifiedOn > @LastSyncTime.-- Updates
UPDATE tgt
SET
tgt.CustomerName = src.CustomerName,
tgt.Email = src.Email,
tgt.ModifiedDate = src.SourceModifiedOn,
tgt.ModifiedBy = @UserName
FROM dbo.Customer tgt
JOIN stg.Customer_Delta src
ON tgt.CustomerID = src.CustomerID
WHERE src.BatchID = @BatchID
AND src.OperationType = 'U';
-- Inserts
INSERT dbo.Customer (CustomerID, CustomerName, Email, IsDeleted,
CreatedDate, ModifiedDate, CreatedBy, ModifiedBy)
SELECT
src.CustomerID,
src.CustomerName,
src.Email,
0,
src.SourceModifiedOn,
src.SourceModifiedOn,
@UserName,
@UserName
FROM stg.Customer_Delta src
LEFT JOIN dbo.Customer tgt
ON tgt.CustomerID = src.CustomerID
WHERE src.BatchID = @BatchID
AND src.OperationType IN ('I','U')
AND tgt.CustomerID IS NULL;
Change Tracking is a lightweight feature that tells you which rows changed since a given version. It’s ideal for driving incremental loads that feed Power BI/Fabric.
At database level:
ALTER DATABASE YourDb
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 7 DAYS, AUTO_CLEANUP = ON);
At table level:
ALTER TABLE dbo.Customer
ENABLE CHANGE_TRACKING
WITH (TRACK_COLUMNS_UPDATED = ON);
Maintain a control table for last sync version:
CREATE TABLE dbo.ETL_Control
(
ProcessName SYSNAME NOT NULL PRIMARY KEY,
LastSyncVersion BIGINT NOT NULL,
LastRunTime DATETIME2(3) NOT NULL
);
Example stored procedure to pull changes:
CREATE OR ALTER PROCEDURE etl.Customer_ExtractChanges
AS
BEGIN
SET NOCOUNT ON;
DECLARE @LastVersion BIGINT;
DECLARE @CurrentVersion BIGINT = CHANGE_TRACKING_CURRENT_VERSION();
SELECT @LastVersion = LastSyncVersion
FROM dbo.ETL_Control
WHERE ProcessName = 'Customer';
IF @LastVersion IS NULL
SET @LastVersion = 0; -- first run
-- Extract changes
SELECT
ct.CustomerID,
c.CustomerName,
c.Email,
CASE
WHEN c.CustomerID IS NULL THEN 'D'
WHEN ct.SYS_CHANGE_OPERATION = 'I' THEN 'I'
ELSE 'U'
END AS OperationType,
ISNULL(c.ModifiedDate, SYSUTCDATETIME()) AS SourceModifiedOn
FROM CHANGETABLE (CHANGES dbo.Customer, @LastVersion) AS ct
LEFT JOIN dbo.Customer c
ON c.CustomerID = ct.CustomerID;
-- Update control
MERGE dbo.ETL_Control AS tgt
USING (SELECT 'Customer' AS ProcessName, @CurrentVersion AS LastSyncVersion) AS src
ON tgt.ProcessName = src.ProcessName
WHEN MATCHED THEN
UPDATE SET
LastSyncVersion = src.LastSyncVersion,
LastRunTime = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ProcessName, LastSyncVersion, LastRunTime)
VALUES (src.ProcessName, src.LastSyncVersion, SYSUTCDATETIME());
END;
You can load this result into stg.Customer_Delta and then run your MERGE.
Change Tracking is usually better than naive timestamp filters when:
Timestamps are simpler when:
A common pattern:
Example parameterized query for Power BI:
SELECT
f.DateKey,
f.CustomerID,
f.SalesAmount
FROM dbo.FactSales f
WHERE f.DateKey >= @RangeStart
AND f.DateKey < @RangeEnd;
Power BI will substitute @RangeStart and @RangeEnd during refresh.
In Fabric, your MS-SQL patterns still apply, but:
Keep the same principles:
MERGE is powerful, but you need guardrails. Wrap it in stored procedures with explicit error handling.
CREATE TABLE dbo.ETL_Log
(
LogID INT IDENTITY(1,1) PRIMARY KEY,
ProcessName SYSNAME,
BatchID UNIQUEIDENTIFIER NULL,
StepName NVARCHAR(200),
Status NVARCHAR(20), -- 'START','SUCCESS','FAIL'
Message NVARCHAR(4000),
ErrorNumber INT NULL,
ErrorLine INT NULL,
LoggedAt DATETIME2(3) NOT NULL DEFAULT (SYSUTCDATETIME())
);
Stored procedure with logging:
CREATE OR ALTER PROCEDURE etl.Customer_LoadIncremental
@BatchID UNIQUEIDENTIFIER,
@UserName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
INSERT dbo.ETL_Log (ProcessName, BatchID, StepName, Status, Message)
VALUES ('Customer', @BatchID, 'MERGE', 'START', 'Starting Customer MERGE');
BEGIN TRAN;
-- MERGE or separate INSERT/UPDATE as shown earlier
-- ...
COMMIT TRAN;
INSERT dbo.ETL_Log (ProcessName, BatchID, StepName, Status, Message)
VALUES ('Customer', @BatchID, 'MERGE', 'SUCCESS', 'Customer MERGE completed successfully');
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRAN;
INSERT dbo.ETL_Log
(
ProcessName, BatchID, StepName, Status, Message,
ErrorNumber, ErrorLine
)
VALUES
(
'Customer', @BatchID, 'MERGE', 'FAIL', ERROR_MESSAGE(),
ERROR_NUMBER(), ERROR_LINE()
);
THROW; -- re-throw to surface error to caller (e.g. Fabric pipeline)
END CATCH;
END;
ETL_Log in a simple Power BI report.Status = 'FAIL' for quick triage.Don’t try to redesign your whole warehouse at once. Pick one critical table that feeds Power BI or Fabric and:
Once that path is stable and observable, you can replicate the pattern across other tables with far less risk and far more confidence.
MS-SQL
New
Next Batches Now Live
Power BI
SQL
Power Apps
Power Automate
Microsoft Fabrics
Azure Data Engineering