Python · SQL · Web Dev · Java · AI/ML tracks launching soon — your one platform for all of IT

ELT and Medallion Architecture in Snowflake

Raw, Silver, Gold, ELT, dbt-style modeling, tests, lineage, ownership, marts, and production transformation patterns in Snowflake.

80 min September 2026
// Part 01 — The big picture

ELT Means Load First, Transform Inside Snowflake

ELT stands for Extract, Load, Transform. Data is extracted from source systems, loaded into the warehouse, and transformed after it lands. This is different from older ETL patterns where data is heavily transformed before it enters the warehouse. Snowflake is built for ELT because it can store raw data cheaply, scale compute separately, and run large SQL transformations inside the platform.

Medallion architecture is a practical way to organize ELT. It separates your warehouse into layers: Raw, Silver, and Gold. Raw keeps source-shaped data. Silver cleans and standardizes it. Gold turns it into business-ready facts, dimensions, and marts. The names matter less than the discipline: each layer has a different trust level and purpose.

Plain-English definition: Raw is what arrived, Silver is what we trust as cleaned data, and Gold is what the business should use for decisions.

ELT mental model
Sources
  application databases
  SaaS tools
  JSON files
  event streams
  partner exports
      |
      v
RAW schema
  source-shaped, replayable, lightly touched
      |
      v
SILVER schema
  typed, cleaned, deduped, standardized
      |
      v
GOLD schema
  facts, dimensions, marts, dashboards, data products
// Part 02 — Why layering exists

Layering Prevents Warehouse Chaos

Without layers, every team creates its own "clean" table. Finance has one revenue query. Product has another. Marketing has a third. Nobody knows which table is official. When a number is wrong, the team spends hours tracing copied SQL through dashboards and scratch schemas. Medallion architecture gives the warehouse a path from messy input to trusted output.

LayerTrust levelWho uses itMain question
RawLow trust, high fidelity.Data engineers and platform/debugging users.What exactly arrived from the source?
SilverMedium/high technical trust.Analytics engineers, data engineers, advanced analysts.What are the clean reusable entities?
GoldBusiness trust.BI users, analysts, executives, downstream data products.What should the business use for decisions?
Important distinction
Raw data is valuable because it is faithful to the source. Gold data is valuable because it is interpreted. Do not force one layer to do both jobs.
// Part 03 — Raw layer

Raw Stores Source-Shaped Data With Load Metadata

The Raw layer should preserve what arrived from the source with minimal transformation. You may add technical metadata such as source file, loaded_at, batch_id, record hash, or connector timestamp. You should avoid heavy business logic here. Raw is your evidence layer. If a downstream model breaks, Raw lets you replay and investigate.

Raw orders table
CREATE SCHEMA IF NOT EXISTS RETAIL.RAW;

CREATE OR REPLACE TABLE RETAIL.RAW.ORDERS (
  order_id STRING,
  customer_id STRING,
  order_ts STRING,
  status STRING,
  total_usd STRING,
  source_file STRING,
  source_row_number NUMBER,
  loaded_at TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP(),
  batch_id STRING
);
Raw design choiceGood practiceBad practice
ShapeKeep close to source shape.Rename and reinterpret every field immediately.
MetadataCapture file, row, load timestamp, batch id.Load rows with no lineage.
AccessRestrict to engineers and approved power users.Grant broad analyst access to sensitive raw payloads.
RetentionKeep enough history for replay and audit.Drop raw after making first dashboard.
QualityCheck load completeness and required keys.Assume loaded means correct.
// Part 04 — Silver layer

Silver Turns Source Data Into Reliable Entities

Silver is where technical cleaning happens. Types become real types. Names become consistent. Duplicate source records are resolved. Required keys are checked. Timestamps are normalized. Deleted records and late updates are handled. Silver should still be close to real-world entities: orders, customers, products, payments, shipments, tickets, sessions.

Silver orders with typing and dedupe
CREATE SCHEMA IF NOT EXISTS RETAIL.SILVER;

