Featured image of post SQLFrame: Migrate PySpark to DuckDB Without Code Changes

SQLFrame: Migrate PySpark to DuckDB Without Code Changes

SQLFrame lets you migrate PySpark apps to DuckDB without changing a single line of code, enjoying the blazing-fast performance of a local analytical engine.

SQLFrame: Migrate PySpark to DuckDB Without Code Changes

Introduction

In the data engineering world, PySpark has been the de facto standard for large-scale data processing. However, when you only need to handle medium-sized datasets (a few GB to tens of GB), or want to quickly prototype and validate analysis locally, spinning up a Spark cluster feels like overkill.

SQLFrame was built precisely to solve this problem — it lets you seamlessly migrate PySpark applications to DuckDB without modifying any code. This means all your existing PySpark DataFrame operation code can be switched to run on DuckDB, delivering faster execution speeds and lower resource consumption.

This approach was featured prominently at DuckCon #7 in June 2026 by Nicolas Renkamp from Merck KGaA, generating significant interest among attendees.

Architecture Diagram

What is SQLFrame?

SQLFrame is a Python library that implements a compatibility layer for the PySpark DataFrame API. Its core principle is straightforward:

  1. API Compatibility: Provides a DataFrame API identical to PySpark’s
  2. Backend Swap: Uses DuckDB as the execution engine underneath
  3. Zero Modifications: Only import statements and connection configs change — business logic remains untouched
# Original PySpark code
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
df = spark.read.parquet("data/")

# Migrated SQLFrame code
from sqlframe.duckdb import DuckDBSession
session = DuckDBSession()
df = session.read.parquet("data/")

As you can see, the only change is replacing SparkSession with DuckDBSession. All other code stays exactly the same.

Installation and Setup

Environment Preparation

pip install sqlframe-duckdb duckdb

Or add to your project dependencies:

[project]
dependencies = [
    "sqlframe-duckdb>=1.0.0",
    "duckdb>=1.0.0",
]

Basic Usage

from sqlframe.duckdb import DuckDBSession
import duckdb

# Create a DuckDB Session
session = DuckDBSession()

# Read data
df = session.read.csv(
    "sales_data.csv",
    header=True,
    inferSchema=True
)

# Display results
df.show(5)

Hands-On Example: E-Commerce Sales Analysis

Let’s walk through a complete e-commerce sales analysis scenario using SQLFrame, from data loading to advanced analytics.

Step 1: Load Data

from sqlframe.duckdb import DuckDBSession
from sqlframe.duckdb import connection

session = DuckDBSession()

# Connect to DuckDB (supports both in-memory and file-based databases)
conn = connection.DuckDBConnection()
session._conn = conn

# Simulate loading multiple CSV files
orders = session.read.csv(
    "data/orders/*.csv",
    header=True,
    inferSchema=True
)

customers = session.read.csv(
    "data/customers.csv",
    header=True,
    inferSchema=True
)

products = session.read.csv(
    "data/products.csv",
    header=True,
    inferSchema=True
)

Step 2: Data Cleaning

# Remove null values
orders_clean = orders.dropna()

# Convert date formats
from pyspark.sql.functions import col, to_date, year, month

orders_clean = orders_clean.withColumn(
    "order_date",
    to_date(col("order_date"), "yyyy-MM-dd")
)

# Add computed columns
orders_clean = orders_clean.withColumn(
    "year",
    year(col("order_date"))
).withColumn(
    "month",
    month(col("order_date"))
)

Step 3: Multi-Table Join Analysis

# Join orders, customers, and products
joined_df = (
    orders_clean
    .join(customers, "customer_id", "inner")
    .join(products, "product_id", "inner")
)

# Monthly sales aggregation
monthly_sales = (
    joined_df
    .groupBy("year", "month")
    .agg(
        {"quantity": "sum", "amount": "sum"}
    )
    .orderBy("year", "month")
)

monthly_sales.show()

Sample output:

+----+-----+--------+-------+
|year|month|sum_qty |sum_amt|
+----+-----+--------+-------+
|2024|   1 |   15234| 892341|
|2024|   2 |   14892| 876523|
|2024|   3 |   16789| 945672|
|2024|   4 |   15678| 923456|
+----+-----+--------+-------+

Step 4: Advanced Analytics

from pyspark.sql.window import Window
from pyspark.sql.functions import rank, sum as spark_sum

# Calculate cumulative spending rank per customer
window_spec = Window.partitionBy("year").orderBy(
    spark_sum("amount").over(
        Window.partitionBy("customer_id")
    ).desc()
)

