A SAS to Databricks migration rebuilds SAS programs, datasets, macros, schedules, and models as PySpark and Spark SQL notebooks, Delta tables in Unity Catalog, Lakeflow Jobs, and MLflow-tracked models. It is a re-implementation, not a lift-and-shift, because SAS processes one observation at a time on a single server, while Spark works on distributed sets with no implicit order. Databricks' Lakebridge added SAS support in v0.14.0 through Switch, its LLM-based converter, which is still experimental, and commercial converters offer more automation, but every converted program must be reconciled against SAS output. The quiet risks are semantic: SAS missing values, 1960-based dates, MERGE behavior, and trailing-blank string comparisons all differ from Spark.
SAS has powered analysis, data preparation, and regulatory reporting in banks, insurers, pharma, and healthcare for decades, and many of those estates now hold thousands of programs, nested macros, and nightly job flows. This page covers why teams move, how SAS concepts map to Databricks, how to convert DATA steps without changing results, what Lakebridge does for SAS today, and how to run both platforms side by side. For the services side, see our Databricks migration services overview, and for every legacy ETL tool at once, our ETL migration to Databricks article.
| SAS | Databricks | Note |
|---|---|---|
| DATA step | PySpark DataFrame transformations or Spark SQL | Row-by-row logic becomes set-based operations |
| PROC SQL | Spark SQL (Databricks SQL) | Usually near-direct, with attention to SAS-specific functions and remerging |
| BY-groups, FIRST., LAST., and RETAIN | Window functions and grouped aggregations | Needs an explicit ordering |
| PROC MEANS, SUMMARY, FREQ, and SORT | groupBy aggregations, pivots, and orderBy | PROC SORT keeps ties in input order by default, and Spark does not |
| Macros and macro variables | Python functions, notebook widgets, and job parameters | Restructure nested macros instead of copying them |
| SAS datasets (.sas7bdat) and LIBNAME | Delta tables in Unity Catalog, as catalog.schema.table | Libraries map to catalogs and schemas |
| Formats and informats | Lookup tables, CASE expressions, and typed casts | Format catalogs need their own inventory |
| SAS/STAT, SAS/ETS, and Enterprise Miner models | Spark MLlib, scikit-learn, statsmodels, and MLflow | Defaults differ, so reconcile outputs |
| SAS/ACCESS engines and file imports | Lakehouse Federation, JDBC connectors, and Auto Loader | Connections inventoried per library |
| DI Studio and Enterprise Guide flows | Lakeflow Jobs and declarative pipelines | Dependencies rebuilt explicitly |
| SAS metadata security | Unity Catalog grants, row filters, and column masks | See our article on migrating row-level security |
| Schedules (LSF, Schedule Manager, cron) | Lakeflow Jobs schedules and triggers | Retries and alerts defined per task |
DI Studio flows overlap with other ETL tools, so our legacy systems to Databricks, Informatica to Databricks, and ETL pipeline automation with Python articles cover the neighboring patterns.
A DATA step reads one observation at a time and carries state between rows through RETAIN, FIRST. and LAST. variables, and sequential MERGE. Spark has no implicit row order, so this logic is re-expressed with window functions partitioned and ordered by the BY variables, explicit joins, and grouped aggregations. Array processing and DO loops typically become column expressions, while PROC SQL usually converts almost directly. The same refactoring mindset applies as in our PL/SQL conversion article: a literal translation of procedural logic is slow or wrong, and the rewrite is where correctness and performance come from.
The conversion has three rules. First, state every ordering assumption that SAS got for free from sorted input. Second, decide each missing-value rule deliberately instead of inheriting Spark's NULL logic. Third, test the edge rows, such as the first and last observation in each group, because that is where RETAIN and FIRST. logic breaks.
A DATA step that keeps a running total per customer and flags each customer's first and last transaction is typical SAS code. This is an illustrative example, not a client engagement.
proc sort data=work.txn;
by cust_id txn_date txn_id;
run;
data work.txn_flags;
set work.txn;
by cust_id;
retain running_total 0;
if first.cust_id then running_total = 0;
running_total + amount;
is_first = first.cust_id;
is_last = last.cust_id;
run;
SELECT
cust_id,
txn_id,
txn_date,
amount,
COALESCE(
SUM(amount) OVER (
PARTITION BY cust_id
ORDER BY txn_date, txn_id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
), 0) AS running_total,
ROW_NUMBER() OVER (PARTITION BY cust_id ORDER BY txn_date, txn_id) = 1 AS is_first,
ROW_NUMBER() OVER (PARTITION BY cust_id ORDER BY txn_date DESC, txn_id DESC) = 1 AS is_last
FROM silver.txn;
Two details matter. The SAS sum statement starts from 0 and ignores missing amounts, so a customer whose first amount is missing still shows a running total of 0, while a window SUM over only NULL values returns NULL, which is why the SQL wraps it in COALESCE. And PROC SORT keeps tied rows in their input order by default, while Spark does not, so if two rows can tie on date and transaction ID, carry an explicit sequence column into the ordering. Please test this example in a Databricks workspace before adapting it.
| SAS Behavior | What Changes on Databricks | Safeguard |
|---|---|---|
| Numeric missing values are smaller than any number and compare as a value | Spark NULL makes comparisons unknown, so filters, sorts, and joins change | Decide each rule, and add IS NULL handling or COALESCE where SAS behavior must hold |
| Special missing values such as .A to .Z | Spark has a single NULL | Keep the codes in a separate column if business rules use them |
| Dates count days, and datetimes count seconds, from January 1, 1960 | Spark dates and timestamps use a different epoch | Convert explicitly, and test boundary dates and datetimes |
| Every number is stored as an 8-byte floating-point value | Mapped types can change precision and rounding | Map to DECIMAL or DOUBLE per column and reconcile totals |
| MERGE with BY on many-to-many keys does not create a Cartesian product | A SQL join does | Re-express the logic explicitly and test many-to-many keys |
| Character comparison pads the shorter value with trailing blanks | Spark compares strings exactly | Trim keys explicitly and test with real data |
| LAG and DIF queues update only when executed | Window lag() is evaluated for every row | Rebuild with an explicit window and compare, especially where LAG sits inside IF logic |
| PROC SORT keeps tied rows in input order by default | Spark gives no tie order without a rule | Carry an explicit sequence column |
| Formats and labels travel with datasets | They are not written to database tables by SAS/ACCESS | Keep formats as lookup tables and labels as column comments |
These are the differences behind outputs that look right and quietly are not, and the reason for column-level checks like those in our automated ETL testing with Python approach. The same set-based thinking appears in our comparison of traditional vs modern ETL.
Lakebridge is Databricks' free, open-source migration toolkit from Databricks Labs. Version 0.14.0 added SAS to the sources Switch can convert, and the pieces differ from the SQL stories:
| Tool | What It Does for SAS | Status and Limits |
|---|---|---|
| Switch (LLM-based) | A built-in prompt converts SAS programs to PySpark notebooks | Experimental, and output is LLM-generated, so it needs review and reconciliation |
| Custom Switch prompts | YAML prompts extend Switch to patterns specific to your estate | Needs engineering effort and testing |
| BladeBridge and Morpheus | Deterministic converters for supported SQL dialects, with no built-in SAS DATA step support | Useful for SQL sources, not for SAS programs |
| Commercial SAS converters | Parser-driven tools from partners, often advertising automation and row-level parity | Treat claims as vendor claims, and ask for a proof of conversion on your own code |
| DataTerrain's approach | Parses SAS programs and macros, maps them through an intermediate schema, generates PySpark and Spark SQL, and reconciles outputs | Complex macros, BY-group logic, and statistical models still need review |
Two practical points apply to Switch. It calls a model serving endpoint that you configure, by default a Claude model through Databricks' Foundation Model API, so review where your code is sent before running it on regulated programs. And all projects in the Databricks Labs GitHub account are provided as-is, with no service level agreements, so confirm support terms before building production timelines on it. Our Databricks migration services overview covers how tooling and services divide the work, and our SQL Server and SSIS, Oracle, and Teradata articles show how the same toolkit behaves on other sources.
SAS can read and write Databricks, so a migration does not need a big-bang cutover. According to SAS community library articles:
In practice, converted programs can write Delta tables while unconverted SAS programs keep reading them, and downstream consumers never notice the switch. The same coexistence pattern appears in our Alteryx to Databricks article, and the wider platform context is in data platform migration to Databricks.
| Phase | What Happens | Exit Criterion |
|---|---|---|
| Inventory | Catalog programs, macros, libraries, datasets, DI Studio jobs, Enterprise Guide projects, and schedules, using automated parsing of code and logs on large estates | A scored inventory with dependencies |
| Rationalization | Retire unused, duplicate, and one-off programs | An approved, smaller scope |
| Target design | Plan workspaces, catalogs, compute policies, networking, and a medallion layout | A signed-off design |
| Pilot | Convert a sample spanning simple, macro-heavy, and statistical programs, with a proof of conversion | A measured conversion rate and reconciled output |
| Data and code conversion | Load datasets into Delta tables, and convert DATA steps, PROC SQL, and macros in waves | Converted code passing tests |
| Models and orchestration | Rebuild statistical models in MLflow, and recreate job flows as Lakeflow Jobs | Models reconciled and schedules running |
| Parallel run and validation | Run SAS and Databricks side by side through a full reporting cycle, and reconcile row by row and aggregate by aggregate | Signed-off parity |
| Cutover and decommission | Repoint consumers, retire SAS workloads, and release licenses | No remaining SAS dependency |
For sequencing across waves, see our key checklist for BI modernization, data warehouse to Databricks migration article, and cloud migration to Databricks article. Reports built on SAS output follow the data, and our reports conversion service covers that layer.
Moving a SAS estate is easy to start and hard to finish, because the effort hides in nested macros, order-dependent DATA steps, and statistical results that must still match. DataTerrain brings 17+ years and 400+ clients in data and reporting modernization, with automated discovery, conversion support, and figure-for-figure validation against SAS output. Start with our ETL Solutions or the wider Databricks migration services page.
Databricks Migration Services | ETL Migration to Databricks | Legacy Systems to Databricks | Informatica to Databricks Migration | Alteryx to Databricks Migration | SQL Server and SSIS to Databricks | Oracle to Databricks Migration | Teradata to Databricks Migration | Data Warehouse to Databricks | Data Platform Migration to Databricks | Cloud Migration to Databricks | Databricks vs Snowflake | Snowflake Migration Services | Microsoft Fabric Migration Services | Legacy PL/SQL to Modern ETL | ETL Pipeline Automation with Python | Automating ETL Testing with Python | Migrating Row-Level Security | Key Checklist for Successful BI Modernization | ETL Solutions