CREATE OR REPLACE TABLE RETAIL.SILVER.ORDERS AS
SELECT
  order_id::STRING AS order_id,
  customer_id::STRING AS customer_id,
  TRY_TO_TIMESTAMP_NTZ(order_ts) AS order_ts,
  LOWER(status)::STRING AS status,
  TRY_TO_NUMBER(total_usd, 12, 2) AS total_usd,
  source_file,
  source_row_number,
  loaded_at,
  batch_id
FROM RETAIL.RAW.ORDERS
WHERE order_id IS NOT NULL
QUALIFY ROW_NUMBER() OVER (
  PARTITION BY order_id
  ORDER BY loaded_at DESC, source_file DESC, source_row_number DESC
) = 1;
  • Cast important fields into stable Snowflake types.
  • Standardize names such as customer_id instead of CustomerID, custId, and CUSTOMER_ID in different tables.
  • Deduplicate using a deterministic business rule.
  • Keep source metadata so Silver records remain traceable.
  • Do not bury final business metrics here; save those for Gold.
// Part 05 — Gold layer

Gold Publishes Business-Ready Marts

Gold is for business consumption. It contains facts, dimensions, aggregates, and marts designed for specific decision-making workflows. A finance mart might define recognized revenue. A product mart might define active users. A support mart might define first-response time. Gold should have clear definitions, owners, tests, and access controls.

Gold daily revenue mart
CREATE SCHEMA IF NOT EXISTS RETAIL.GOLD;

CREATE OR REPLACE TABLE RETAIL.GOLD.DAILY_REVENUE AS
SELECT
  DATE(order_ts) AS order_date,
  COUNT(*) AS order_count,
  COUNT(DISTINCT customer_id) AS customer_count,
  SUM(total_usd) AS gross_revenue_usd,
  AVG(total_usd) AS avg_order_value_usd
FROM RETAIL.SILVER.ORDERS
WHERE status NOT IN ('cancelled', 'fraud')
GROUP BY 1;
Gold objectPurposeTypical consumer
FCT_ORDERSTransaction-level order facts.Analytics engineers and BI semantic layers.
DIM_CUSTOMERSCustomer attributes and lifecycle state.Marketing, support, product analytics.
DAILY_REVENUEExecutive revenue reporting.Finance dashboards and leadership.
PRODUCT_SALES_MARTProduct-level sales and units.Merchandising, inventory, product teams.
Gold is a contract
When people build dashboards and executive reports on Gold, changing definitions becomes a product decision. Document changes and communicate them like you would an API change.
// Part 06 — Facts and dimensions

Gold Often Uses Facts and Dimensions

A fact table records measurable events: orders, payments, shipments, page views, support tickets. A dimension table describes entities: customers, products, stores, dates, sales reps. This pattern makes analytics easier because metrics and descriptive attributes are separated but joinable.

Table typeContainsExample columnsMistake to avoid
FactEvents and measures.order_id, customer_id, order_date, revenue_usd.Putting every customer attribute into every order row.
DimensionEntity description.customer_id, signup_date, segment, region.Changing dimensions without history when history matters.
Aggregate martPre-computed summary.order_date, revenue_usd, order_count.Losing the definition of filters and exclusions.
Bridge tableMany-to-many relationships.customer_id, segment_id.Forcing complex relationships into one comma-separated field.
Fact and dimension example
CREATE OR REPLACE TABLE RETAIL.GOLD.DIM_CUSTOMERS AS
SELECT
  customer_id,
  MIN(order_ts) AS first_order_ts,
  MAX(order_ts) AS latest_order_ts,
  COUNT(*) AS lifetime_orders,
  SUM(total_usd) AS lifetime_revenue_usd
FROM RETAIL.SILVER.ORDERS
GROUP BY customer_id;

CREATE OR REPLACE TABLE RETAIL.GOLD.FCT_ORDERS AS
SELECT
  order_id,
  customer_id,
  DATE(order_ts) AS order_date,
  status,
  total_usd
FROM RETAIL.SILVER.ORDERS;
// Part 07 — dbt style

dbt Fits Naturally With Medallion Modeling

You can build medallion layers with plain SQL, tasks, stored procedures, or orchestration tools. Many Snowflake teams use dbt because it organizes SQL transformations as version-controlled models with dependencies, tests, documentation, and environment-aware deployments.

