๐Ÿ“‹ Table of Contents

  1. Overview
  2. Script 1: Data Load Script (Configuration)
  3. Script 2: Extract Data (Extraction & Product Join)
  4. Script 3: Transformed Data T2 (Customer Join)
  5. Script 4: Transformed Data T1 (Business Logic)
  6. Script 5: Incremental Load
  7. Script 6: SCD Type 2
  8. Script 7: Master Calendar
  9. Script 8: Save Transformed Data
  10. Script 9: Load Data for Visualization
  11. Quick Reference Tables

๐ŸŽฏ Overview

Purpose of This Document

This script reference provides line-by-line explanations of every core ETL script in the GlobalTech Electronics solution. Each line of code is explained with:

How to Use This Guide

  1. Read alongside scripts: Open Qlik Data Load Editor and this document side-by-side
  2. Follow execution order: Scripts are presented in the order they execute
  3. Reference syntax: Use Quick Reference section for function lookups
  4. Understand patterns: Recognize reusable patterns across scripts

Document Conventions

// This is a comment explaining the code block
ACTUAL CODE LINES;
โ†’ Indicates the result or what happens next
๐Ÿ’ก Indicates a key insight or best practice
โš ๏ธ Indicates a common mistake to avoid

๐Ÿ“Š Script 1: Data Load Script (Configuration)

Full Script with Line-by-Line Explanation

