En övergripande guide för hur vi bygger en datapipeline med medallion-arkitektur (Bronze → Silver → Gold) i Databricks.
Raw data → Bronze (rå inläsning) → Silver (rensad OBT) → Gold (aggregerat, klart för analys)
Varje lager hanteras som ett eget schema i en gemensam catalog, och data flödar via Databricks Pipelines (DLT).
Innan något annat behöver du sätta upp strukturen i Databricks. Det görs antingen via ett Notebook eller direkt i Catalog Explorer.
Ordning:
- Skapa en catalog
- Skapa tre scheman:
bronze,silverochgold - Skapa ett volume för rådata under
default-schemat
CREATE CATALOG IF NOT EXISTS <ditt_catalog_namn>;
USE CATALOG <ditt_catalog_namn>;
CREATE SCHEMA IF NOT EXISTS bronze;
CREATE SCHEMA IF NOT EXISTS silver;
CREATE SCHEMA IF NOT EXISTS gold;
CREATE VOLUME IF NOT EXISTS <ditt_catalog_namn>.default.raw;Skapa sedan de mappar du behöver inuti volymen:
dbutils.fs.mkdirs("/Volumes/<catalog>/default/raw/data")
dbutils.fs.fs.mkdirs("/Volumes/<catalog>/default/raw/logs")
dbutils.fs.mkdirs("/Volumes/<catalog>/default/raw/metadata")Målet med Bronze är att läsa in data precis som det är, utan transformationer.
Ordning:
- Ladda upp råfilen till
/Volumes/<catalog>/default/raw/data/ - Skapa en DLT-pipeline som pekar på den mappen
- Kör pipeline – data landar i
bronze-schemat
Ingen rensning sker här. Bronze är en spegelbild av källan.
Målet med Silver är att ha en ren, välstrukturerad tabell redo för analys. Vi gör en extensiv EDA (Exploratory Data Analysis) för att förstå vad som behöver åtgärdas.
Ordning:
Läs in tabellen och identifiera problem: nullvärden, felaktiga format, konstiga kolumnnamn, osv.
Vanliga åtgärder i det här steget:
- Kolumnnamn – konvertera till
snake_case(t.ex.Customer Email→customer_email) - Datumformat – parse strängar till
timestampmed rätt format - Nullvärden – ersätt med standardvärde (t.ex.
"unknown"eller"-") - Felaktiga värden – mappa om (t.ex.
"EE. UU."→"United States") - Avrundning – runda av decimalkolumner till 2 decimaler
- Ta bort kolumner – droppa känsliga eller irrelevanta kolumner (t.ex. lösenord, e-post)
Rensningen görs med kedjade withColumn-anrop. Exempel på hur flera vanliga fall hanteras i ett svep:
from pyspark.sql.functions import to_timestamp, col, coalesce, lit, when, round
df_clean = (
df.withColumn(
"shipping_date", to_timestamp("shipping_date_(dateorders)", "M/d/yyyy H:m")
)
.withColumn(
"order_zipcode", coalesce(col("order_zipcode").cast("string"), lit("unknown"))
)
.withColumn(
"customer_country",
when(col("customer_country") == "EE. UU.", "United States").otherwise(
col("customer_country")
),
)
.withColumn("product_price", round(col("product_price"), 2))
.drop("customer_email", "customer_password", "product_description")
)Rensningslogiken skrivs som en funktion dekorerad med @dp.table(...) och läggs in i pipelinen. Funktionen läser från Bronze via SELECT * FROM STREAM ... och returnerar den rensade DataFramen. Se till att ange table_properties för Delta Column Mapping om du döper om kolumner.
from pyspark import pipelines as dp
from pyspark.sql.functions import coalesce, lit, when, col, to_timestamp, round
from utils.utils import rename_column_to_snake_case
@dp.table(
name="<catalog>.silver.supply_chain_obt",
comment="Cleaned supply chain data",
table_properties={
"delta.columnMapping.mode": "name",
"delta.minReaderVersion": "2",
"delta.minWriterVersion": "5",
},
)
def cleaned_supply_chain():
df = rename_column_to_snake_case(
spark.sql("SELECT * FROM STREAM <catalog>.bronze.raw_supply_chain")
)
return (
df.withColumn("shipping_date", to_timestamp(col("shipping_date_(dateorders)"), "M/d/yyyy H:mm"))
.withColumn("order_zipcode", coalesce(col("order_zipcode").cast("string"), lit("unknown")))
.withColumn(
"customer_country",
when(col("customer_country") == "EE. UU.", "United States").otherwise(col("customer_country")),
)
.withColumn("product_price", round(col("product_price"), 2))
.drop("customer_email", "customer_password", "shipping_date_(dateorders)")
)Kör sedan om pipelinen – Silver-tabellen uppdateras automatiskt när Bronze får ny data.
Gold-lagret byggs ovanpå Silver och innehåller faktatabeller, dimensionstabeller och marts anpassade för analys och dashboards. Strukturen planeras med dimensionsmodellering (t.ex. i dbdiagram) innan tabellerna skapas.
Ordning:
- Modellera strukturen (faktatabell + dimensionstabeller)
- Skapa faktatabell som Streaming Table
- Skapa dimensionstabeller som Materialized Views
- Testa joins i notebook
- Skapa marts för specifika målgrupper/filter
Faktatabellen läser från Silver via streaming och beräknar härledda kolumner (t.ex. total_amount) som medvetet utelämnats i Silver-lagret.
CREATE OR REFRESH STREAMING TABLE supply_chain_live.gold.fct_orderlines
COMMENT "Fact table - gold layer" AS
SELECT
order_item_id,
order_id,
customer_id,
product_card_id AS product_id,
date_format(order_date, 'yyyyMMddHHmm')::bigint AS order_datetime_id,
ROUND(order_item_product_price, 2) AS order_item_price,
order_item_quantity AS quantity,
order_item_discount_rate AS discount_rate,
ROUND(order_item_price * quantity * (1 - discount_rate), 2) AS total_amount
FROM STREAM supply_chain_live.silver.supply_chain_obt;Dimensionstabeller skapas som Materialized Views. Använd MAX_BY för att deduplicera och alltid behålla det senaste värdet per nyckel.
CREATE OR REFRESH MATERIALIZED VIEW supply_chain_live.gold.dim_product
COMMENT "Dim products deduplicated - gold layer" AS
SELECT
product_card_id AS product_id,
MAX_BY(product_name, order_date) AS product_name,
MAX_BY(product_price, order_date) AS product_price
FROM supply_chain_live.silver.supply_chain_obt
GROUP BY product_id
ORDER BY product_id;Upprepa samma mönster för övriga dimensioner (t.ex. dim_customer, dim_date).
Verifiera att faktatabell och dimensioner går att joina korrekt innan du skapar marts.
En mart är en Materialized View som kombinerar faktatabell och dimensioner och filtrerar för ett specifikt användningsfall.
CREATE OR REFRESH MATERIALIZED VIEW supply_chain_live.gold.mart_puerto_rico
COMMENT "Mart for the Puerto Rico stores - gold layer" AS
SELECT
ol.total_amount,
c.first_name,
c.last_name,
c.country,
p.product_name,
p.product_price
FROM fct_orderlines ol
LEFT JOIN dim_customer c ON ol.customer_id = c.customer_id
LEFT JOIN dim_product p ON ol.product_id = p.product_id
WHERE c.country = 'Puerto Rico';- Kör alltid EDA innan du skriver Silver-logiken – annars missar du kantfall
- Lägg hjälpfunktioner (t.ex.
rename_column_to_snake_case) i en separatutils/utils.pyoch importera dem i pipelinen - Använd
coalesceistället för att direkt droppa rader med null – bevara data så länge som möjligt - Bronze ska aldrig modifieras retroaktivt; rätta fel i Silver eller Gold istället