dbt-style model layout
models/
  staging/
    sources.yml
    stg_orders.sql
    stg_customers.sql
  intermediate/
    int_orders_enriched.sql
    int_customer_first_order.sql
  marts/
    finance/
      fct_orders.sql
      daily_revenue.sql
    product/
      product_sales_mart.sql
dbt-style SQL
-- models/staging/stg_orders.sql
SELECT
  order_id::STRING AS order_id,
  customer_id::STRING AS customer_id,
  TRY_TO_TIMESTAMP_NTZ(order_ts) AS order_ts,
  LOWER(status) AS status,
  TRY_TO_NUMBER(total_usd, 12, 2) AS total_usd
FROM {{ source('raw', 'orders') }}

-- models/marts/finance/daily_revenue.sql
SELECT
  DATE(order_ts) AS order_date,
  SUM(total_usd) AS revenue_usd
FROM {{ ref('stg_orders') }}
WHERE status NOT IN ('cancelled', 'fraud')
GROUP BY 1
dbt conceptSnowflake meaningWhy it matters
source()A declared raw input table.Documents source ownership and freshness.
ref()A dependency on another model.Builds lineage and correct execution order.
materializationView, table, incremental, ephemeral.Controls cost, speed, and storage.
testData assertion.Stops broken assumptions from reaching Gold.
docsHuman-readable model description.Makes tables reusable by other teams.
// Part 08 — Materialization choices

View, Table, or Incremental Model?

Not every transformation should become a physical table. Not every transformation should stay a view. Materialization is a tradeoff between freshness, cost, query speed, storage, and complexity. In Snowflake, this decision directly affects compute usage and user experience.

MaterializationWhat it doesUse whenAvoid when
ViewStores SQL, computes on query.Logic is light or data is small.Many dashboards repeatedly run expensive logic.
TableStores computed result.Output is reused often and rebuild is affordable.Data changes constantly and full rebuild is too costly.
Incremental tableProcesses only new/changed data.Large tables with append/update patterns.Unique keys and change logic are unclear.
Ephemeral/dbt CTEInlines logic into downstream SQL.Small helper transformations.Used repeatedly in many heavy downstream models.
Dynamic tableSnowflake-managed refresh to target lag.Declarative pipeline fits limitations.Refresh cost/behavior is not understood.
Do not table everything
Making every model a table can hide bad SQL by precomputing it, but it can also increase storage, compute, and maintenance. Choose materialization because of workload behavior, not habit.
// Part 09 — Tests

Tests Are Part of the Architecture

A medallion pipeline without tests is just a sequence of SQL statements. Tests define the promises each layer makes. Raw tests ask whether data arrived. Silver tests ask whether entities are valid. Gold tests ask whether metrics are trustworthy.

Snowflake quality tests
-- Silver: order_id should be unique and non-null.
SELECT order_id, COUNT(*) AS row_count
FROM RETAIL.SILVER.ORDERS
GROUP BY order_id
HAVING order_id IS NULL OR COUNT(*) > 1;

-- Silver: totals should be valid.
SELECT *
FROM RETAIL.SILVER.ORDERS
WHERE total_usd IS NULL OR total_usd < 0;

-- Gold: revenue should reconcile with Silver for the same business rule.
WITH silver_revenue AS (
  SELECT DATE(order_ts) AS order_date, SUM(total_usd) AS revenue_usd
  FROM RETAIL.SILVER.ORDERS
  WHERE status NOT IN ('cancelled', 'fraud')
  GROUP BY 1
)
SELECT g.order_date, g.gross_revenue_usd, s.revenue_usd
FROM RETAIL.GOLD.DAILY_REVENUE g
JOIN silver_revenue s USING (order_date)
WHERE ABS(g.gross_revenue_usd - s.revenue_usd) > 0.01;
Test typeExampleLayer
Not nullorder_id is not null.Silver and Gold.
Uniqueone row per order_id.Silver entity tables.
Accepted valuesstatus in completed/cancelled/fraud/refunded.Silver.
Relationshiporder.customer_id exists in customers.Silver/Gold.
ReconciliationGold revenue equals approved Silver logic.Gold.
Freshnesslatest load less than expected delay.Raw/Gold.
// Part 10 — Lineage