customer_ranking = joined_df.withColumn(
    "rank",
    rank().over(window_spec)
).filter(col("rank") <= 10)

customer_ranking.select(
    "customer_id", "name", "year", "total_amount"
).show()

Comparison with Traditional Approaches

FeaturePySpark StandaloneSQLFrame + DuckDBSpark on YARN/K8s
Installation ComplexityHigh (cluster config needed)Low (pip install)Very High
Startup TimeMinutesMillisecondsMinutes
Memory FootprintGB~TB scaleMB~GB scaleGB~TB scale
Suitable Data Scale>100GB<50GB>100GB
Local Development ExperiencePoorExcellentPoor
Runs LocallyNoYesNo
Distributed ScalingNativeLimitedNative

Performance Benchmark

In a typical e-commerce analysis scenario (~5 million order rows, 3-table join aggregation):

import time
from sqlframe.duckdb import DuckDBSession

session = DuckDBSession()

# Read data
start = time.time()
orders = session.read.csv("data/orders/*.csv", header=True)
print(f"Read time: {time.time() - start:.2f}s")

# Complex query
start = time.time()
result = (
    orders
    .filter(col("amount") > 100)
    .groupBy("category")
    .agg({"amount": "avg", "quantity": "sum"})
    .orderBy(col("avg_amount").desc())
)
result.show()
print(f"Query time: {time.time() - start:.2f}s")

Typical output:

Read time: 0.85s
Query time: 0.32s

Compared to equivalent Spark Standalone mode (startup + execution ~45 seconds), SQLFrame + DuckDB is over 100x faster in development iteration scenarios.

Caveats and Limitations

1. Data Scale Limits

DuckDB is a single-process analytical engine suitable for local machines. Performance degrades significantly when data exceeds available memory. Recommendations:

  • < 10GB: Runs perfectly, no concerns
  • 10-50GB: Ensure sufficient memory (64GB+ recommended)
  • > 50GB: Consider Spark or other distributed solutions

2. API Compatibility

While SQLFrame covers the most commonly used PySpark APIs, note the following:

  • Custom UDFs may need adaptation
  • Some advanced Spark functions (e.g., MLlib) are unavailable
  • Streaming functionality is not supported

3. Transaction Support

DuckDB supports ACID transactions, but when writing to CSV/Parquet, pay attention to atomicity issues. Use DuckDB’s native file format or transaction-supporting storage formats for critical writes.

Monetization Advice

For Enterprise Data Teams

If you’re considering reducing Spark infrastructure costs, SQLFrame + DuckDB can help you:

  1. Reduce Cloud Spend: Migrate 80% of small-to-medium ETL tasks from Spark to local DuckDB, saving thousands of dollars annually on cloud resources
  2. Accelerate Development Cycles: Data engineers can prototype and test locally without waiting for cluster scheduling
  3. Lower Operational Burden: No need to maintain Spark clusters, reducing DevOps overhead

For Individual Developers and Data Scientists

  1. Build Data Products: Leverage DuckDB’s embedded nature to embed analytical capabilities into web apps, CLI tools, or desktop applications
  2. Rapid Prototyping: When pitching new projects or validating ideas, skip complex Spark environments and start analyzing within minutes
  3. Educational Use: As a teaching tool, students can learn big data processing concepts on their own laptops

For Consulting Firms

  1. Faster Delivery: When advising clients on data migration and modernization, SQLFrame enables PoCs in days rather than weeks
  2. Hybrid Architecture Design: Help clients design “Spark + DuckDB” hybrid architectures — Spark for massive-scale data, DuckDB for daily analytics and reporting

Conclusion

SQLFrame offers an elegant fallback path for PySpark users — when you don’t need distributed processing power, DuckDB delivers faster development cycles and superior local performance. For most medium-scale data analysis scenarios, it’s a compelling alternative worth considering.

As demonstrated at DuckCon #7, many enterprises are already using this pattern in production, achieving multi-fold improvements in development efficiency.


Original source: https://duckdb.org/2026/06/24/duckcon7/sessions/sqlframe-migration

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

Built with Hugo
Theme Stack designed by Jimmy

⚠️ This site is an independent community project, not affiliated with, endorsed by, or sponsored by the DuckDB Foundation or official DuckDB project.

"DuckDB" is a registered trademark of the DuckDB Foundation. This site uses the name solely for factual description purposes.

All content is for educational and community promotion purposes only and does not constitute any commercial service.