JarvisX
Back to Engineering Blogs

SQL Server (T-SQL) to Databricks: Converting Stored Procedures and MERGE

JarvisX Team 2026-08-23 min read

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