Lineage Explains Where a Number Came From

Lineage is the chain from source to final output. When someone asks why revenue changed, lineage tells you which raw files, Silver models, Gold marts, SQL definitions, and dashboard filters were involved. Without lineage, every metric dispute becomes a search party.

Manual lineage example
GOLD.DAILY_REVENUE
  depends on SILVER.ORDERS
    depends on RAW.ORDERS
      loaded from @ORDERS_STAGE
        copied from s3://company-orders/prod/orders/YYYY/MM/DD/

Definition:
  revenue includes orders where status NOT IN ('cancelled', 'fraud')
  revenue uses total_usd after source-system discount calculation
  order_date is DATE(order_ts) in UTC
  • Every Gold metric should point back to its source tables and business rule.
  • dbt ref/source graphs help automate lineage for SQL models.
  • Load metadata connects modeled rows to source files or batches.
  • Query history and access history provide operational lineage during incidents.
  • Lineage should be understandable to humans, not only tools.
// Part 11 — Ownership

Every Gold Table Needs an Owner

A table without an owner becomes a rumor. Someone uses it, nobody knows if it is correct, and nobody feels responsible when it breaks. Ownership is not just a Snowflake OWNERSHIP privilege. It is a product responsibility: definition, quality, access, cost, freshness, and communication.

ObjectTechnical ownerBusiness ownerWhy both matter
RAW.ORDERSData platform / ingestion team.Source application owner.Engineering owns load reliability; source owner owns meaning.
SILVER.ORDERSAnalytics engineering.Operations or commerce domain owner.Cleaning logic needs both SQL and domain context.
GOLD.DAILY_REVENUEAnalytics engineering.Finance.Metric definitions affect executive decisions.
GOLD.PRODUCT_SALES_MARTAnalytics engineering.Product or merchandising.Product hierarchy and exclusions need business approval.
Ownership metadata pattern
-- Example documentation fields to keep in dbt docs, a catalog, or a metadata table:
object_name: RETAIL.GOLD.DAILY_REVENUE
technical_owner: analytics-engineering@company.com
business_owner: finance-analytics@company.com
freshness_sla: available by 7:00 AM America/New_York
grain: one row per order_date
primary_consumers: executive revenue dashboard, finance monthly close
definition: excludes cancelled and fraud orders
// Part 12 — Freshness and SLAs

Freshness Is a Business Promise

A table can be perfectly modeled and still useless if it arrives too late. Freshness should be stated in business terms: the executive dashboard must be ready by 7:00 AM Eastern, customer support metrics must update every 15 minutes, finance close tables must be final by the third business day.

Freshness audit table
CREATE OR REPLACE TABLE OPS.DATASET_FRESHNESS (
  dataset_name STRING,
  expected_by STRING,
  latest_data_ts TIMESTAMP_NTZ,
  checked_at TIMESTAMP_NTZ,
  status STRING
);

INSERT INTO OPS.DATASET_FRESHNESS
SELECT
  'RETAIL.GOLD.DAILY_REVENUE',
  '07:00 America/New_York',
  MAX(order_date)::TIMESTAMP_NTZ,
  CURRENT_TIMESTAMP(),
  CASE
    WHEN MAX(order_date) >= CURRENT_DATE() - 1 THEN 'OK'
    ELSE 'STALE'
  END
FROM RETAIL.GOLD.DAILY_REVENUE;
SLA typeExampleHow to monitor
FreshnessDaily revenue ready by 7 AM ET.Max business date or loaded_at.
CompletenessAll store files arrived.Expected files versus loaded files.
AccuracyRevenue reconciles with payments.Reconciliation tests.
AvailabilityBI queries complete within target.Query history and dashboard health.
CostPipeline stays under monthly budget.Warehouse metering and resource monitors.
// Part 13 — Incremental vs full refresh

Not Every Model Should Rebuild From Scratch

Full refresh is simple: rebuild the whole target table. Incremental processing is efficient: process only new or changed records. The right choice depends on data volume, source behavior, correction patterns, and how expensive the transformation is.

