Snowflake for Healthcare Data
A practical guide for healthcare data engineers and architects building on Snowflake — HIPAA-compliant schema design, PHI security controls, virtual warehouse sizing, data sharing patterns, and dbt integration for claims, clinical, and member data at scale.
Why Snowflake for Healthcare?
Snowflake has become the dominant cloud data platform for healthcare payers, health systems, and health tech companies — adopted by major health plans, pharmacy benefit managers, and healthcare analytics vendors for its combination of HIPAA-eligible compliance controls, near-zero administration overhead, and elastic compute that handles both daily incremental loads and peak-season HEDIS measure calculation runs without infrastructure planning.
The separation of storage and compute — Snowflake's defining architectural characteristic — is particularly valuable for healthcare workloads where data volumes are large and growing but query patterns are highly variable, from lightweight daily eligibility checks to full-population quality measure calculations running once per quarter across hundreds of millions of claim records.
This guide covers the practical decisions healthcare data engineers face when building on Snowflake — from HIPAA configuration and PHI security controls to schema design, warehouse sizing, and dbt integration patterns that teams actually use in production.
Snowflake Edition Selection for Healthcare
| Feature | Standard | Enterprise | Business Critical |
|---|---|---|---|
| BAA available | ❌ | ❌ | ✅ Required for PHI |
| Customer-managed keys (Tri-Secret) | ❌ | ❌ | ✅ HIPAA best practice |
| Private connectivity (PrivateLink) | ❌ | ✅ | ✅ |
| Time Travel (max days) | 1 day | 90 days | 90 days |
| Column-level security | ❌ | ✅ | ✅ |
| Row access policies | ❌ | ✅ | ✅ |
| Dynamic data masking | ❌ | ✅ | ✅ |
| Multi-cluster warehouses | ❌ | ✅ | ✅ |
| Database replication | ❌ | ✅ | ✅ |
| Recommended for PHI | ❌ No | ⚠️ Limited | ✅ Yes |
Recommendation: Healthcare organizations handling PHI should use Business Critical edition and sign a BAA with Snowflake before loading any protected health information.
Healthcare Schema Organization in Snowflake
Organize your Snowflake environment to reflect both data lifecycle stages and access control boundaries, using separate databases for each layer of your medallion architecture.
-- ════════════════════════════════════════════════ -- SNOWFLAKE HEALTHCARE ENVIRONMENT SETUP -- ════════════════════════════════════════════════ -- Database per lifecycle stage CREATE DATABASE raw_db COMMENT = 'Source-aligned raw ingested data — PHI present'; CREATE DATABASE staging_db COMMENT = 'Cleaned validated data — PHI present'; CREATE DATABASE marts_db COMMENT = 'Analytics-ready dimensional models — PHI controlled'; CREATE DATABASE reference_db COMMENT = 'ICD-10, NPI, taxonomy reference data — no PHI'; -- Domain schemas within each database CREATE SCHEMA raw_db.claims; CREATE SCHEMA raw_db.clinical; CREATE SCHEMA raw_db.member; CREATE SCHEMA raw_db.pharmacy; CREATE SCHEMA raw_db.eligibility; CREATE SCHEMA marts_db.claims; CREATE SCHEMA marts_db.member; CREATE SCHEMA marts_db.quality; CREATE SCHEMA marts_db.finance; -- Separate warehouses by workload type CREATE WAREHOUSE load_wh WAREHOUSE_SIZE = 'MEDIUM' AUTO_SUSPEND = 60 AUTO_RESUME = TRUE COMMENT = 'Data loading — isolated from query workloads'; CREATE WAREHOUSE transform_wh WAREHOUSE_SIZE = 'LARGE' AUTO_SUSPEND = 120 AUTO_RESUME = TRUE COMMENT = 'dbt transformations and ELT processing'; CREATE WAREHOUSE analyst_wh WAREHOUSE_SIZE = 'MEDIUM' MAX_CLUSTER_COUNT = 3 SCALING_POLICY = 'ECONOMY' AUTO_SUSPEND = 300 AUTO_RESUME = TRUE COMMENT = 'Interactive analyst queries and BI dashboards'; CREATE WAREHOUSE hedis_wh WAREHOUSE_SIZE = 'X-LARGE' AUTO_SUSPEND = 60 AUTO_RESUME = TRUE COMMENT = 'Monthly HEDIS and quality measure calculations';
PHI Security Controls in Snowflake
Dynamic Data Masking for PHI Fields
Mask PHI automatically for unauthorized roles without changing stored data
-- Create masking policy for member names
CREATE OR REPLACE MASKING POLICY phi_name_mask AS (val STRING)
RETURNS STRING ->
CASE
WHEN CURRENT_ROLE() IN ('PHI_ANALYST', 'DATA_ENGINEER', 'CLINICAL_OPS')
THEN val -- authorized roles see real value
WHEN CURRENT_ROLE() IN ('ANALYTICS_VIEWER', 'FINANCE_ANALYST')
THEN '***MASKED***' -- restricted roles see masked value
ELSE NULL -- all others see NULL
END;
-- Create masking policy for dates (PHI for patients over 89)
CREATE OR REPLACE MASKING POLICY phi_dob_mask AS (val DATE)
RETURNS DATE ->
CASE
WHEN CURRENT_ROLE() IN ('PHI_ANALYST', 'DATA_ENGINEER')
THEN val
ELSE DATE_TRUNC('year', val) -- show year only for non-PHI roles
END;
-- Apply masking policies to PHI columns
ALTER TABLE marts_db.member.dim_member
MODIFY COLUMN mbr_first_nm SET MASKING POLICY phi_name_mask;
ALTER TABLE marts_db.member.dim_member
MODIFY COLUMN mbr_last_nm SET MASKING POLICY phi_name_mask;
ALTER TABLE marts_db.member.dim_member
MODIFY COLUMN mbr_dob SET MASKING POLICY phi_dob_mask;Row Access Policy for Population-Level Access Control
Restrict analysts to specific member populations, plans, or regions
-- Access control mapping table
CREATE TABLE security_db.access_control.user_population_access (
user_role VARCHAR(100),
plan_cd VARCHAR(20),
allowed_state VARCHAR(2)
);
-- Row access policy — users only see their authorized population
CREATE OR REPLACE ROW ACCESS POLICY member_population_policy
AS (plan_cd VARCHAR, mbr_state_cd VARCHAR)
RETURNS BOOLEAN ->
CURRENT_ROLE() IN ('DATA_ENGINEER', 'PHI_ANALYST') -- full access roles
OR EXISTS (
SELECT 1
FROM security_db.access_control.user_population_access
WHERE user_role = CURRENT_ROLE()
AND (plan_cd = plan_cd OR plan_cd = 'ALL')
AND (allowed_state = mbr_state_cd OR allowed_state = 'ALL')
);
-- Apply to member dimension
ALTER TABLE marts_db.member.dim_member
ADD ROW ACCESS POLICY member_population_policy
ON (plan_cd, mbr_state_cd);Role Hierarchy for Healthcare Access Control
-- Healthcare role hierarchy CREATE ROLE phi_analyst; -- full PHI access, audited CREATE ROLE data_engineer; -- platform admin, full access CREATE ROLE claims_analyst; -- claims data, PHI masked CREATE ROLE quality_analyst; -- quality measures, PHI masked CREATE ROLE finance_analyst; -- aggregated financials only CREATE ROLE executive_viewer; -- summary dashboards only -- Grant schema-level access GRANT USAGE ON DATABASE marts_db TO ROLE claims_analyst; GRANT USAGE ON SCHEMA marts_db.claims TO ROLE claims_analyst; GRANT SELECT ON ALL TABLES IN SCHEMA marts_db.claims TO ROLE claims_analyst; -- Quality analysts see quality + member (masked) GRANT USAGE ON SCHEMA marts_db.quality TO ROLE quality_analyst; GRANT USAGE ON SCHEMA marts_db.member TO ROLE quality_analyst; GRANT SELECT ON ALL TABLES IN SCHEMA marts_db.quality TO ROLE quality_analyst; GRANT SELECT ON ALL TABLES IN SCHEMA marts_db.member TO ROLE quality_analyst; -- Warehouse access GRANT USAGE ON WAREHOUSE analyst_wh TO ROLE claims_analyst; GRANT USAGE ON WAREHOUSE analyst_wh TO ROLE quality_analyst; GRANT USAGE ON WAREHOUSE hedis_wh TO ROLE quality_analyst;
Performance Optimization for Healthcare Workloads
Clustering Keys for Claims Tables
Reduce scan costs for time-bounded claims queries — the most common healthcare analytics pattern
-- Cluster claims fact table on service date and payer
-- Most queries filter on date range + payer combination
ALTER TABLE marts_db.claims.fact_claim_header
CLUSTER BY (svc_dt_key, payer_key);
-- Cluster member eligibility on plan and effective date
ALTER TABLE marts_db.member.dim_member
CLUSTER BY (plan_cd, eff_dt);
-- Monitor clustering effectiveness
SELECT SYSTEM$CLUSTERING_INFORMATION(
'marts_db.claims.fact_claim_header',
'(svc_dt_key, payer_key)'
);
-- Query optimization: always filter on clustering key first
SELECT
m.plan_cd,
SUM(f.paid_amt) AS total_paid,
COUNT(DISTINCT f.mbr_key) AS member_count,
SUM(f.paid_amt) / COUNT(DISTINCT f.mbr_key) AS pmpm
FROM fact_claim_header f
JOIN dim_member m ON m.mbr_key = f.mbr_key
WHERE f.svc_dt_key BETWEEN 20250101 AND 20251231 -- clustering key first
AND f.payer_key = 42 -- clustering key second
GROUP BY m.plan_cd;Snowflake Dynamic Tables for Near-Real-Time Eligibility
Auto-refreshing eligibility summaries without manual pipeline scheduling
-- Dynamic table: current member eligibility summary
-- Refreshes automatically within 1 hour of source changes
CREATE OR REPLACE DYNAMIC TABLE marts_db.member.current_eligibility
TARGET_LAG = '1 hour'
WAREHOUSE = transform_wh
AS
SELECT
m.mbr_id,
m.plan_cd,
m.grp_nbr,
m.mbr_state_cd,
m.eff_dt,
m.exp_dt,
CASE
WHEN m.exp_dt IS NULL OR m.exp_dt > CURRENT_DATE
THEN TRUE ELSE FALSE
END AS is_active,
CURRENT_TIMESTAMP AS refreshed_at
FROM marts_db.member.dim_member m
WHERE m.curr_rec_flg = TRUE;dbt + Snowflake Healthcare Configuration
profiles.yml — Snowflake connection
healthcare_dw:
target: prod
outputs:
prod:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
private_key_path: "{{ env_var('SNOWFLAKE_PRIVATE_KEY_PATH') }}"
role: data_engineer
database: marts_db
warehouse: transform_wh
schema: claims
threads: 8
client_session_keep_alive: false
query_tag: dbt_healthcare_prod
dev:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
authenticator: externalbrowser
role: data_engineer
database: marts_db_dev
warehouse: transform_wh
schema: "dbt_{{ env_var('DBT_USER', 'dev') }}"
threads: 4dbt_project.yml — Healthcare model configuration
models:
healthcare_dw:
staging:
+materialized: view
+schema: staging
+tags: ['staging']
marts:
claims:
+materialized: incremental
+schema: claims
+cluster_by: ['svc_dt_key', 'payer_key']
+tags: ['claims', 'phi']
member:
+materialized: incremental
+schema: member
+tags: ['member', 'phi', 'scd2']
quality:
+materialized: table
+schema: quality
+tags: ['quality', 'hedis']
finance:
+materialized: table
+schema: finance
+tags: ['finance']
reference:
+materialized: table
+schema: reference
+database: reference_db
+tags: ['reference', 'no_phi']Validate column names against ISO-11179 standards before deploying using the Naming Auditor →
Snowflake Data Sharing for Healthcare
Snowflake Data Sharing enables healthcare organizations to share live data with trading partners — payers sharing with providers, health systems sharing with ACOs — without copying or moving data.
-- Create a data share for provider network partners CREATE SHARE provider_network_share COMMENT = 'Claims and quality data for in-network providers'; -- Grant access to specific schemas GRANT USAGE ON DATABASE marts_db TO SHARE provider_network_share; GRANT USAGE ON SCHEMA marts_db.quality TO SHARE provider_network_share; -- Share quality measure results (de-identified aggregate) GRANT SELECT ON TABLE marts_db.quality.provider_quality_summary TO SHARE provider_network_share; -- Add provider organization's Snowflake account ALTER SHARE provider_network_share ADD ACCOUNTS = 'provider_org_snowflake_account'; -- Note: PHI sharing requires BAA with receiving organization -- Apply row access policies to limit data to the receiving provider's patients
Snowflake Healthcare Best Practices
- →Sign the BAA before loading PHI: Never load protected health information into Snowflake before executing a Business Associate Agreement. Contact Snowflake sales for Business Critical edition and BAA signing — do not load PHI on trial or standard accounts.
- →Use key pair authentication in production: Disable username/password authentication for production service accounts. Configure RSA key pair authentication for dbt, Airflow, and pipeline service accounts to eliminate credential exposure risk.
- →Tag PHI columns with object tagging: Apply Snowflake object tags to PHI columns using CREATE TAG phi_indicator and ALTER TABLE MODIFY COLUMN SET TAG to enable automated data catalog classification and policy discovery across all PHI-containing columns.
- →Enable query activity monitoring: Configure Snowflake account usage alerts on QUERY_HISTORY to detect unusual PHI access patterns — large result set exports, off-hours queries, or access from unexpected IP ranges that may indicate unauthorized access.
- →Isolate dbt development schemas: Configure dbt user-specific development schemas (dbt_username) so developers can run and test models without affecting shared development or production data, and grant appropriate PHI access only to developer roles that require it for model testing.
- →Monitor credit consumption by query tag: Use query tags in your dbt profiles and pipeline connections to attribute Snowflake credit consumption to specific workloads, enabling accurate cost allocation by data domain and identification of unexpectedly expensive queries.
Frequently Asked Questions
Is Snowflake HIPAA compliant?
Snowflake offers a HIPAA-eligible configuration and will sign a Business Associate Agreement (BAA) with covered entities and business associates handling protected health information. However, HIPAA compliance is a shared responsibility — Snowflake secures the platform infrastructure while your organization must configure appropriate access controls, audit logging, encryption key management, and data governance policies. Simply running on Snowflake does not make your data HIPAA compliant; you must implement the technical safeguards described in this guide.
What Snowflake edition do I need for healthcare?
Healthcare organizations handling PHI should use Snowflake Business Critical or higher edition, which includes customer-managed encryption keys (Tri-Secret Secure), enhanced security controls, private connectivity options, and the ability to sign a BAA with Snowflake. Enterprise edition lacks customer-managed encryption and some compliance features required for strict HIPAA technical safeguard implementation. Business Critical also includes enhanced query result caching and multi-cluster warehouses that benefit high-concurrency healthcare analytics workloads.
How do I implement row-level security for PHI in Snowflake?
Use Snowflake Row Access Policies to restrict which rows a user or role can query based on policy logic evaluated at runtime. Create a policy that joins the querying user's role to an access control mapping table defining which member populations, plan codes, or geographic regions they are authorized to see. Apply the policy to all tables containing PHI using ALTER TABLE. Unlike view-based security, row access policies enforce restrictions even when users query tables directly, making them more robust for HIPAA minimum necessary access compliance.
How should I size Snowflake virtual warehouses for healthcare workloads?
Healthcare workloads typically require separate virtual warehouses for different workload types: an X-Small or Small warehouse for dbt model runs and lightweight transformations, a Medium or Large warehouse for interactive analyst queries and dashboard serving, an X-Large or larger warehouse for monthly HEDIS measure calculations and annual quality reporting that process hundreds of millions of claim records, and a separate warehouse for data loading to isolate ingestion from query performance. Use auto-suspend set to 1-5 minutes and auto-resume to minimize cost while maintaining responsiveness.
What is Snowflake Data Sharing and how can healthcare organizations use it?
Snowflake Data Sharing allows organizations to share live, read-only access to Snowflake data with other Snowflake accounts without copying or moving data. Healthcare use cases include payer-to-provider data sharing for care coordination, health system to ACO data feeds for value-based care performance reporting, public health agency data submission, and pharmaceutical company real-world evidence data access. Data sharing requires a BAA extension with receiving organizations when PHI is involved and should be implemented with appropriate row-level security to limit access to authorized data subsets.
How do I handle PHI in dbt models running on Snowflake?
Apply a layered approach: in raw and staging models, preserve PHI columns as-is with meta: {phi: true} tags in schema YAML to enable data catalog classification. In mart-level models consumed by analysts, apply Snowflake dynamic data masking policies to PHI columns so authorized roles see unmasked values while others see redacted output. Use dbt grants configuration to restrict which Snowflake roles can query each model. Never include PHI in dbt model tests that write results to results schemas without appropriate access controls on those schemas.
Should I use Snowflake Time Travel for healthcare audit requirements?
Yes — Snowflake Time Travel provides a straightforward mechanism for point-in-time data recovery and audit that satisfies several HIPAA audit control requirements. Configure a 90-day data retention period (available on Business Critical) for tables containing PHI to support breach investigation timelines. Time Travel enables you to reconstruct the state of member eligibility, claims data, or quality measure results as of any date within the retention window, supporting both regulatory audits and data pipeline debugging without maintaining separate audit table infrastructure.
What is the best Snowflake schema organization for a healthcare data platform?
Organize Snowflake databases and schemas to reflect both data lifecycle stages and access control boundaries. Use separate databases for RAW (source-aligned ingested data), STAGING (cleaned and validated), and MARTS (analytics-ready dimensional models). Within each database, create schemas by domain such as CLAIMS, CLINICAL, MEMBER, PHARMACY, and QUALITY. This structure enables fine-grained access control at the schema level, clear data lineage across lifecycle stages, and workload isolation where different teams have USAGE grants on different schemas aligned to their business domain.