SQL Server (T-SQL) to Databricks: Converting Stored Procedures and MERGE
SQL Server migrations to Databricks are usually driven by scale and cost — the analytics workload has outgrown the box. The conversion work is mostly about translating procedural T-SQL into set-based Spark SQL or PySpark, and knowing which T-SQL idioms have no Spark equivalent.
MERGE converts cleanly — mostly
Databricks SQL supports MERGE INTO on Delta tables, so T-SQL merges map over well:
MERGE INTO target t
USING source s ON t.id = s.id
WHEN MATCHED THEN UPDATE SET ...
WHEN NOT MATCHED THEN INSERT ...
Two things to check: T-SQL's WHEN NOT MATCHED BY SOURCE (delete rows missing from source) is supported in Databricks but easy to drop in a hand conversion — losing it turns a full-sync into an append. And OUTPUT clauses (capturing affected rows) have no direct equivalent; you rebuild that with a separate read of the change data.
Temp tables and table variables
#temp tables and @table variables become either Spark temp views (CREATE OR REPLACE TEMP VIEW) or persisted Delta tables, depending on whether they're reused across statements. A @table variable inside a loop usually signals row-by-row logic that should be rewritten set-based.
The T-SQL that doesn't have a home
- **Cursors** (`DECLARE CURSOR ... FETCH`) — almost always rewrite as a set-based operation. A cursor converted literally to a Python loop over a collected DataFrame will run, but it defeats the purpose of moving to Spark.
- **`sp_executesql` / dynamic SQL** — becomes parameterized Spark SQL built in PySpark; review carefully for injection and for logic that can't be known until runtime.
- **`TRY...CATCH`** — becomes Python `try/except` around the notebook cells.
- **Scalar UDFs** — T-SQL scalar functions called per-row are a performance trap; convert to Spark SQL expressions or `pandas_udf`, not row-wise Python.
Syntax translations
- `ISNULL(x, y)` → `COALESCE(x, y)` (Spark has no `ISNULL` with two args).
- `GETDATE()` → `current_timestamp()`; `DATEADD`/`DATEDIFF` → `date_add`/`datediff` (argument order differs — verify).
- `TOP n` → `LIMIT n`; `TOP n WITH TIES` → `QUALIFY`/window.
- `CHARINDEX` → `locate`; `LEN` → `length`; `+` string concat → `concat` (Spark won't concat strings with `+`).
- `[bracketed identifiers]` → backticks.
- `CONVERT(type, x, style)` date styles → explicit `to_date`/`date_format` with a format string.
Where results quietly diverge
The high-risk conversions — a dropped WHEN NOT MATCHED BY SOURCE, DATEDIFF argument-order flips, implicit string-to-number coercion differences — all produce runnable Spark code that returns different data. Validate by running both against the same sample and diffing the output, not by reading the notebook.
Attaching that verification verdict to each converted procedure is the problem [JarvisX](/) is built around.
---
**Try it now:** run a [SQL Server → Databricks conversion](/convert/sqlserver-to-databricks) free in the browser — no login for SQL pairs.
Related Publications
Teradata to Databricks SQL Migration – Common Pitfalls and Fixes
Teradata to Databricks migration is one of the most complex SQL modernization journeys because Teradata SQL contains many platform-specific behaviors that do not directly translate.
SAS to PySpark Migration: DATA Steps, PROC SQL, and Macros
Migrating SAS to PySpark on Databricks — DATA steps, PROC SQL, macros, and the SAS behaviors that need explicit handling in Spark.
Accelerating Data Modernization: Leveraging JarvisFlow for Seamless ETL to Airflow Transitions
In the rapidly evolving landscape of data management, transitioning from legacy ETL systems to modern orchestration tools like Apache Airflow is a critical step for many organizations. This FAQ-style guide explores how J