StrategyBest forRiskSnowflake pattern
Full refreshSmall tables, dimensions, simple marts.Expensive when data grows.CREATE OR REPLACE TABLE AS SELECT.
Append incrementalImmutable events.Duplicates if retries are not handled.INSERT new records using watermark.
MERGE incrementalMutable entities such as orders/customers.Wrong key corrupts target.MERGE staging into Silver/Gold.
Streams/tasksNative change processing.Stream staleness and task failures.STREAM + TASK + MERGE.
Dynamic tablesDeclarative refresh use cases.Refresh mode/cost misunderstood.CREATE DYNAMIC TABLE.
Module connection
The next module, MERGE and idempotency, goes deep on incremental design. For medallion architecture, remember the principle: Raw should allow replay, Silver should be reliable, and Gold should be rebuildable or repairable.
// Part 14 — Security boundaries

Use Layers as Access Boundaries

Medallion layers are not only logical modeling layers. They are also useful security boundaries. Raw often contains sensitive fields and source-system mess. Silver may still have detailed customer data. Gold should expose approved, governed, documented outputs to broad users.

Layered grants
-- Broad users get Gold, not Raw.
GRANT USAGE ON DATABASE RETAIL TO ROLE ANALYST_READER;
GRANT USAGE ON SCHEMA RETAIL.GOLD TO ROLE ANALYST_READER;
GRANT SELECT ON ALL TABLES IN SCHEMA RETAIL.GOLD TO ROLE ANALYST_READER;
GRANT SELECT ON FUTURE TABLES IN SCHEMA RETAIL.GOLD TO ROLE ANALYST_READER;

-- Transformation role gets Raw and modeled schemas.
GRANT USAGE ON SCHEMA RETAIL.RAW TO ROLE DBT_TRANSFORMER;
GRANT SELECT ON ALL TABLES IN SCHEMA RETAIL.RAW TO ROLE DBT_TRANSFORMER;
GRANT USAGE, CREATE TABLE, CREATE VIEW ON SCHEMA RETAIL.SILVER TO ROLE DBT_TRANSFORMER;
GRANT USAGE, CREATE TABLE, CREATE VIEW ON SCHEMA RETAIL.GOLD TO ROLE DBT_TRANSFORMER;
LayerAccess postureReason
RawRestricted.May contain PII, duplicates, dirty records, and source-only context.
SilverModerately restricted.Useful to builders, but still detailed and not always business-approved.
GoldBroadest approved access.Designed for consumption, documentation, and governed metrics.
// Part 15 — Performance and cost

Medallion Design Affects Performance and Cost

Layering can save money or waste money depending on how it is implemented. If every model rebuilds every row every hour, cost explodes. If every dashboard repeats expensive raw parsing, cost explodes. Good medallion design precomputes stable business outputs, avoids repeated heavy work, and uses warehouses sized for the workload.

  • Use Raw for storage and replay, not for every dashboard query.
  • Use Silver to avoid repeating type casts, dedupe logic, and JSON parsing.
  • Use Gold to avoid repeating business metric logic in every dashboard.
  • Use separate warehouses for load, transform, BI, and ad hoc workloads when needed.
  • Use Query Profile and warehouse metering to find repeated expensive transformations.
Cost smell query
SELECT
  warehouse_name,
  user_name,
  query_text,
  total_elapsed_time / 1000 AS seconds_elapsed,
  bytes_scanned
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP())
  AND query_text ILIKE '%RAW.%'
  AND bytes_scanned > 1000000000
ORDER BY bytes_scanned DESC
LIMIT 20;
// Part 16 — Anti-patterns

Common Medallion Anti-Patterns

Avoid these
  • Letting dashboards query Raw because Gold is not ready.
  • Calling a schema Silver even though it has no tests, dedupe, or typing.
  • Creating five different Gold revenue tables with conflicting definitions.
  • Dropping Raw too early and losing replay/debugging ability.
  • Using one giant SQL model that jumps directly from raw source to executive metric.
  • Giving every analyst access to every layer.
  • Treating dbt tests as optional because the SQL compiled successfully.
  • Having no owners for Gold metrics used by leadership.
// Part 17 — End-to-end lab

Hands-On Lab: Build Orders Raw, Silver, and Gold

