| name | 02-merge-patterns |
| description | Provides production-grade patterns for Gold layer MERGE operations from Silver to Gold tables. Covers column mapping, schema evolution, SCD Type 1/2 patterns, fact table aggregation, and preventing variable naming conflicts with PySpark functions. Use when creating Gold layer MERGE operations, handling column name differences between Silver and Gold, implementing SCD Type 1/2 dimensions, aggregating fact tables, or troubleshooting MERGE errors. Triggers on "Gold merge", "MERGE operation", "upsert Gold", "SCD Type 1", "SCD Type 2", "fact table merge", "Silver to Gold", "column mapping", "schema evolution". |
| license | Apache-2.0 |
| clients | ["ide_cli","genie_code"] |
| bundle_resource | jobs |
| deploy_verb | bundle_deploy |
| deploy_note | Pattern consumed by the Gold merge jobs (notebook_task); deploy via `bundle deploy --target dev` (runDatabricksCli on Genie Code). MERGE src view must .select() every Gold column with lit(None) placeholders for YAML-only columns. |
| coverage | full |
| metadata | {"author":"prashanth subrahmanyam","version":"1.0.0","domain":"gold","role":"worker","pipeline_stage":4,"pipeline_stage_name":"gold-implementation","called_by":["gold-layer-setup"],"standalone":true,"last_verified":"2026-02-07","volatility":"low","upstream_sources":[]} |
Gold Layer MERGE Patterns
Core Principle: Schema-Aware Transformations
Gold layer merge operations read from Silver and must handle:
- Column name differences
- Data type transformations
- Business logic calculations
- SCD Type 2 tracking
Column Name Mapping Pattern
Problem: Column Names Differ Between Layers
Example: Silver has company_rcn, but Gold expects company_retail_control_number
❌ DON'T: Reference non-existent columns
updates_df = (
silver_df
.select(
"store_number",
"company_retail_control_number",
)
)
✅ DO: Map columns explicitly with withColumn
updates_df = (
silver_df
.withColumn("company_retail_control_number", col("company_rcn"))
.select(
"store_number",
"company_retail_control_number",
)
)
Variable Naming Conflicts
Problem: Import Conflicts with Local Variables
Critical Rule: NEVER name local variables the same as imported PySpark functions.
❌ DON'T: Shadow imported functions
from pyspark.sql.functions import count
def merge_data():
count = updates_df.count()
df.agg(count("*"))
✅ DO: Use descriptive variable names
from pyspark.sql.functions import count
def merge_data():
record_count = updates_df.count()
df.agg(count("*"))
Common PySpark Functions to Avoid as Variable Names
count → use record_count, row_count, num_records
sum → use total, sum_value, aggregated_sum
min → use min_value, minimum
max → use max_value, maximum
round → use rounded_value, result
filter → use filtered_df, subset
Merge Operation Patterns
SCD Type 1 (Overwrite)
Use for: Dimension tables where history doesn't matter
Template: See assets/templates/scd-type1-merge.py for complete pattern.
def merge_dim_product(spark: SparkSession, catalog: str, silver_schema: str, gold_schema: str):
"""Merge dim_product from Silver to Gold (SCD Type 1)."""
silver_table = f"{catalog}.{silver_schema}.silver_product_dim"
gold_table = f"{catalog}.{gold_schema}.dim_product"
silver_df = spark.table(silver_table)
updates_df = (
silver_df
.withColumn("product_key", col("upc_code"))
.withColumn("record_updated_timestamp", current_timestamp())
.select(
"product_key", "upc_code", "product_description",
"record_updated_timestamp"
)
)
delta_gold = DeltaTable.forName(spark, gold_table)
delta_gold.alias("target").merge(
updates_df.alias("source"),
"target.product_key = source.product_key"
).whenMatchedUpdateAll(
).whenNotMatchedInsertAll(
).execute()
record_count = updates_df.count()
print(f"✓ Merged {record_count} records into dim_product")
SCD Type 2 (Historical Tracking)
Use for: Dimension tables where you need to track changes over time
Template: See assets/templates/scd-type2-merge.py for complete pattern.
def merge_dim_store(spark: SparkSession, catalog: str, silver_schema: str, gold_schema: str):
"""Merge dim_store from Silver to Gold (SCD Type 2)."""
silver_table = f"{catalog}.{silver_schema}.silver_store_dim"
gold_table = f"{catalog}.{gold_schema}.dim_store"
silver_df = spark.table(silver_table)
updates_df = (
silver_df
.withColumn("store_key", md5(concat_ws("||", col("store_id"), col("processed_timestamp"))))
.withColumn("effective_from", col("processed_timestamp"))
.withColumn("effective_to", lit(None).cast("timestamp"))
.withColumn("is_current", lit(True))
.withColumn("store_status",
when(col("close_date").isNotNull(), "Closed").otherwise("Active"))
.withColumn("company_retail_control_number", col("company_rcn"))
.withColumn("record_created_timestamp", current_timestamp())
.withColumn("record_updated_timestamp", current_timestamp())
.select(
"store_key", "store_number", "store_name",
"company_retail_control_number",
"effective_from", "effective_to", "is_current",
)
)
delta_gold = DeltaTable.forName(spark, gold_table)
delta_gold.alias("target").merge(
updates_df.alias("source"),
"target.store_number = source.store_number AND target.is_current = true"
).whenMatchedUpdate(set={
"record_updated_timestamp": "source.record_updated_timestamp"
}).whenNotMatchedInsertAll(
).execute()
record_count = updates_df.count()
print(f"✓ Merged {record_count} records into dim_store")
Fact Table Aggregation
Use for: Pre-aggregated fact tables from transactional Silver data
Template: See assets/templates/fact-table-aggregation-merge.py for complete pattern.
def merge_fact_sales_daily(spark: SparkSession, catalog: str, silver_schema: str, gold_schema: str):
"""Merge fact_sales_daily from Silver to Gold."""
silver_table = f"{catalog}.{silver_schema}.silver_transactions"
gold_table = f"{catalog}.{gold_schema}.fact_sales_daily"
transactions = spark.table(silver_table)
daily_sales = (
transactions
.groupBy("store_number", "upc_code", "transaction_date")
.agg(
spark_sum(when(col("quantity_sold") > 0, col("final_sales_price")).otherwise(0)).alias("gross_revenue"),
spark_sum(col("final_sales_price")).alias("net_revenue"),
spark_sum(when(col("quantity_sold") > 0, col("quantity_sold")).otherwise(0)).alias("units_sold"),
count("*").alias("transaction_count"),
)
.withColumn("record_created_timestamp", current_timestamp())
.withColumn("record_updated_timestamp", current_timestamp())
)
delta_gold = DeltaTable.forName(spark, gold_table)
delta_gold.alias("target").merge(
daily_sales.alias("source"),
"""target.store_number = source.store_number
AND target.upc_code = source.upc_code
AND target.transaction_date = source.transaction_date"""
).whenMatchedUpdate(set={
"net_revenue": "source.net_revenue",
"units_sold": "source.units_sold",
"transaction_count": "source.transaction_count",
"record_updated_timestamp": "source.record_updated_timestamp"
}).whenNotMatchedInsertAll(
).execute()
record_count = daily_sales.count()
print(f"✓ Merged {record_count} records into fact_sales_daily")
Schema Evolution Handling
Data Type Changes
.withColumn("quantity_sold", col("quantity_sold").cast("bigint"))
.withColumn("price", col("price").cast("double"))
Adding Derived Columns
.withColumn("total_discount",
coalesce(col("multi_unit_discount"), lit(0)) +
coalesce(col("coupon_discount"), lit(0)) +
coalesce(col("loyalty_discount"), lit(0)))
Error Handling Pattern
def main():
"""Main entry point for Gold layer MERGE operations."""
catalog, silver_schema, gold_schema = get_parameters()
spark = SparkSession.builder.appName("Gold Layer MERGE").getOrCreate()
try:
merge_dim_store(spark, catalog, silver_schema, gold_schema)
merge_dim_product(spark, catalog, silver_schema, gold_schema)
merge_dim_date(spark, catalog, silver_schema, gold_schema)
merge_fact_sales_daily(spark, catalog, silver_schema, gold_schema)
merge_fact_inventory_snapshot(spark, catalog, silver_schema, gold_schema)
print("\n" + "=" * 80)
print("✓ Gold layer MERGE completed successfully!")
print("=" * 80)
except Exception as e:
print(f"\n❌ Error during Gold layer MERGE: {str(e)}")
raise
finally:
spark.stop()
Validation Checklist
Before deploying Gold merge scripts:
Common Errors and Solutions
Error: Column 'X' does not exist
Solution: Check Silver table schema. Add explicit column mapping if names differ.
Error: 'int' object is not callable
Solution: Variable name shadows a PySpark function. Rename the variable.
Error: Cartesian product detected
Solution: MERGE condition is missing or incorrect. Add proper join keys.
Error: Schema mismatch during MERGE
Solution: Cast columns to match target table schema explicitly.
References
Additional Merge Patterns
Beyond the core SCD Type 1/2 and fact aggregation patterns above, these advanced patterns handle specialized table types identified during Gold layer design.
Design-Driven Pattern Selection: Select the correct merge pattern based on YAML table_properties:
YAML grain_type | YAML dimension_pattern | Pattern | Template |
|---|
transaction or aggregated | — | Standard fact aggregation | fact-table-aggregation-merge.py |
accumulating_snapshot | — | Milestone progression | accumulating-snapshot-merge.py |
factless | — | INSERT-only (no measures) | factless-fact-merge.py |
periodic_snapshot | — | Full period replacement | periodic-snapshot-merge.py |
| — | junk | DISTINCT flag extraction | junk-dimension-populate.py |
| — | (standard) + scd_type: 1 | SCD Type 1 | scd-type1-merge.py |
| — | (standard) + scd_type: 2 | SCD Type 2 | scd-type2-merge.py |