amee-joshi-data-engineering-portfolio
aradotso/data-skills
展示 Azure 資料工程模式、Medallion 架構及端到端分析解決方案的參考專案集
...展開全部關於amee-joshi-data-engineering-portfolio
Amee Joshi 的資料工程作品集是一份參考指南,記錄了用於建置可擴展、雲原生資料平台的生產級 Azure 資料工程模式與架構。 本作品集透過以 Medallion(青銅-白銀-黃金)架構為核心,示範如何運用 Azure 服務、Databricks、SQL Server 及 BI 工具進行資料擷取、轉換、建模與分析,從而解決了端到端分析解決方案缺乏具體且經實證參考範例的問題。
作為文件化內容,本作品著重於說明實作流程,而非實際執行任何操作:包含採用 Delta Lake 的 Medallion 架構、Azure 平台元件(ADF、ADLS Gen2、Databricks、 Synapse Analytics)的 Medallion 架構、湖屋模式、採用星型資料模型與緩慢變更維度(SCD 第 1 型與第 2 型)的維度建模、元資料驅動的資料導入框架、增量 ETL/ELT 載入,以及 Power BI 與 Tableau 報表分析。 其中包含每個 Medallion 層級的 PySpark 程式碼範例——將帶有血統欄位的原始資料導入「青銅級」Delta 資料表、進行資料清洗與去重後導入「銀級」,以及用於「金級」的 SCD 第 2 型維度建構器。
本專案專為希望獲得 Azure 湖屋與資料倉儲工作參考模式及專案架構的資料工程師與分析工程師而設計,也適合正在學習如何端到端設計這些系統的人士。由於這是一個唯讀的參考專案集,內容包含架構模式與示範程式碼,且不含任何破壞性操作、憑證處理或外部呼叫,因此是一項安全無虞的教育資源。
常見問題
這項技能提供什麼?
一套 Azure 資料工程模式的參考作品集——包含 Medallion 架構、採用 Delta Lake 的湖屋、維度建模、元資料驅動的資料導入以及 BI 報表——並針對每個層級提供示範性的 PySpark 程式碼。
涵蓋哪些技術?
Azure Data Factory、ADLS Gen2、Databricks、Synapse Analytics、Delta Lake、SQL Server,以及 Power BI 和 Tableau 等 BI 工具。
這是可執行的程式碼還是參考資料?
這是一套展示模式與架構的參考作品集,內容包含具代表性的程式碼片段,而非可部署的端到端可執行管線。
適用對象為何?
專門負責建置 Azure Lakehouse 或資料倉儲平台的資料與分析工程師,以及正在學習端到端資料工程設計模式的人士。
是否涵蓋緩變維度?
是的。本範例展示了維度建模,包括星型模式以及 SCD 類型 1 和類型 2,並提供了一個在 Gold 層應用 SCD 類型 2 邏輯的範例函式。
Skill by ara.so — Data Skills collection.
This portfolio showcases production-grade data engineering patterns and architectures for building scalable, cloud-native data platforms. It demonstrates end-to-end solutions covering data ingestion, transformation, modeling, and analytics using Azure services, Databricks, SQL Server, and BI tools.
What This Portfolio Demonstrates
This is a reference collection showing:
- Medallion Architecture (Bronze-Silver-Gold) implementations
- Azure cloud data platforms (ADF, ADLS Gen2, Databricks, Synapse Analytics)
- Data lakehouse patterns with Delta Lake
- Dimensional modeling (Star Schema, SCD Type 1 & 2)
- Metadata-driven ingestion frameworks
- Analytics-ready datasets for BI consumption
- ETL/ELT pipeline design with incremental loading
- Power BI and Tableau reporting solutions
Key Portfolio Projects
1. Azure Databricks Retail Lakehouse
Repository: azure-databricks-end-to-end-retail-lakehouse
Pattern: Enterprise Medallion Architecture with Delta Lake
Architecture:
Bronze (Raw) → Silver (Cleansed) → Gold (Analytics-Ready)Key Implementation Concepts:
# Bronze Layer - Raw Ingestionfrom pyspark.sql import SparkSessionfrom delta.tables import DeltaTable# Ingest raw data with metadatadf_raw = (spark.read .format("parquet") .load(f"{bronze_path}/source_data/") .withColumn("ingestion_timestamp", current_timestamp()) .withColumn("source_file", input_file_name()))# Write to Bronze Delta table(df_raw.write .format("delta") .mode("append") .option("mergeSchema", "true") .save(f"{bronze_path}/retail_transactions"))
# Silver Layer - Data Quality & Transformationfrom pyspark.sql.functions import col, when, trim, upper# Cleanse and standardizedf_silver = (df_bronze .filter(col("transaction_id").isNotNull()) .withColumn("customer_name", trim(upper(col("customer_name")))) .withColumn("transaction_amount", when(col("transaction_amount") < 0, 0) .otherwise(col("transaction_amount"))) .dropDuplicates(["transaction_id"]) .select("transaction_id", "customer_id", "product_id", "transaction_amount", "transaction_date"))# Write with schema enforcement(df_silver.write .format("delta") .mode("overwrite") .option("overwriteSchema", "false") .save(f"{silver_path}/transactions"))
# Gold Layer - SCD Type 2 Dimensiondef apply_scd_type2(target_table, source_df, key_columns, scd_columns): """ Implements Slowly Changing Dimension Type 2 """ from delta.tables import DeltaTable from pyspark.sql.functions import lit, current_timestamp # Prepare source with SCD metadata source_prepared = (source_df .withColumn("effective_date", current_timestamp()) .withColumn("end_date", lit(None).cast("timestamp")) .withColumn("is_current", lit(True)) ) # Read existing target target_delta = DeltaTable.forPath(spark, target_table) # Identify changes merge_condition = " AND ".join([f"target.{k} = source.{k}" for k in key_columns]) # Perform SCD Type 2 merge (target_delta.alias("target") .merge(source_prepared.alias("source"), merge_condition) .whenMatchedUpdate( condition = "target.is_current = true AND " + " OR ".join([f"target.{c} != source.{c}" for c in scd_columns]), set = { "is_current": "false", "end_date": "current_timestamp()" } ) .whenNotMatchedInsertAll() .execute() )
2. Metadata-Driven Ingestion Framework
Pattern: Dynamic, configuration-based pipeline generation
Configuration Schema:
{ "pipeline_config": { "source_system": "SQL_SERVER", "target_layer": "bronze", "ingestion_type": "incremental", "watermark_column": "modified_date", "tables": [ { "schema_name": "sales", "table_name": "orders", "partition_column": "order_date", "primary_key": ["order_id"], "target_path": "/bronze/sales/orders" } ] }}Azure Data Factory Pattern:
# Dynamic pipeline parameter processing# This represents the logic implemented in ADFdef generate_copy_activity(table_config): """ Generates ADF copy activity from metadata """ return { "name": f"Copy_{table_config['table_name']}", "type": "Copy", "inputs": [{ "referenceName": "SourceDataset", "type": "DatasetReference", "parameters": { "schemaName": table_config['schema_name'], "tableName": table_config['table_name'] } }], "outputs": [{ "referenceName": "SinkDataset", "type": "DatasetReference", "parameters": { "targetPath": table_config['target_path'] } }], "typeProperties": { "source": { "type": "SqlServerSource", "sqlReaderQuery": f""" SELECT * FROM {table_config['schema_name']}.{table_config['table_name']} WHERE {table_config['watermark_column']} > '@{{pipeline().parameters.watermarkValue}}' """ }, "sink": { "type": "ParquetSink", "storeSettings": { "type": "AzureBlobFSWriteSettings", "copyBehavior": "PreserveHierarchy" } } } }
3. Star Schema Data Warehouse
Pattern: Dimensional Modeling with SQL Server
Dimension Table (SCD Type 1):
-- Dimension: Product (SCD Type 1)CREATE TABLE dim_product ( product_key INT IDENTITY(1,1) PRIMARY KEY, product_id INT NOT NULL, product_name NVARCHAR(100), category NVARCHAR(50), subcategory NVARCHAR(50), unit_price DECIMAL(10,2), modified_date DATETIME DEFAULT GETDATE(), CONSTRAINT uk_product UNIQUE (product_id));-- ETL Merge (SCD Type 1 - Overwrite)MERGE INTO dim_product AS targetUSING ( SELECT product_id, product_name, category, subcategory, unit_price FROM staging.products) AS sourceON target.product_id = source.product_idWHEN MATCHED AND ( target.product_name != source.product_name OR target.category != source.category OR target.unit_price != source.unit_price)THEN UPDATE SET target.product_name = source.product_name, target.category = source.category, target.subcategory = source.subcategory, target.unit_price = source.unit_price, target.modified_date = GETDATE()WHEN NOT MATCHED BY TARGETTHEN INSERT (product_id, product_name, category, subcategory, unit_price)VALUES (source.product_id, source.product_name, source.category, source.subcategory, source.unit_price);
Dimension Table (SCD Type 2):
-- Dimension: Customer (SCD Type 2)CREATE TABLE dim_customer ( customer_key INT IDENTITY(1,1) PRIMARY KEY, customer_id INT NOT NULL, customer_name NVARCHAR(100), email NVARCHAR(100), city NVARCHAR(50), state NVARCHAR(50), effective_date DATETIME NOT NULL, end_date DATETIME NULL, is_current BIT DEFAULT 1, CONSTRAINT uk_customer_current UNIQUE (customer_id, is_current));-- ETL for SCD Type 2-- Step 1: Expire changed recordsUPDATE dim_customerSET end_date = GETDATE(), is_current = 0WHERE customer_id IN ( SELECT s.customer_id FROM staging.customers s INNER JOIN dim_customer d ON s.customer_id = d.customer_id WHERE d.is_current = 1 AND (s.city != d.city OR s.state != d.state));-- Step 2: Insert new versionsINSERT INTO dim_customer ( customer_id, customer_name, email, city, state, effective_date, end_date, is_current)SELECT s.customer_id, s.customer_name, s.email, s.city, s.state, GETDATE() AS effective_date, NULL AS end_date, 1 AS is_currentFROM staging.customers sLEFT JOIN dim_customer d ON s.customer_id = d.customer_id AND d.is_current = 1WHERE d.customer_key IS NULL OR s.city != d.city OR s.state != d.state;
Fact Table:
-- Fact: Sales TransactionsCREATE TABLE fact_sales ( sales_key BIGINT IDENTITY(1,1) PRIMARY KEY, date_key INT NOT NULL, customer_key INT NOT NULL, product_key INT NOT NULL, store_key INT NOT NULL, quantity INT NOT NULL, unit_price DECIMAL(10,2) NOT NULL, discount_amount DECIMAL(10,2) DEFAULT 0, tax_amount DECIMAL(10,2) DEFAULT 0, total_amount DECIMAL(10,2) NOT NULL, CONSTRAINT fk_date FOREIGN KEY (date_key) REFERENCES dim_date(date_key), CONSTRAINT fk_customer FOREIGN KEY (customer_key) REFERENCES dim_customer(customer_key), CONSTRAINT fk_product FOREIGN KEY (product_key) REFERENCES dim_product(product_key), CONSTRAINT fk_store FOREIGN KEY (store_key) REFERENCES dim_store(store_key));-- Create columnstore index for analyticsCREATE NONCLUSTERED COLUMNSTORE INDEX idx_fact_sales_csON fact_sales (date_key, customer_key, product_key, store_key, quantity, unit_price, total_amount);-- ETL LoadINSERT INTO fact_sales ( date_key, customer_key, product_key, store_key, quantity, unit_price, discount_amount, tax_amount, total_amount)SELECT dd.date_key, dc.customer_key, dp.product_key, ds.store_key, st.quantity, st.unit_price, st.discount_amount, st.tax_amount, st.total_amountFROM staging.transactions stINNER JOIN dim_date dd ON CAST(st.transaction_date AS DATE) = dd.dateINNER JOIN dim_customer dc ON st.customer_id = dc.customer_id AND dc.is_current = 1INNER JOIN dim_product dp ON st.product_id = dp.product_idINNER JOIN dim_store ds ON st.store_id = ds.store_id;
4. Incremental Data Loading Pattern
Watermark-Based Incremental Load:
# Databricks notebook - Incremental load with watermarkfrom pyspark.sql.functions import col, max as spark_maxfrom delta.tables import DeltaTable# Configurationsource_table = "source_database.transactions"target_path = "/mnt/silver/transactions"watermark_table = "control.watermark"watermark_column = "modified_date"# Get last watermarklast_watermark = (spark.table(watermark_table) .filter(col("table_name") == source_table) .select("watermark_value") .first()[0])# Read incremental datadf_incremental = (spark.table(source_table) .filter(col(watermark_column) > last_watermark))# Check if target existsif DeltaTable.isDeltaTable(spark, target_path): # Merge into existing table target_table = DeltaTable.forPath(spark, target_path) (target_table.alias("target") .merge( df_incremental.alias("source"), "target.transaction_id = source.transaction_id" ) .whenMatchedUpdateAll() .whenNotMatchedInsertAll() .execute() )else: # Initial load (df_incremental.write .format("delta") .mode("overwrite") .save(target_path) )# Update watermarknew_watermark = df_incremental.agg(spark_max(watermark_column)).first()[0]spark.sql(f""" UPDATE {watermark_table} SET watermark_value = '{new_watermark}', last_updated = current_timestamp() WHERE table_name = '{source_table}'""")
5. Data Quality Framework
Quality Checks Pattern:
from pyspark.sql.functions import col, count, sum as spark_sum, whenclass DataQualityChecker: """ Data quality validation framework """ def __init__(self, dataframe, table_name): self.df = dataframe self.table_name = table_name self.quality_results = [] def check_null_values(self, columns): """Check for null values in critical columns""" for column in columns: null_count = self.df.filter(col(column).isNull()).count() total_count = self.df.count() self.quality_results.append({ "check_type": "null_check", "column": column, "null_count": null_count, "total_count": total_count, "null_percentage": (null_count / total_count * 100) if total_count > 0 else 0, "passed": null_count == 0 }) return self def check_duplicates(self, key_columns): """Check for duplicate records""" duplicate_count = (self.df .groupBy(key_columns) .count() .filter(col("count") > 1) .count() ) self.quality_results.append({ "check_type": "duplicate_check", "key_columns": key_columns, "duplicate_count": duplicate_count, "passed": duplicate_count == 0 }) return self def check_referential_integrity(self, foreign_key, reference_df, reference_key): """Check referential integrity""" missing_references = (self.df .select(foreign_key) .distinct() .join(reference_df.select(reference_key), col(foreign_key) == col(reference_key), "left_anti") .count() ) self.quality_results.append({ "check_type": "referential_integrity", "foreign_key": foreign_key, "missing_references": missing_references, "passed": missing_references == 0 }) return self def check_value_range(self, column, min_value=None, max_value=None): """Check if values are within expected range""" out_of_range = self.df.filter( (col(column) < min_value if min_value is not None else False) | (col(column) > max_value if max_value is not None else False) ).count() self.quality_results.append({ "check_type": "range_check", "column": column, "min_value": min_value, "max_value": max_value, "out_of_range_count": out_of_range, "passed": out_of_range == 0 }) return self def get_results(self): """Return quality check results""" return self.quality_results# Usage exampledf_transactions = spark.read.format("delta").load("/mnt/silver/transactions")df_customers = spark.read.format("delta").load("/mnt/gold/dim_customer")quality_checker = DataQualityChecker(df_transactions, "transactions")results = (quality_checker .check_null_values(["transaction_id", "customer_id", "transaction_date"]) .check_duplicates(["transaction_id"]) .check_referential_integrity("customer_id", df_customers, "customer_id") .check_value_range("transaction_amount", min_value=0, max_value=100000) .get_results())# Log resultsfor result in results: print(f"{result['check_type']}: {'PASSED' if result['passed'] else 'FAILED'}")
Power BI Analytics Patterns
DAX Measures for KPIs:
// Total SalesTotal Sales = SUM(fact_sales[total_amount])// Year-over-Year GrowthSales YoY Growth = VAR CurrentYearSales = [Total Sales]VAR PreviousYearSales = CALCULATE( [Total Sales], DATEADD(dim_date[Date], -1, YEAR) )RETURN DIVIDE( CurrentYearSales - PreviousYearSales, PreviousYearSales, 0 )// Customer Lifetime ValueCustomer LTV = CALCULATE( [Total Sales], ALLEXCEPT(dim_customer, dim_customer[customer_id]))// Moving Average (3 months)Sales 3M MA = CALCULATE( [Total Sales], DATESINPERIOD( dim_date[Date], LASTDATE(dim_date[Date]), -3, MONTH )) / 3// Rank by SalesProduct Sales Rank = RANKX( ALL(dim_product[product_name]), [Total Sales], , DESC, DENSE)Common Architectural Patterns
Medallion Architecture Best Practices
Bronze Layer:
- Raw data ingestion with minimal transformation
- Add audit columns (ingestion_timestamp, source_file)
- Preserve source schema with schema evolution enabled
- Partition by ingestion date for performance
Silver Layer:
- Data cleansing and standardization
- Deduplication based on business keys
- Data type conversions and validations
- Enforce schema constraints
- Join related datasets
Gold Layer:
- Business-aggregated datasets
- Dimensional models (Star/Snowflake schema)
- Pre-calculated metrics and KPIs
- Optimized for BI tool consumption
Delta Lake Optimization
# Optimize Delta tablesfrom delta.tables import DeltaTable# Optimize with Z-orderingdeltaTable = DeltaTable.forPath(spark, "/mnt/gold/fact_sales")# Optimize files and Z-order by common filter columnsdeltaTable.optimize().executeZOrderBy("date_key", "customer_key")# Vacuum old files (retention 168 hours = 7 days)deltaTable.vacuum(168)# Update table statisticsspark.sql("ANALYZE TABLE gold.fact_sales COMPUTE STATISTICS FOR ALL COLUMNS")
Unity Catalog Security
-- Create catalog and schemaCREATE CATALOG IF NOT EXISTS retail_analytics;CREATE SCHEMA IF NOT EXISTS retail_analytics.gold;-- Grant permissionsGRANT USE CATALOG ON CATALOG retail_analytics TO `data_analysts`;GRANT USE SCHEMA ON SCHEMA retail_analytics.gold TO `data_analysts`;GRANT SELECT ON TABLE retail_analytics.gold.fact_sales TO `data_analysts`;-- Row-level securityCREATE FUNCTION retail_analytics.gold.customer_filter(customer_region STRING)RETURN customer_region = current_user_region();ALTER TABLE retail_analytics.gold.fact_sales SET ROW FILTER retail_analytics.gold.customer_filter ON (region);
Environment Setup
Azure Configuration:
# Set Azure environment variablesexport AZURE_SUBSCRIPTION_ID=your_subscription_idexport AZURE_RESOURCE_GROUP=rg-data-platformexport AZURE_STORAGE_ACCOUNT=datalakestorageexport AZURE_DATABRICKS_WORKSPACE=databricks-workspace# ADF connectionexport ADF_FACTORY_NAME=adf-data-ingestionexport ADF_LINKED_SERVICE_NAME=ls-sqlserver-source
Databricks Configuration:
# Mount ADLS Gen2 in Databricksconfigs = { "fs.azure.account.auth.type": "OAuth", "fs.azure.account.oauth.provider.type": "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider", "fs.azure.account.oauth2.client.id": dbutils.secrets.get(scope="keyvault", key="client-id"), "fs.azure.account.oauth2.client.secret": dbutils.secrets.get(scope="keyvault", key="client-secret"), "fs.azure.account.oauth2.client.endpoint": f"https://login.microsoftonline.com/{dbutils.secrets.get(scope='keyvault', key='tenant-id')}/oauth2/token"}dbutils.fs.mount( source = "abfss://[email protected]/", mount_point = "/mnt/bronze", extra_configs = configs)
Troubleshooting
Issue: Delta Lake merge taking too long
# Solution: Optimize before mergefrom delta.tables import DeltaTabletarget_table = DeltaTable.forPath(spark, target_path)# Compact small files firsttarget_table.optimize().executeCompaction()# Enable auto-optimize and auto-compactionspark.sql(f""" ALTER TABLE delta.`{target_path}` SET TBLPROPERTIES ( delta.autoOptimize.optimizeWrite = true, delta.autoOptimize.autoCompact = true )""")
Issue: ADF pipeline timeout
// Increase timeout in ADF pipeline activity{ "typeProperties": { "timeout": "0.12:00:00" }, "policy": { "timeout": "7.00:00:00", "retry": 2, "retryIntervalInSeconds": 30 }}
Issue: Power BI slow refresh
// Use incremental refresh configuration// In Power BI Desktop: Table Tools > Incremental Refresh// Or optimize DAX measuresOptimized Total Sales = CALCULATE( SUM(fact_sales[total_amount]), KEEPFILTERS(dim_date[Date]) // Reduce context transition overhead)Issue: Schema evolution conflicts
# Enable schema merging in Delta writes(df.write .format("delta") .mode("append") .option("mergeSchema", "true") .save(target_path))# Or explicitly allow schema overwrite(df.write .format("delta") .mode("overwrite") .option("overwriteSchema", "true") .save(target_path))
Reference Architecture
This portfolio demonstrates a typical enterprise data platform architecture:
┌─────────────────┐│ Source Systems ││ (SQL Server, ││ APIs, Files) │└────────┬────────┘ │ ▼┌─────────────────┐│ Azure Data ││ Factory (ADF) │ ◄──── Metadata-driven ingestion└────────┬────────┘ │ ▼┌─────────────────┐│ ADLS Gen2 ││ Bronze Layer │ ◄──── Raw data landing└────────┬────────┘ │ ▼┌─────────────────┐│ Databricks ││ Silver Layer │ ◄──── Cleansing & transformation└────────┬────────┘ │ ▼┌─────────────────┐│ Databricks ││ Gold Layer │ ◄──── Analytics-ready datasets└────────┬────────┘ │ ├──────────────────┐ ▼ ▼┌─────────────────┐ ┌─────────────────┐│ Power BI │ │ Synapse ││ Reporting │ │ Analytics │└─────────────────┘ └─────────────────┘This skill provides patterns and code examples for building production-grade data platforms following industry best practices demonstrated across the portfolio projects.
所有檔案
0 個檔案安裝 amee-joshi-data-engineering-portfolio
請下載並將技能檔案解壓縮至您的 .claude/skills/ 目錄中。
下載 ZIP複製儲存庫並將技能檔案複製到您的專案中。
git clone https://github.com/Aradotso/data-skills/blob/main/skills/amee-joshi-data-engineering-portfolio/SKILL.md # Copy SKILL.md to your .claude/skills/ directory
複製





首頁