This lab is the minimum practical medallion project. You will create a raw table, clean it into Silver, publish a Gold mart, and write checks that prove the pipeline is usable.

Lab: create sample raw data
CREATE OR REPLACE DATABASE MEDALLION_LAB;
CREATE OR REPLACE SCHEMA MEDALLION_LAB.RAW;
CREATE OR REPLACE SCHEMA MEDALLION_LAB.SILVER;
CREATE OR REPLACE SCHEMA MEDALLION_LAB.GOLD;

CREATE OR REPLACE TABLE MEDALLION_LAB.RAW.ORDERS (
  order_id STRING,
  customer_id STRING,
  order_ts STRING,
  status STRING,
  total_usd STRING,
  loaded_at TIMESTAMP_NTZ
);

INSERT INTO MEDALLION_LAB.RAW.ORDERS VALUES
  ('O-1', 'C-1', '2026-09-01 10:00:00', 'completed', '100.00', CURRENT_TIMESTAMP()),
  ('O-2', 'C-2', '2026-09-01 11:00:00', 'cancelled', '50.00', CURRENT_TIMESTAMP()),
  ('O-1', 'C-1', '2026-09-01 10:05:00', 'completed', '100.00', DATEADD(minute, 1, CURRENT_TIMESTAMP())),
  ('O-3', 'C-1', 'bad timestamp', 'completed', '25.00', CURRENT_TIMESTAMP());
Lab: Silver and Gold
CREATE OR REPLACE TABLE MEDALLION_LAB.SILVER.ORDERS AS
SELECT
  order_id,
  customer_id,
  TRY_TO_TIMESTAMP_NTZ(order_ts) AS order_ts,
  LOWER(status) AS status,
  TRY_TO_NUMBER(total_usd, 12, 2) AS total_usd,
  loaded_at
FROM MEDALLION_LAB.RAW.ORDERS
WHERE order_id IS NOT NULL
QUALIFY ROW_NUMBER() OVER (
  PARTITION BY order_id
  ORDER BY loaded_at DESC
) = 1;

CREATE OR REPLACE TABLE MEDALLION_LAB.GOLD.DAILY_REVENUE AS
SELECT
  DATE(order_ts) AS order_date,
  COUNT(*) AS order_count,
  SUM(total_usd) AS revenue_usd
FROM MEDALLION_LAB.SILVER.ORDERS
WHERE status = 'completed'
  AND order_ts IS NOT NULL
GROUP BY 1;

Lab checks

  • Why does O-1 appear only once in Silver?
  • Why does O-3 not contribute to Daily Revenue?
  • Which query would catch the bad timestamp?
  • Should analysts query RAW.ORDERS or GOLD.DAILY_REVENUE?
  • What metadata would you add before making this production?
// Part 18 — Interview answer

How to Explain ELT and Medallion Architecture in an Interview

A strong answer sounds like this: In Snowflake, I prefer ELT because the warehouse can store raw source data and scale compute for transformations. I organize data into Raw, Silver, and Gold. Raw preserves source-shaped data with load metadata for replay. Silver standardizes types, names, deduplicates records, and creates reliable entities. Gold publishes business-ready marts, facts, dimensions, and metrics with clear owners and tests. I would use dbt or a similar workflow to manage SQL dependencies, tests, documentation, CI/CD, and environments. I would also include security boundaries, freshness monitoring, cost controls, and a backfill plan.

Questions you should answer out loud

  • What is the difference between ETL and ELT?
  • What belongs in Raw, Silver, and Gold?
  • Why should dashboards avoid querying Raw tables?
  • How do dbt refs, sources, tests, and docs support medallion architecture?
  • When would you choose a view versus a table versus an incremental model?
  • How would you monitor freshness and quality for a Gold table?

🎯 Key Takeaways

  • ELT loads data first and transforms it inside Snowflake.
  • Raw preserves source-shaped data and load metadata.
  • Silver creates clean, typed, deduped, reusable entities.
  • Gold publishes business-ready facts, dimensions, marts, and metrics.
  • Tests, documentation, lineage, ownership, and freshness are part of the architecture.
  • Layering also supports security, performance, cost control, and incident recovery.
Share

Discussion

0

Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.

Continue with GitHub
Loading...