← Blog

Published · By Henry Palma

Build Your First Delta Lake Table with PySpark on Databricks Free Edition

Create a managed Delta table on Databricks Free Edition, read it with SQL and PySpark, update a row, and time travel to the version before the update.

You can build a working Delta Lake table on Databricks Free Edition in one short notebook: create a managed table, read it with SQL and PySpark, update one row, then read the version from before that update with time travel. This post walks through the same steps as the video, using three fictional customer rows.

<iframe width="560" height="315" src="https://www.youtube.com/embed/FeQso1KkBPY" title="Delta Lake Tutorial: Build Your First Delta Table with PySpark on Databricks (Free)" frameborder="0" allowfullscreen></iframe>

Watch on YouTube

The full notebook, a Databricks source .py copy and a printable cheat sheet are in the lesson folder on GitHub.

Set up the notebook

Use a Python notebook attached to Serverless compute. From the workspace home page choose New > Notebook, give it a title, pick Python and Serverless. You can also import firstdeltalake_table.ipynb from the repo through Workspace > Import.

Run the cells top to bottom in one session. The time travel step depends on a Python variable set earlier in the notebook.

Create a managed Delta table in its own schema

The notebook reads your current catalog at runtime and creates everything under one demo schema, bytedeltademo_20261106. On Free Edition the catalog is often workspace.

DEMO_SCHEMA = "byte_delta_demo_20261106"
CATALOG = spark.sql(
    "SELECT current_catalog() AS catalog"
).collect()[0]["catalog"]
schema_fqn = f"`{CATALOG}`.`{DEMO_SCHEMA}`"
customers = f"{schema_fqn}.`customers`"

spark.sql(f"CREATE SCHEMA IF NOT EXISTS {schemafqn}") spark.sql(f""" CREATE TABLE IF NOT EXISTS {customers} ( customerid INT, name STRING, city STRING, signupdate DATE, datanote STRING ) USING DELTA """) ```

This is a managed table. There is no LOCATION, no /mnt path and no cloud storage to configure. The notebook never runs DROP or CREATE OR REPLACE. A re-run deletes the rows in the demo table only and inserts the sample rows again.

Load and read the sample rows

Three fictional rows go in, each with a data_note of SAMPLE — fictional:

| id | name | city | |---|---|---| | 1 | Ada Lovelace | London | | 2 | Alan Turing | Manchester | | 3 | Grace Hopper | New York |

You can read the same table with SQL or with the DataFrame API:

display(spark.sql(f"""
SELECT customer_id, name, city, signup_date, data_note
FROM {customers}
ORDER BY customer_id
"""))
display(spark.table(customers).orderBy("customer_id"))
display(spark.table(customers).filter("city = 'London'"))  # Ada

Both reads return the same three rows. The filter returns only Ada Lovelace.

Capture the version before you change anything

Delta Lake records a new table version for every commit. Creating the table, deleting rows, inserting rows and updating a row are all separate commits, so the version numbers move forward every time you re-run the notebook.

That is why the lesson does not type a fixed number like VERSION AS OF 1. Instead it reads the latest version from DESCRIBE HISTORY and stores it in a Python variable:

hist = spark.sql(f"DESCRIBE HISTORY {customers}")
pre_update_version = int(
    hist.selectExpr("max(version) AS v").collect()[0]["v"]
)

The variable lives in the notebook session. If serverless compute restarts, run the notebook again from the top.

Update one row

The update touches only the fictional Alan Turing row:

spark.sql(f"""
UPDATE {customers}
SET city = 'Cambridge'
WHERE customer_id = 2
  AND data_note LIKE 'SAMPLE%'
""")

After it runs, Alan's city is Cambridge. Ada and Grace are unchanged.

Read history and time travel

DESCRIBE HISTORY now includes an UPDATE operation. Use the captured version to read the table as it was before that update, first in SQL:

display(spark.sql(f"""
SELECT customer_id, name, city, data_note
FROM {customers} VERSION AS OF {pre_update_version}
ORDER BY customer_id
"""))

Then in PySpark:

df_prior = (
    spark.read
    .format("delta")
    .option("versionAsOf", str(pre_update_version))
    .table(customers)
)
display(df_prior.orderBy("customer_id"))

Both reads show Alan in Manchester. The current table still shows Cambridge. Time travel reads an older version. It does not undo the update.

Practice challenge

The last section creates a second table, orders, in the same demo schema with three fictional orders and joins it to customers. Ada appears twice (orders 1001 and 1003), Alan once (1002), and Grace not at all because she has no order.

Your stretch goal: update one order amount, capture that table's pre-update version in a Python variable first, then compare the current table with VERSION AS OF that version.

Key takeaways

  • Managed Delta tables on Free Edition need no storage setup.
  • Every commit adds a version, so capture max(version) from DESCRIBE HISTORY instead of hard-coding a number.
  • VERSION AS OF in SQL and versionAsOf in PySpark read the same older version.
  • Keep demo work in its own schema and avoid DROP and CREATE OR REPLACE while you learn.

Have an idea, question, or feedback?

We'd genuinely like to hear from you.