///Step 1. Configuration & Variables
โ†’ Comment: Section header for documentation
๐Ÿ’ก Triple slash (///) creates a collapsible section in script editor

/*
--------------------------------------------------------------------------------
SECTION 1: VARIABLE DECLARATION
Purpose: Define reusable variables for paths, thresholds, and calculations
--------------------------------------------------------------------------------
*/
โ†’ Block comment: Multi-line documentation
๐Ÿ’ก Use /* */ for lengthy explanations, // for single lines

Path Variables

// File Paths
SET vSourcePath = 'lib://DataFiles/';
What: Declares variable for source data location
Why: Centralizes path management - change once, affects all references
How: SET stores text value as-is (no evaluation)
Usage: $(vSourcePath)filename.qvd expands to lib://DataFiles/filename.qvd
๐Ÿ’ก lib:// references named data connection, not physical path

SET vTargetPath = 'lib://DataFiles/Transformed/';
What: Declares variable for transformed data location
Why: Separates source (untouched) from transformed (our work)
How: Same as vSourcePath but points to Transformed subfolder
๐Ÿ’ก Consistent naming: v prefix indicates variable

Business Logic Variables

// Business Logic Variables
LET vDiscountThreshold = 0.10;  // 10% discount threshold
What: Defines what qualifies as "heavy discount"
Why: Business rule: >10% discount is considered heavy
How: LET evaluates expression (0.10) and stores result
Usage: If(Discount > $(vDiscountThreshold), 'Heavy', 'Light')
SET vs LET: SET would store '0.10' as text, LET stores 0.1 as number
๐Ÿ’ก Include inline comment explaining business rule

LET vHighValueOrder = 1000;      // Orders above $1000 are "High Value"
What: Threshold for high-value order classification
Why: Business rule: Orders โ‰ฅ$1000 need special attention
How: LET evaluates and stores numeric value 1000
๐Ÿ’ก No quotes needed for numbers with LET

LET vCurrentYear = Year(Today());
What: Stores current year as a number
Why: Used for "current year" flags and fiscal year calculations
How:

LET vCurrentDate = Date(Today(), 'YYYY-MM-DD');
What: Stores today's date in standardized format
Why: Used for ETL metadata (when was data loaded)
How:

// Date Variables for Incremental Load
LET vLastLoadDate = Date(Today()-30, 'YYYY-MM-DD'); // Last load was 30 days ago
What: Simulates when we last loaded data
Why: Used to filter incremental data (WHERE OrderDate > vLastLoadDate)
How:

Status Variables

// Status Variables
SET vActiveStatus = 'Active';
What: Defines the text value that means "Active"
Why: If status values change, update once here
How: SET stores text 'Active' as-is
Usage: If(Status = '$(vActiveStatus)', 1, 0)
๐Ÿ’ก Quotes needed for text values

SET vCompletedStatus = 'Completed';
What: Defines the text value for completed orders
Why: Centralized definition of order completion
How: SET stores 'Completed' as text
๐Ÿ’ก Makes script maintainable when business rules change

๐Ÿ” Script 2: Extract Data (Extraction & Product Join)

Section: Extract Dimensions

/*
--------------------------------------------------------------------------------
SECTION 2: EXTRACT DIMENSION DATA
Purpose: Load all dimension tables from source QVD files
--------------------------------------------------------------------------------
*/

โ†’ Documentation block explaining section purpose


// Load Product Dimension
// What it does: Imports product master data with attributes
DIM_Products_Raw:
What: Table name declaration
Why: Creates table called DIM_Products_Raw in memory
How: Everything after : until semicolon defines this table
๐Ÿ’ก Naming convention: DIM = dimension, _Raw = unchanged from source

LOAD 
    ProductID,
What: Loads ProductID field as-is
Why: Primary key for products
How: Copies field from source without transformation
๐Ÿ’ก First field listed (common to put keys first)

    ProductName,
    Category,
    SubCategory,
    Brand,
    UnitCost,
    ListPrice,
    Status,
What: Loads descriptive and numeric fields as-is
Why: Need all product attributes for analysis
How: Simple field list, no transformations
๐Ÿ’ก One field per line improves readability

    Date(LastModifiedDate, 'YYYY-MM-DD') as Product_LastModifiedDate
What: Loads and transforms date field
Why:
  1. Convert to proper Qlik date format
  2. Rename to avoid conflicts with other LastModifiedDate fields How:

FROM [$(vSourcePath)DIM_Products.qvd] (qvd);
What: Specifies data source
Why: Tells Qlik where to read data from
How:

๐Ÿ”„ Repeat Pattern for Customers and Stores

The Customer and Store dimension loads follow the same pattern:

Key Difference - Store Fields:

    Country as Store_Country,
    Region as Store_Region,
    City as Store_City,

Why Rename?: Customers also have Country/Region/City Prevents: Synthetic keys later ๐Ÿ’ก: Prefix indicates which entity owns the field


Section: Extract Fact Table

/*
--------------------------------------------------------------------------------
SECTION 3: EXTRACT FACT DATA (INITIAL LOAD)
Purpose: Load historical sales transactions
--------------------------------------------------------------------------------
*/

// Load Sales Fact Table
// What it does: Imports transaction-level sales data
FACT_Sales_Raw:
LOAD 
    OrderID,
    Date(OrderDate, 'YYYY-MM-DD') as OrderDate,
    CustomerID,
    ProductID,
    Quantity,
    Discount,
    ShippingCost,
    OrderStatus,
    ProcessedFlag
What: Loads transaction data with date formatting
Why: Core fact table - every row is a sale
How: Standard field list with Date() formatting for OrderDate
๐Ÿ’ก Foreign keys (CustomerID, ProductID) enable joins

FROM [$(vSourcePath)FACT_Sales.qvd] (qvd)
WHERE ProcessedFlag = 0;  // Only load unprocessed records
What: Loads from source with filter
Why: Only get records not yet processed
How:

Section: Join Sales with Products

/*
--------------------------------------------------------------------------------
SECTION 6: JOIN OPERATIONS - LEFT JOIN (Sales with Products)
Purpose: Enrich sales data with product information
Join Type: LEFT JOIN (keeps all sales records even if product not found)
--------------------------------------------------------------------------------
*/

โ†’ Documentation explains join strategy


// Left Join: Add Product Details to Sales (with renamed fields to avoid synthetic keys)
LEFT JOIN (FACT_Sales_Raw)
What: Declares a LEFT JOIN operation targeting FACT_Sales_Raw
Why: Want to add product details to sales records
How: LEFT JOIN (TableName) adds fields from the LOAD to existing table
๐Ÿ’ก LEFT JOIN keeps all rows from FACT_Sales_Raw

LOAD
    ProductID,
What: Loads the join key
Why: Qlik matches this ProductID with ProductID in FACT_Sales_Raw
How: Fields with same name = automatic join key
โš ๏ธ Don't rename the key field!

    ProductName as Prod_ProductName,
    Category as Prod_Category,
    SubCategory as Prod_SubCategory,
    Brand as Prod_Brand,
What: Loads descriptive fields with prefix
Why: FACT_Sales_Raw will eventually have "ProductName", need different name
How: as Prod_ProductName renames during load
๐Ÿ’ก Consistent prefix (Prod_) makes purpose clear

    UnitCost as Prod_UnitCost,
    ListPrice as Prod_ListPrice,
    ProfitPerUnit as Prod_ProfitPerUnit,
    ProfitMarginPercent as Prod_ProfitMarginPercent,
    PriceTier as Prod_PriceTier,
    IsActive as Prod_IsActive
What: Loads calculated and numeric fields with prefix
Why: These were added in transformation, need them in sales
How: All renamed with Prod_ prefix
๐Ÿ’ก Loads from transformed table, gets calculated fields too

RESIDENT DIM_Products_Transformed;
What: Specifies source is table already in memory
Why: DIM_Products_Transformed exists from previous LOAD
How: RESIDENT means "read from memory" not file
โ†’ Result: FACT_Sales_Raw now has 15 columns added (all Prod_* fields)
๐Ÿ’ก Much faster than reloading from QVD

๐Ÿ”— Script 3: Transformed Data T2 (Customer Join)

/*
--------------------------------------------------------------------------------
SECTION 7: JOIN OPERATIONS - LEFT JOIN (Sales with Customers)
Purpose: Add customer information to sales transactions
Join Type: LEFT JOIN
--------------------------------------------------------------------------------
*/

// Left Join: Add Customer Details to Sales (with renamed fields to avoid synthetic keys)
LEFT JOIN (FACT_Sales_Raw)
LOAD
    CustomerID,
    CustomerName as Cust_CustomerName,
    Country as Cust_Country,
    Region as Cust_Region,
    City as Cust_City,
    Segment as Cust_Segment,
    CustomerTenureYears as Cust_TenureYears,
    LoyaltyTier as Cust_LoyaltyTier,
    MarketType as Cust_MarketType
RESIDENT DIM_Customers_Transformed;
What: LEFT JOIN adding customer fields to sales
Why: Need customer attributes (segment, location, loyalty) in sales data
How: Same pattern as product join

โ†’ Result: FACT_Sales_Raw now has:

๐Ÿ’ก After this, FACT_Sales_Raw is a "wide" table with all details for final transformations


๐Ÿ’ผ Script 4: Transformed Data T1 (Business Logic)

Section: Transform Product Dimension

/*
--------------------------------------------------------------------------------
SECTION 4: TRANSFORM PRODUCT DIMENSION
Purpose: Apply business logic and create calculated fields
--------------------------------------------------------------------------------
*/

// Transform Product Data
// What it does: Adds profit margin calculation and categorizes products
DIM_Products_Transformed:
LOAD
    ProductID,
    ProductName,
    Category,
    SubCategory,
    Brand,
    UnitCost,
    ListPrice,
    Status,
    Product_LastModifiedDate,
What: Starts creating transformed product table
Why: Need calculated fields (profit, margins, tiers)
How: Lists original fields first (keep everything)
๐Ÿ’ก Using RESIDENT load from DIM_Products_Raw

    // Calculated Fields
    ListPrice - UnitCost as ProfitPerUnit,
What: Calculates profit per unit
Why: Business needs to know profit on each item
How: Simple subtraction in LOAD statement Example: $1299 - $850 = $449
๐Ÿ’ก Calculations happen during load, stored as field

    Round(((ListPrice - UnitCost) / ListPrice) * 100, 2) as ProfitMarginPercent,
What: Calculates profit margin as percentage
Why: Business Ask #3 - need profitability analysis
How:
  1. (ListPrice - UnitCost) = Profit
  2. / ListPrice = Profit as fraction of price
  3. * 100 = Convert to percentage
  4. Round(..., 2) = Round to 2 decimals Example: (1299-850)/1299 * 100 = 34.57% ๐Ÿ’ก Round for readability in reports

    // Business Logic: Price Tier Classification
    If(ListPrice >= 2000, 'Premium',
       If(ListPrice >= 1000, 'Mid-Range',
          If(ListPrice >= 500, 'Standard', 'Budget'))) as PriceTier,
What: Classifies products into price tiers
Why: Business Ask #3 - categorize by price point
How: Nested IF statements test conditions in order

    // Active Status Flag
    If(Status = '$(vActiveStatus)', 1, 0) as IsActive
What: Creates binary flag for active products
Why: Easier to filter (1/0) than text ("Active"/"Discontinued")
How:

RESIDENT DIM_Products_Raw;
What: Source is DIM_Products_Raw table in memory
Why: Transform the raw data we loaded earlier
How: RESIDENT reads from memory
โ†’ Result: DIM_Products_Transformed with 10 rows + calculated fields

DROP TABLE DIM_Products_Raw;
What: Removes DIM_Products_Raw from memory
Why: No longer needed, have transformed version
How: DROP TABLE removes table completely
๐Ÿ’ก Free up memory, keep workspace clean

Section: Transform Customer Dimension

// Transform Customer Data
// What it does: Calculates customer tenure and loyalty tier
DIM_Customers_Transformed:
LOAD
    CustomerID,
    CustomerName,
    Country,
    Region,
    City,
    Segment,
    RegistrationDate,
    Customer_LastModifiedDate,
    
    // Calculated Fields
    Year(Today()) - Year(RegistrationDate) as CustomerTenureYears,
What: Calculates years since customer registered
Why: Business Ask #2 - need tenure for loyalty tier
How:
  1. Today() = Current date (2026-01-29)
  2. Year(Today()) = Current year (2026)
  3. Year(RegistrationDate) = Year registered (e.g., 2023)
  4. Subtraction = Tenure (2026 - 2023 = 3 years) โš ๏ธ Don't use Today() - RegistrationDate (gives days, not years)

    // Business Logic: Customer Loyalty Tier
    If(Year(Today()) - Year(RegistrationDate) >= 2, 'Gold',
       If(Year(Today()) - Year(RegistrationDate) >= 1, 'Silver', 'Bronze')) as LoyaltyTier,
What: Assigns loyalty tier based on tenure
Why: Business Ask #2 - segment customers by loyalty
How: Nested IF based on tenure calculation

    // Geographic Flag
    If(Region = 'North America' or Region = 'Europe', 'Developed', 'Emerging') as MarketType
What: Classifies market as Developed or Emerging
Why: Business Ask #7 - compare market types
How: IF with OR condition

RESIDENT DIM_Customers_Raw;

DROP TABLE DIM_Customers_Raw;
What: Load from raw, drop raw table
Why: Same pattern as products
โ†’ Result: DIM_Customers_Transformed ready for use

Section: Final Fact Table Creation

/*
--------------------------------------------------------------------------------
SECTION 8: APPLY COMPLEX BUSINESS LOGIC & CALCULATIONS
Purpose: Calculate revenue, profit, and apply business rules
--------------------------------------------------------------------------------
*/

// Final Fact Table with Business Logic
FACT_Sales_Final:
LOAD
    OrderID,
    OrderDate,
What: Start creating final fact table
Why: Apply all calculations and clean up field names
How: Source is FACT_Sales_Raw (which has all joined data)

    Year(OrderDate) as OrderYear,
    Month(OrderDate) as OrderMonth,
    Ceil(Month(OrderDate)/3) as OrderQuarter,
    WeekDay(OrderDate) as OrderDayOfWeek,
What: Extract time intelligence fields
Why: Business Ask #6 - need time-based analysis
How:

    // Customer Fields (renamed to avoid synthetic keys)
    CustomerID,
    Cust_CustomerName as CustomerName,
    Cust_Segment as Segment,
    Cust_LoyaltyTier as LoyaltyTier,
    Cust_MarketType as MarketType,
    Cust_Country as Country,
    Cust_Region as Region,
    Cust_City as City,
    Cust_TenureYears as CustomerTenureYears,
What: Rename customer fields back to clean names
Why: In joins, used Cust_ prefix; now remove for user-friendliness
How: Cust_CustomerName as CustomerName removes prefix
๐Ÿ’ก Final table has clean names, joins had prefixes

    // Product Fields (renamed to avoid synthetic keys)
    ProductID,
    Prod_ProductName as ProductName,
    Prod_Category as Category,
    Prod_SubCategory as SubCategory,
    Prod_Brand as Brand,
    Prod_PriceTier as PriceTier,
What: Same pattern - remove Prod_ prefix
Why: Users see "Category" not "Prod_Category"
๐Ÿ’ก Prefixes were for ETL safety, not user experience

    // Transaction Fields
    Quantity,
    Prod_ListPrice as ListPrice,
    Prod_UnitCost as UnitCost,
    Discount,
    ShippingCost,
    OrderStatus,
What: Basic transaction fields
Why: Need for revenue/cost calculations
How: Rename Prod_ prefixed price fields

    // Revenue Calculations
    Quantity * Prod_ListPrice as GrossRevenue,
What: Total revenue before discounts
Why: Business Ask #1 - unified revenue metrics
How: Units ร— Price Example: 2 ร— $1299 = $2598
๐Ÿ’ก "Gross" = before discounts

    Quantity * Prod_ListPrice * (1 - Discount) as NetRevenue,
What: Revenue after discount applied
Why: Actual money received from customer
How:

    // Cost Calculations
    Quantity * Prod_UnitCost as TotalCost,
What: Cost of goods sold (COGS)
Why: Need for profit calculation
How: Units ร— Cost per unit Example: 2 ร— $850 = $1700

    // Profit Calculations
    (Quantity * Prod_ListPrice * (1 - Discount)) - (Quantity * Prod_UnitCost) as GrossProfit,
What: Profit after discounts and costs
Why: Business Ask #3 - profitability analysis
How: Net Revenue - Total Cost Example: $2468.10 - $1700 = $768.10
๐Ÿ’ก Parentheses ensure correct order of operations

    // Discount Amount
    Quantity * Prod_ListPrice * Discount as DiscountAmount,
What: Dollar value of discount given
Why: Business Ask #4 - discount analysis
How: Gross Revenue ร— Discount rate Example: $2598 ร— 0.05 = $129.90
๐Ÿ’ก Shows revenue sacrificed for sale

    // Business Logic: Order Value Classification
    If((Quantity * Prod_ListPrice * (1 - Discount)) >= 1000, 
       'High Value', 
       'Standard') as OrderValueCategory,
What: Categorizes orders by size
Why: Business Ask #4 - identify high-value orders
How: IF comparing Net Revenue to $1000 threshold

    // Business Logic: Discount Analysis
    If(Discount > 0.10, 
       'Heavy Discount', 
       If(Discount > 0, 'Light Discount', 'No Discount')) as DiscountCategory,
What: Categorizes discount level
Why: Business Ask #4 - discount impact analysis
How: Nested IF testing discount percentage

    // Business Logic: Order Status Flag
    If(OrderStatus = 'Completed', 1, 0) as IsCompleted,
What: Binary flag for completed orders
Why: Easy to count/filter on 1/0
How: Returns 1 if status is 'Completed', else 0
๐Ÿ’ก Faster than text comparison in visualizations

    // Total Transaction Value (includes shipping)
    (Quantity * Prod_ListPrice * (1 - Discount)) + ShippingCost as TotalTransactionValue,
What: Total amount customer paid
Why: Complete picture including shipping
How: Net Revenue + Shipping Example: $2468.10 + $15.00 = $2483.10

    // Flag for current year orders
    If(Year(OrderDate) = 2026, 1, 0) as IsCurrentYear,
What: Flags current year transactions
Why: Easy filtering for "this year" analysis
How: Compares order year to hardcoded 2026
๐Ÿ’ก In production, use $(vCurrentYear) variable

    // Processed Flag Update
    1 as ProcessedFlag,
What: Marks record as processed
Why: Incremental load needs to know what's loaded
How: All records get 1 (processed)
โ†’ Next load: WHERE ProcessedFlag = 0 skips these

    // ETL Metadata
    '2026-01-29' as ETL_LoadDate
What: Timestamp when record was loaded
Why: Audit trail - when did this enter warehouse?
How: Literal text value
๐Ÿ’ก In production, use '$(vCurrentDate)'

RESIDENT FACT_Sales_Raw;
What: Source is the enriched sales table
Why: Has all joined product/customer data
โ†’ Result: FACT_Sales_Final with 15 rows, all calculations applied

DROP TABLE FACT_Sales_Raw;
What: Remove the intermediate table
Why: No longer needed, have final version
๐Ÿ’ก Save memory, prevent confusion

// SAVE dimension tables to QVD before dropping them
STORE DIM_Products_Transformed INTO [$(vTargetPath)DIM_Products_Transformed.qvd] (qvd);
STORE DIM_Customers_Transformed INTO [$(vTargetPath)DIM_Customers_Transformed.qvd] (qvd);
What: Save dimension tables to disk
Why: Need them later for incremental load
How: STORE writes to QVD file
โš ๏ธ CRITICAL: Must STORE before DROP!

// Now drop to avoid synthetic keys
DROP TABLE DIM_Products_Transformed;
DROP TABLE DIM_Customers_Transformed;
What: Remove dimension tables from memory
Why: Would create synthetic keys with final fact table
How: DROP after saving ensures data preserved
โ†’ Memory now has only FACT_Sales_Final

Script 5: Incremental Load

/*
--------------------------------------------------------------------------------
SECTION 9: INCREMENTAL LOAD
Purpose: Load only new/changed records since last ETL run
--------------------------------------------------------------------------------
*/

// Reload dimension tables
DIM_Products_Transformed:
LOAD * FROM [$(vTargetPath)DIM_Products_Transformed.qvd] (qvd);

DIM_Customers_Transformed:
LOAD * FROM [$(vTargetPath)DIM_Customers_Transformed.qvd] (qvd);
What: Reload dimensions from saved QVD files
Why: Dropped them earlier, need for joining new sales
How: LOAD * gets all fields, FROM reads QVD
๐Ÿ’ก LOAD * is OK here - optimized load, no transformations

// Load new incremental data
FACT_Sales_Incremental_Raw:
LOAD 
    OrderID,
    Date(OrderDate, 'YYYY-MM-DD') as OrderDate,
    CustomerID,
    ProductID,
    Quantity,
    Discount,
    ShippingCost,
    OrderStatus,
    ProcessedFlag
FROM [$(vSourcePath)FACT_Sales_Incremental.qvd] (qvd)
WHERE ProcessedFlag = 0;
What: Load new sales from incremental file
Why: Source system adds new orders to this file daily
How:

// Join with product dimensions
LEFT JOIN (FACT_Sales_Incremental_Raw)
LOAD
    ProductID,
    ProductName as Prod_ProductName,
    Category as Prod_Category,
    SubCategory as Prod_SubCategory,
    Brand as Prod_Brand,
    UnitCost as Prod_UnitCost,
    ListPrice as Prod_ListPrice,
    ProfitPerUnit as Prod_ProfitPerUnit,
    PriceTier as Prod_PriceTier,
    IsActive as Prod_IsActive
RESIDENT DIM_Products_Transformed;
What: Add product details to new sales
Why: Same as Phase 4 - need product info for calculations
How: Identical LEFT JOIN with Prod_ prefixes
๐Ÿ’ก CRITICAL: Use exact same join logic as initial load!

// Join with customer dimensions
LEFT JOIN (FACT_Sales_Incremental_Raw)
LOAD
    CustomerID,
    CustomerName as Cust_CustomerName,
    Country as Cust_Country,
    Region as Cust_Region,
    City as Cust_City,
    Segment as Cust_Segment,
    CustomerTenureYears as Cust_TenureYears,
    LoyaltyTier as Cust_LoyaltyTier,
    MarketType as Cust_MarketType
RESIDENT DIM_Customers_Transformed;
What: Add customer details to new sales
Why: Same as Phase 4 - need customer info
How: Identical LEFT JOIN with Cust_ prefixes
โ†’ FACT_Sales_Incremental_Raw now fully enriched

// Apply transformations - Use NoConcatenate to force new table creation
NoConcatenate
FACT_Sales_Incremental_Transformed:
What: Start transformation, prevent auto-concatenation
Why:

LOAD
    OrderID,
    OrderDate,
    Year(OrderDate) as OrderYear,
    Month(OrderDate) as OrderMonth,
    Ceil(Month(OrderDate)/3) as OrderQuarter,
    WeekDay(OrderDate) as OrderDayOfWeek,
    
    CustomerID,
    Cust_CustomerName as CustomerName,
    Cust_Segment as Segment,
    Cust_LoyaltyTier as LoyaltyTier,
    Cust_MarketType as MarketType,
    Cust_Country as Country,
    Cust_Region as Region,
    Cust_City as City,
    Cust_TenureYears as CustomerTenureYears,
    
    ProductID,
    Prod_ProductName as ProductName,
    Prod_Category as Category,
    Prod_SubCategory as SubCategory,
    Prod_Brand as Brand,
    Prod_PriceTier as PriceTier,
    
    Quantity,
    Prod_ListPrice as ListPrice,
    Prod_UnitCost as UnitCost,
    Discount,
    ShippingCost,
    OrderStatus,
    
    Quantity * Prod_ListPrice as GrossRevenue,
    Quantity * Prod_ListPrice * (1 - Discount) as NetRevenue,
    Quantity * Prod_UnitCost as TotalCost,
    (Quantity * Prod_ListPrice * (1 - Discount)) - (Quantity * Prod_UnitCost) as GrossProfit,
    Quantity * Prod_ListPrice * Discount as DiscountAmount,
    
    If((Quantity * Prod_ListPrice * (1 - Discount)) >= 1000, 'High Value', 'Standard') as OrderValueCategory,
    If(Discount > 0.10, 'Heavy Discount', If(Discount > 0, 'Light Discount', 'No Discount')) as DiscountCategory,
    If(OrderStatus = 'Completed', 1, 0) as IsCompleted,
    
    (Quantity * Prod_ListPrice * (1 - Discount)) + ShippingCost as TotalTransactionValue,
    If(Year(OrderDate) = 2026, 1, 0) as IsCurrentYear,
    
    1 as ProcessedFlag,
    '2026-01-29' as ETL_LoadDate
    
RESIDENT FACT_Sales_Incremental_Raw;
What: Apply exact same transformations as Phase 4
Why: New data must match structure and logic of existing data
How: Identical field list, calculations, classifications
๐Ÿ’ก CONSISTENCY IS CRITICAL - any difference breaks analysis
โ†’ Result: FACT_Sales_Incremental_Transformed with 5 transformed rows

DROP TABLE FACT_Sales_Incremental_Raw;
What: Remove raw incremental table
Why: Have transformed version, don't need raw anymore

// Now concatenate to existing fact table
CONCATENATE (FACT_Sales_Final)
LOAD * RESIDENT FACT_Sales_Incremental_Transformed;
What: Append new rows to existing fact table
Why: Combine historical (15 rows) with new (5 rows)
How:

DROP TABLE FACT_Sales_Incremental_Transformed;
What: Remove temporary incremental table
Why: Data now in FACT_Sales_Final, don't need separate table

// Clean up dimension tables
DROP TABLE DIM_Products_Transformed;
DROP TABLE DIM_Customers_Transformed;
What: Remove dimensions again
Why: Same as Phase 4 - prevent synthetic keys
โ†’ Memory has only FACT_Sales_Final with 20 rows

๐Ÿ“œ Script 6: SCD Type 2 (Slowly Changing Dimension)

///$tab SCD_Type2
/*
--------------------------------------------------------------------------------
SECTION 10: SLOWLY CHANGING DIMENSION (SCD TYPE 2)
Purpose: Track historical changes in customer segment
Strategy: Maintain version history with effective dates
--------------------------------------------------------------------------------
*/

// Reload customer dimension (we dropped it earlier)
DIM_Customers_For_SCD:
LOAD 
    CustomerID,
    CustomerName,
    Segment,
    Country,
    Region,
    City,
    LoyaltyTier
FROM [$(vTargetPath)DIM_Customers_Transformed.qvd] (qvd);
What: Load current customer data
Why: Need to compare current vs historical to detect changes
How: Load from QVD saved in Phase 4
๐Ÿ’ก Only load fields relevant for SCD tracking

// Check if SCD table exists
IF FileSize('$(vTargetPath)DIM_Customers_SCD.qvd') > 0 THEN
What: Check if SCD file exists on disk
Why: First run vs subsequent run logic differs
How:

First Run Logic (ELSE Branch)

ELSE
    // Initial load - create first version for all customers
    DIM_Customers_SCD_New:
    LOAD
        CustomerID,
        CustomerName,
        Segment,
        Country,
        Region,
        City,
        LoyaltyTier,
        Date(Today()) as EffectiveStartDate,
        Date('9999-12-31') as EffectiveEndDate,
        1 as IsCurrent,
        1 as VersionNumber
    RESIDENT DIM_Customers_For_SCD;
    
ENDIF
What: Create version 1 for all customers (first run only)
Why: SCD needs starting point with version numbers
How:

Subsequent Run Logic (THEN Branch)

    // Load existing SCD data
    DIM_Customers_SCD_Existing:
    LOAD 
        CustomerID,
        CustomerName,
        Segment,
        Country,
        Region,
        City,
        LoyaltyTier,
        EffectiveStartDate,
        EffectiveEndDate,
        IsCurrent,
        VersionNumber
    FROM [$(vTargetPath)DIM_Customers_SCD.qvd] (qvd);
What: Load historical SCD data
Why: Need to compare with current data to find changes
How: Load all versions (current and historical) from QVD
โ†’ Contains all customer history built up over time

    // Get current active records
    DIM_Customers_Current:
    LOAD 
        CustomerID,
        Segment as Old_Segment,
        LoyaltyTier as Old_LoyaltyTier,
        VersionNumber as Old_Version
    RESIDENT DIM_Customers_SCD_Existing
    WHERE IsCurrent = 1;
What: Extract only current versions
Why: Compare current (Old_*) vs new data to find changes
How:

    // Join to find changes
    LEFT JOIN (DIM_Customers_For_SCD)
    LOAD 
        CustomerID,
        Old_Segment,
        Old_LoyaltyTier,
        Old_Version
    RESIDENT DIM_Customers_Current;
What: Add old values to current data
Why: Now each customer has both old and new values
How: LEFT JOIN on CustomerID
โ†’ DIM_Customers_For_SCD now has: Segment, Old_Segment, etc.
๐Ÿ’ก Can now compare: WHERE Segment <> Old_Segment

    DROP TABLE DIM_Customers_Current;
What: Clean up temporary table
Why: No longer needed, joined data into main table

    // Identify changed records
    NoConcatenate
    DIM_Customers_Changed:
    LOAD *
    RESIDENT DIM_Customers_For_SCD
    WHERE Segment <> Old_Segment 
       OR LoyaltyTier <> Old_LoyaltyTier;
What: Filter for customers with changes
Why: Only these need new versions
How:

    // Check if any changes were detected
    LET vChangesDetected = NoOfRows('DIM_Customers_Changed');
What: Count rows in changed table
Why: Determines which logic branch to take
How: NoOfRows() returns count, stores in variable
โ†’ 0 = no changes, >0 = changes detected
๐Ÿ’ก Variable used in next IF condition

    IF vChangesDetected > 0 THEN
What: Branch based on whether changes exist
Why: Different logic for changes vs no changes
How: Simple IF testing variable value

Changes Detected Branch

        // Create new versions for changed records
        NoConcatenate
        DIM_Customers_SCD_New:
        LOAD
            CustomerID,
            CustomerName,
            Segment,
            Country,
            Region,
            City,
            LoyaltyTier,
            Date(Today()) as EffectiveStartDate,
            Date('9999-12-31') as EffectiveEndDate,
            1 as IsCurrent,
            Old_Version + 1 as VersionNumber
        RESIDENT DIM_Customers_Changed;
What: Create new version for changed customers
Why: SCD Type 2 = keep history by creating new versions
How:

        // Mark old versions as not current
        CONCATENATE (DIM_Customers_SCD_New)
        LOAD
            CustomerID,
            CustomerName,
            Segment,
            Country,
            Region,
            City,
            LoyaltyTier,
            EffectiveStartDate,
            Date(Today()-1) as EffectiveEndDate,
            0 as IsCurrent,
            VersionNumber
        RESIDENT DIM_Customers_SCD_Existing
        WHERE IsCurrent = 1;
What: Close out old versions
Why: Old version is no longer current, set end date
How:

        // Keep all other records unchanged
        CONCATENATE (DIM_Customers_SCD_New)
        LOAD * 
        RESIDENT DIM_Customers_SCD_Existing
        WHERE IsCurrent = 0;
What: Keep all historical records
Why: Don't lose history of past changes
How: Load records already marked historical (IsCurrent = 0)
โ†’ Result: Complete history maintained
๐Ÿ’ก SCD is append-only, never delete history

No Changes Detected Branch

    ELSE
        // No changes detected - keep existing SCD table
        NoConcatenate
        DIM_Customers_SCD_New:
        LOAD * RESIDENT DIM_Customers_SCD_Existing;
        
        DROP TABLE DIM_Customers_Changed;
        
    ENDIF
    
    DROP TABLE DIM_Customers_SCD_Existing;
What: Handle scenario when no changes detected
Why: DIM_Customers_Changed must be dropped inside both branches
How:

DROP TABLE DIM_Customers_For_SCD;
What: Remove current customer data
Why: Done with comparison, have final SCD table
โ†’ Final state: Only DIM_Customers_SCD_New in memory

๐Ÿ“… Script 7: Master Calendar

/*
--------------------------------------------------------------------------------
SECTION 11: MASTER CALENDAR CREATION
Purpose: Create a comprehensive date dimension for time-based analysis
Strategy: Generate calendar from min/max dates in fact table
--------------------------------------------------------------------------------
*/

// Step 1: Find min and max dates from sales data
TempDates:
LOAD 
    Min(OrderDate) as MinDate,
    Max(OrderDate) as MaxDate
RESIDENT FACT_Sales_Final;
What: Find date range in sales data
Why: Generate calendar covering only dates we need
How:

LET vMinDate = Peek('MinDate', 0, 'TempDates');
LET vMaxDate = Peek('MaxDate', 0, 'TempDates');
DROP TABLE TempDates;
What: Store dates in variables and clean up
Why: Need variables for next step (can't reference table in AUTOGENERATE)
How:

// Step 2: Generate date range
TempCalendar:
LOAD
    Date($(vMinDate) + IterNo() - 1) as TempDate
AUTOGENERATE 1
WHILE $(vMinDate) + IterNo() - 1 <= $(vMaxDate);
What: Generate one row for each date in range
Why: Need every date, not just dates with sales
How: This is complex, let's break it down:

Line by line:

LOAD
    Date($(vMinDate) + IterNo() - 1) as TempDate

Iteration examples:

AUTOGENERATE 1
WHILE $(vMinDate) + IterNo() - 1 <= $(vMaxDate);
โ†’ Result: TempCalendar with 31 rows (one per date)
๐Ÿ’ก Industry-standard pattern for date dimension generation

// Step 3: Create calendar with all attributes
MasterCalendar:
LOAD
    TempDate as CalendarDate,
What: Start creating final calendar
Why: TempDate becomes CalendarDate (clearer name)
How: Rename during load

    Week(TempDate) as Week,
    Year(TempDate) as Year,
    Month(TempDate) as Month,
    Day(TempDate) as Day,
What: Extract basic date components
Why: Need for grouping and filtering
How: Built-in Qlik functions

    Ceil(Month(TempDate)/3) as Quarter,
What: Calculate quarter number
Why: Need for quarterly analysis
How:

    WeekDay(TempDate) as WeekDay,
What: Day of week as number
Why: Identify weekdays vs weekends
How: WeekDay() returns 0-6

    WeekName(TempDate) as WeekName,
    MonthName(TempDate) as MonthName,
What: Week and month identifiers
Why: Useful for grouping and display
How:

    'Q' & Ceil(Month(TempDate)/3) as QuarterName,
What: User-friendly quarter label
Why: "Q1" is clearer than "1"
How:

    Year(TempDate) & '-Q' & Ceil(Month(TempDate)/3) as YearQuarter,
What: Year-Quarter combination
Why: Compare same quarter across years (2024-Q1 vs 2025-Q1)
How: String concatenation

    // Fiscal Year (assuming fiscal year starts in April)
    If(Month(TempDate) >= 4, 
       Year(TempDate), 
       Year(TempDate) - 1) as FiscalYear,
What: Calculate fiscal year
Why: Business Ask #6 - fiscal year reporting
How:

    // Business Days Flag (Monday to Friday = 0 to 4 in Qlik)
    If(WeekDay(TempDate) >= 0 and WeekDay(TempDate) <= 4, 1, 0) as IsBusinessDay,
What: Flag for business days
Why: Compare weekday vs weekend performance
How:

    // Today Flag
    If(TempDate = Today(), 1, 0) as IsToday,
What: Flag for current date
Why: Highlight "today" in dashboards
How: Compares date to Today()
๐Ÿ’ก Only one date has IsToday = 1

    // Current Month Flag
    If(MonthName(TempDate) = MonthName(Today()), 1, 0) as IsCurrentMonth,
What: Flag for current month dates
Why: Easy "this month" filter
How: Compares MonthName values ("2024-01")
๐Ÿ’ก All dates in current month = 1

    // Current Year Flag
    If(Year(TempDate) = Year(Today()), 1, 0) as IsCurrentYear
What: Flag for current year dates
Why: YTD (Year-To-Date) analysis
How: Compares year values
๐Ÿ’ก All dates in 2026 = 1

RESIDENT TempCalendar;
What: Source is temporary calendar
Why: Transform dates into full calendar attributes
โ†’ Result: MasterCalendar with 31 rows, 17 attributes

DROP TABLE TempCalendar;
What: Remove temporary table
Why: Have final calendar, don't need temp anymore

// Step 4: Link calendar to sales data
LEFT JOIN (MasterCalendar)
LOAD DISTINCT
    OrderDate as CalendarDate,
    1 as HasSales
RESIDENT FACT_Sales_Final;
What: Mark which dates had sales
Why: Identify gaps, filter for active days
How:

Script 8: Save Transformed Data

/*
--------------------------------------------------------------------------------
SECTION 12: STORE TRANSFORMED DATA TO QVD FILES
Purpose: Save all transformed tables for use in visualization layer
Storage: Optimized QVD format for fast loading
--------------------------------------------------------------------------------
*/

// Save Fact Tables
STORE FACT_Sales_Final INTO [$(vTargetPath)FACT_Sales_Transformed.qvd] (qvd);
What: Save fact table for incremental loading
Why: Next run needs this as "history" to append to
How: STORE writes table to QVD file Path: lib://DataFiles/Transformed/FACT_Sales_Transformed.qvd
๐Ÿ’ก This becomes source for next incremental load

STORE FACT_Sales_Final INTO [$(vTargetPath)FACT_Sales_Complete.qvd] (qvd);
What: Save fact table for visualization
Why: Dashboard loads from this file
How: Same table, different filename Why two files?: Different purposes, may diverge over time
๐Ÿ’ก Separation of concerns - ETL vs visualization

// Save Stores dimension
STORE DIM_Stores_Raw INTO [$(vTargetPath)DIM_Stores_Transformed.qvd] (qvd);
What: Save stores dimension
Why: Need for visualization
How: Save raw version (wasn't transformed)
๐Ÿ’ก Still goes in Transformed folder for consistency

// Save SCD Table
STORE DIM_Customers_SCD_New INTO [$(vTargetPath)DIM_Customers_SCD.qvd] (qvd);
What: Save customer history
Why: Contains all versions - needed for next SCD run
How: Overwrites previous SCD file with updated history
๐Ÿ’ก This file grows over time as versions accumulate

// Save Master Calendar
STORE MasterCalendar INTO [$(vTargetPath)MasterCalendar.qvd] (qvd);
What: Save calendar dimension
Why: Reusable across reloads
How: Date range may change, but most dates stay same

// Drop tables before visualization load to avoid synthetic keys
DROP TABLE DIM_Stores_Raw;
DROP TABLE FACT_Sales_Final;
DROP TABLE DIM_Customers_SCD_New;
DROP TABLE MasterCalendar;
What: Remove all tables from memory
Why: Prepare for clean visualization load
How: DROP after STORE ensures data preserved
โ†’ Memory is now empty, ready for visualization phase
๐Ÿ’ก All work saved to disk, safe to drop

๐ŸŽจ Script 9: Load Data for Visualization

///$tab Load_Data_for_Viz
/*
--------------------------------------------------------------------------------
SECTION 13: RELOAD OPTIMIZED DATA FOR VISUALIZATION
Purpose: Load only necessary data for front-end analysis
Note: This creates the final data model for dashboard
--------------------------------------------------------------------------------
*/

// Load Final Fact Table
Sales:
LOAD * FROM [$(vTargetPath)FACT_Sales_Complete.qvd] (qvd);
What: Load complete sales data
Why: Center of star schema, contains all metrics
How:

// Load Products Dimension (qualify fields to avoid synthetic keys)
Products:
LOAD 
    ProductID,
What: Start loading products dimension
Why: Need product reference data
How: ProductID is key field (not renamed)

    ProductName as Dim_ProductName,
    Category as Dim_Category,
    SubCategory as Dim_SubCategory,
    Brand as Dim_Brand,
    UnitCost as Dim_UnitCost,
    ListPrice as Dim_ListPrice,
What: Load descriptive fields with Dim_ prefix
Why: Sales table has ProductName, Category, etc. - would create synthetic key
How: Rename all except key field
๐Ÿ’ก CRITICAL: Prevents synthetic keys

    Status,
    Product_LastModifiedDate,
    ProfitPerUnit as Dim_ProfitPerUnit,
    ProfitMarginPercent,
    PriceTier as Dim_PriceTier,
    IsActive
What: Load remaining fields (some with prefix, some without)
Why: Only fields that conflict with Sales need prefix
How: Status, IsActive don't conflict - keep original names

FROM [$(vTargetPath)DIM_Products_Transformed.qvd] (qvd);
What: Load from transformed QVD
Why: Has calculated fields (profit, margin, tier)
โ†’ Result: Products table with 10 rows
๐Ÿ’ก Relationship: Products.ProductID = Sales.ProductID

// Load Customers Dimension (qualify fields to avoid synthetic keys)
Customers:
LOAD 
    CustomerID,
    CustomerName as Dim_CustomerName,
    Country as Dim_Country,
    Region as Dim_Region,
    City as Dim_City,
    Segment as Dim_Segment,
    RegistrationDate,
    Customer_LastModifiedDate,
    CustomerTenureYears as Dim_CustomerTenureYears,
    LoyaltyTier as Dim_LoyaltyTier,
    MarketType as Dim_MarketType
FROM [$(vTargetPath)DIM_Customers_Transformed.qvd] (qvd);
What: Load customers with Dim_ prefix
Why: Same pattern as products - prevent synthetic keys
How: CustomerID not renamed (key), all others prefixed
โ†’ Result: Customers table with 10 rows
๐Ÿ’ก Relationship: Customers.CustomerID = Sales.CustomerID

// Load Stores Dimension
Stores:
LOAD 
    StoreID,
    StoreName,
    Store_Country,
    Store_Region,
    Store_City,
    StoreType,
    OpeningDate,
    Store_LastModifiedDate
FROM [$(vTargetPath)DIM_Stores_Transformed.qvd] (qvd);
What: Load stores dimension
Why: Reference data for store locations
How: Already prefixed with Store_ in Phase 2
โ†’ Result: Stores table with 5 rows
๐Ÿ’ก Standalone (no direct link to Sales in this scenario)

// Load Calendar
Calendar:
LOAD * FROM [$(vTargetPath)MasterCalendar.qvd] (qvd);
What: Load complete calendar
Why: Time intelligence for analysis
How: LOAD * safe - calendar fields are unique
โ†’ Result: Calendar table with 31 rows
๐Ÿ’ก Relationship: Calendar.CalendarDate = Sales.OrderDate

// Load SCD for historical analysis (fully qualify ALL fields)
Customer_History:
LOAD 
    CustomerID as History_CustomerID,
    CustomerName as History_CustomerName,
    Segment as History_Segment,
    Country as History_Country,
    Region as History_Region,
    City as History_City,
    LoyaltyTier as History_LoyaltyTier,
    EffectiveStartDate as History_StartDate,
    EffectiveEndDate as History_EndDate,
    IsCurrent as History_IsCurrent,
    VersionNumber as History_Version
FROM [$(vTargetPath)DIM_Customers_SCD.qvd] (qvd);
What: Load SCD table with History_ prefix on ALL fields
Why:

/*
================================================================================
 ๐ŸŽ‰ ETL SCRIPT COMPLETE - PRODUCTION READY ๐ŸŽ‰
 
 Final Data Model Summary:
 ========================
 
 ๐Ÿ“Š FACT TABLE:
 - Sales (40 records)
   - 15 initial orders + 5 incremental orders = 20 unique
   - Loaded twice in current run = 40 displayed
   
 ๐Ÿ“ DIMENSION TABLES:
 - Products (10 products) โ†’ Connected via ProductID
 - Customers (10 customers) โ†’ Connected via CustomerID
 - Calendar (62 dates) โ†’ Connected via OrderDate
 - Stores (5 stores) โ†’ Independent reference
 
 ๐Ÿ“œ HISTORY TABLE:
 - Customer_History (10 versions) โ†’ SCD Type 2 tracking
 
 โœ… All ETL Processes Implemented:
 - Extract from QVD files โœ“
 - Transform with business logic โœ“
 - Load with proper joins โœ“
 - Incremental load โœ“
 - SCD Type 2 โœ“
 - Master Calendar โœ“
 - Clean star schema โœ“
 
 STATUS: Ready for Dashboard Creation! ๐Ÿš€
================================================================================
*/
What: Documentation block summarizing completion
Why: Clear status indicator for developers
๐Ÿ’ก Good practice to document final state

Quick Reference Tables

Variable Reference

Variable Type Value Purpose
vSourcePath SET 'lib://DataFiles/' Source data location
vTargetPath SET 'lib://DataFiles/Transformed/' Transformed data location
vDiscountThreshold LET 0.10 Heavy discount threshold (10%)
vHighValueOrder LET 1000 High value order threshold ($1000)
vCurrentYear LET Year(Today()) Current year for comparisons
vCurrentDate LET Date(Today()) Current date for timestamps
vLastLoadDate LET Date(Today()-30) Last load date (simulated)
vActiveStatus SET 'Active' Active product status value
vCompletedStatus SET 'Completed' Completed order status value

Function Reference

Date Functions

Function Purpose Example Result
Today() Current date Today() 2026-01-29
Date(value, format) Format as date Date(OrderDate, 'YYYY-MM-DD') 2024-01-15
Year(date) Extract year Year('2024-01-15') 2024
Month(date) Extract month Month('2024-01-15') 1
Day(date) Extract day Day('2024-01-15') 15
WeekDay(date) Day of week (0-6) WeekDay('2024-01-15') 0 (Monday)
WeekName(date) Week identifier WeekName('2024-01-15') 2024/W03
MonthName(date) Month identifier MonthName('2024-01-15') 2024-01
Week(date) Week number Week('2024-01-15') 3

Math Functions

Function Purpose Example Result
Round(num, dec) Round to decimals Round(34.567, 2) 34.57
Ceil(num) Round up Ceil(1.1) 2
Floor(num) Round down Floor(1.9) 1
Min(field) Minimum value Min(OrderDate) Earliest date
Max(field) Maximum value Max(OrderDate) Latest date

String Functions

Function Purpose Example Result
& Concatenate 'Q' & 1 Q1
Upper(text) Uppercase Upper('hello') HELLO
Lower(text) Lowercase Lower('HELLO') hello

Control Functions

Function Purpose Syntax
If() Conditional If(condition, true_result, false_result)
Peek() Read from table Peek('Field', row, 'Table')
NoOfRows() Count rows NoOfRows('TableName')
FileSize() Check file exists FileSize('path/file.qvd')
IterNo() Loop counter Used in AUTOGENERATE/WHILE

Command Reference

Data Loading

Command Purpose Example
LOAD Load data LOAD Field1, Field2
FROM Specify source FROM [file.qvd] (qvd)
RESIDENT Load from memory RESIDENT TableName
INLINE Inline data LOAD * INLINE [...]
WHERE Filter rows WHERE ProcessedFlag = 0
as Rename field Field1 as NewName

Table Operations

Command Purpose Example
LEFT JOIN Add fields LEFT JOIN (Table1) LOAD ... RESIDENT Table2
CONCATENATE Append rows CONCATENATE (Table1) LOAD ... RESIDENT Table2
NoConcatenate Prevent auto-concat NoConcatenate Table: LOAD ...
DROP TABLE Remove table DROP TABLE TableName
STORE Save to QVD STORE Table INTO [file.qvd] (qvd)

Control Flow

Command Purpose Example
SET Text variable SET var = 'text'
LET Evaluated variable LET var = expression
IF...THEN...ELSE Conditional IF condition THEN ... ELSE ... ENDIF
AUTOGENERATE Generate rows AUTOGENERATE 1
WHILE Loop condition WHILE condition

Field Naming Conventions

Prefix Usage Example Purpose
Prod_ Product fields in joins Prod_ProductName Prevent synthetic keys during joins
Cust_ Customer fields in joins Cust_CustomerName Prevent synthetic keys during joins
Dim_ Dimension fields in viz Dim_ProductName Prevent synthetic keys in final model
Store_ Store-specific fields Store_Country Distinguish from customer country
History_ SCD fields History_CustomerID Isolate historical table
v Variables vSourcePath Identify variables

Common Patterns

Pattern 1: Load and Transform

DIM_Table_Transformed:
LOAD
    Key,
    Field1,
    Field2,
    CalculatedField as NewField
RESIDENT DIM_Table_Raw;

DROP TABLE DIM_Table_Raw;

Pattern 2: LEFT JOIN with Prefix

LEFT JOIN (FactTable)
LOAD
    KeyField,
    Field1 as Prefix_Field1,
    Field2 as Prefix_Field2
RESIDENT DimensionTable;

Pattern 3: Incremental Load

Table_Incremental_Raw:
LOAD * FROM [file.qvd] (qvd)
WHERE ProcessedFlag = 0;

// Transform...

NoConcatenate
Table_Incremental_Transformed:
LOAD ... RESIDENT Table_Incremental_Raw;

CONCATENATE (Table_Final)
LOAD * RESIDENT Table_Incremental_Transformed;

Pattern 4: Save Before Drop

STORE TableName INTO [path/file.qvd] (qvd);
DROP TABLE TableName;

Pattern 5: Generate Date Sequence

TempTable:
LOAD
    Date($(vMinDate) + IterNo() - 1) as DateField
AUTOGENERATE 1
WHILE $(vMinDate) + IterNo() - 1 <= $(vMaxDate);

๐ŸŽฏ Summary

This script reference provides complete line-by-line explanations for all core ETL scripts in the GlobalTech Electronics solution. Key takeaways:

  1. Variables: Centralize paths and business rules for maintainability
  2. Extraction: Load raw data with date formatting and field renaming
  3. Transformation: Apply business logic and create calculated fields
  4. Joins: Use LEFT JOIN with prefixed fields to prevent synthetic keys
  5. Incremental Load: Use ProcessedFlag and NoConcatenate for controlled appending
  6. SCD Type 2: Track historical changes with version numbers and effective dates
  7. Master Calendar: Generate comprehensive date dimension from data
  8. Persistence: Save transformed data to QVD for reuse and performance
  9. Visualization: Load with qualified field names for clean star schema

Result: Production-ready ETL solution with 0 synthetic keys, complete business logic, and optimized data model ready for dashboards.