Federated Healthcare Analytics Across Snowflake and Amazon S3 Tables Using Iceberg
Learn how to run federated healthcare analytics across Snowflake and Amazon S3 Tables using Apache Iceberg — no ETL pipelines or data movement required. This guide demonstrates how to join governed patient demographics in Snowflake with ICU admissions and clinical measurements stored in S3 Tables, enabling mortality risk scoring, readmission prediction, and ventilator utilization analysis while keeping sensitive data in place and governance controls intact.
Improving patient outcomes is one of the most consequential challenges facing modern healthcare. Clinicians and hospital administrators need timely, accurate insights: which patients are at highest risk of ICU mortality, who is likely to be readmitted within 30 days, and which cohorts are consuming the most ventilator hours. Answering these questions reliably requires a complete picture of the patient, covering demographics, real-time clinical measurements, and admission history. When these insights arrive too late or are built on incomplete data, the consequences are measured not in dollars but in lives.
The data needed to drive better outcomes is rarely in one place. In hospitals, patient demographic and administrative records often sit in governed, audited data warehouses with strict access controls. High-frequency clinical telemetry like continuous vital signs, lab results, and admission events is generated at a volume that demands scalable, cost-effective object storage. Healthcare data teams have historically resorted to brittle ETL pipelines or risky data duplication to bridge this gap, and neither approach ages well.
With Snowflake and Amazon S3 Tables , data engineers can run federated SQL analytics across both platforms, joining governed Snowflake records with Iceberg-native clinical data in S3 Tables, without moving sensitive patient data or breaking existing governance boundaries.
Snowflake is a cloud-native data platform designed for governed, high-performance analytics. It provides organizations with a secure, scalable environment for storing and querying structured data, with built-in access controls, auditing, and support for open table formats like Apache Iceberg. For healthcare teams, Snowflake often serves as the trusted analytical layer where sensitive demographic and administrative records can be managed under strict governance policies.
Amazon S3 Tables is a purpose-built storage solution for high-volume, high-frequency data workloads, offering native Apache Iceberg support directly within Amazon S3. It is designed to handle the scale demands of continuously generated data, such as clinical telemetry, lab results, and admission events, at low cost and without sacrificing query performance. Together with Snowflake, S3 Tables enables organisations to build a federated analytical architecture where each platform handles what it does best, without requiring data to be copied or consolidated into a single system.
Getting Started
To demonstrate how this works, we are using a synthetic patient dataset. In Snowflake, we have a table (patient_demographics) containing no identifiable information, but including characteristics like age, BMI, ethnicity, gender, and other fields that will assist with the analysis we will be doing later.

In addition to the demographic data in Snowflake, the Amazon S3 Tables store ICU admissions (icu_admissions) and clinical measurements (clinical_measurements). Combining these datasets enables a much richer picture of patient risk and outcomes.

To work with this data in Snowflake, we will access S3 Tables through the Amazon SageMaker Lakehouse Iceberg REST endpoint. This means we query the data directly from Snowflake without any ingestion step.
Note: To set up the Iceberg catalog integration, refer to the Snowflake documentation and this blog post that walks through the configuration and setup requirements. You will need to customize the configuration for your own environments and data requirements, but it is a good walk through.
Once the catalog integration is in place, you can mount the tables from S3 Tables so you can start querying them in Snowflake. For this example, I have prefixed the table names with "s3tables", so you can easily see where each table is stored. From your Snowflake environment, you can use the following SQL commands to mount the tables:
create iceberg table s3tables_icu_admissions CATALOG='glue_rest_catalog_integration' CATALOG_TABLE_NAME="icu_admissions" AUTO_REFRESH = TRUE;
create iceberg table s3tables_clinical_measurements CATALOG='glue_rest_catalog_integration' CATALOG_TABLE_NAME="clinical_measurements" AUTO_REFRESH = TRUE;
Once the tables have been mounted, they will appear in your catalog, alongside the other tables stored in Snowflake:

To verify that the tables have been mounted correctly, you view the table properties for the tables you just mounted. In the example below, we are looking at the properties for s3tables_icu_admissions, and you can click on the Data Preview to see a sample of the records available in the table in S3 Tables.

How Catalog Federation Works
Before we get started combining the data to do some analysis, it is worth understanding what is actually happening under the hood when Snowflake queries a table that lives in S3 Tables.
Apache Iceberg separates the concerns of storage, metadata, and compute. A table is not a monolithic object; it is a layered structure. At the bottom are Parquet data files sitting in S3. Above that is a layer of manifest files that track which data files belong to a snapshot, including column-level statistics like min/max values and null counts.
Above that is a metadata JSON file that records the current snapshot, the table schema, partition spec, and sort order. The catalog's only job is to track the current metadata file location for each table. Everything else is self-describing from that pointer downward.
When Snowflake mounts an Iceberg table via a REST catalog integration, it registers that catalog pointer locally. At query time, the following sequence happens:
- Snowflake calls the SageMaker Lakehouse Iceberg REST catalog endpoint to resolve the current metadata file location for the requested table
- It reads the metadata JSON to understand the schema, partition spec, and current snapshot
- It reads the relevant manifest list to find which manifest files cover the query's partition range
- It reads those manifests to identify the specific Parquet files needed, using column statistics to skip files that cannot satisfy the query's predicates
- It reads only those Parquet files directly from S3, applying any remaining filter predicates at the scan level
This means Snowflake is not pulling the entire table into its own storage. It is doing targeted, predicate-pushed reads against S3, guided by Iceberg's metadata layer. For a large clinical measurements table with billions of rows partitioned by admission date, a query filtering to a single month might read less than 1% of the physical data files.
The AUTO_REFRESH = TRUE setting tells Snowflake to periodically re-check the catalog for new snapshots, keeping its local metadata cache current as new data lands in S3 Tables. This is important for clinical telemetry workloads where measurements are being written continuously.
Cross-system joins work because both tables are now visible to Snowflake's query planner under a unified schema. When the planner encounters a join between patient demographics (native Snowflake) and s3tables_icu_admission (Iceberg via S3 Tables), for example, it builds a single execution plan that fetches the Iceberg data files it needs and joins them in Snowflake's compute layer. No intermediate staging table is created. The data from S3 is streamed directly into the join operation.
Bringing It All Together
Now that we have both tables mounted, we create a consolidated view joining patient demographics from Snowflake with ICU admissions and clinical measurements from S3 Tables. To create the (patient_icu_summary) view, you can use the following SQL in your Snowflake environment:
CREATE OR REPLACE VIEW HEALTH.RECORDS.PATIENT_ICU_SUMMARY ASSELECT p.PATIENT_ID, p.AGE, p.GENDER, p.ETHNICITY, p.BMI, p.SMOKING_STATUS, p.INSURANCE_TYPE, p.HAS_DIABETES, p.HAS_HYPERTENSION, p.HAS_HEART_DISEASE, p.HAS_COPD, p.HAS_KIDNEY_DISEASE, COUNT(DISTINCT a.ADMISSION_ID) AS TOTAL_ADMISSIONS, SUM(a.LENGTH_OF_STAY_DAYS) AS TOTAL_LOS_DAYS, AVG(a.LENGTH_OF_STAY_DAYS) AS AVG_LOS_DAYS, AVG(a.APACHE_SCORE) AS AVG_APACHE_SCORE, AVG(a.SOFA_SCORE) AS AVG_SOFA_SCORE, SUM(CASE WHEN a.ICU_MORTALITY THEN 1 ELSE 0 END) AS MORTALITY_COUNT, SUM(CASE WHEN a.READMISSION_WITHIN_30DAYS THEN 1 ELSE 0 END) AS READMISSION_COUNT, SUM(CASE WHEN a.REQUIRES_VENTILATION THEN 1 ELSE 0 END) AS VENTILATION_COUNT, SUM(CASE WHEN a.REQUIRES_DIALYSIS THEN 1 ELSE 0 END) AS DIALYSIS_COUNT, SUM(CASE WHEN a.VASOPRESSOR_USE THEN 1 ELSE 0 END) AS VASOPRESSOR_COUNT, AVG(m.HEART_RATE) AS AVG_HEART_RATE, AVG(m.BLOOD_PRESSURE_SYSTOLIC) AS AVG_BP_SYSTOLIC, AVG(m.BLOOD_PRESSURE_DIASTOLIC) AS AVG_BP_DIASTOLIC, AVG(m.OXYGEN_SATURATION) AS AVG_OXYGEN_SATURATION, AVG(m.TEMPERATURE_CELSIUS) AS AVG_TEMPERATURE, AVG(m.RESPIRATORY_RATE) AS AVG_RESPIRATORY_RATE, AVG(m.GLUCOSE_LEVEL) AS AVG_GLUCOSE, AVG(m.CREATININE) AS AVG_CREATININE, AVG(m.LACTATE) AS AVG_LACTATE, AVG(m.WHITE_BLOOD_CELL_COUNT) AS AVG_WBC FROM HEALTH.RECORDS.PATIENT_DEMOGRAPHICS pLEFT JOIN HEALTH.RECORDS.S3TABLES_ICU_ADMISSIONS a ON p.PATIENT_ID = a.PATIENT_IDLEFT JOIN HEALTH.RECORDS.S3TABLES_CLINICAL_MEASUREMENTS m ON a.ADMISSION_ID = m.ADMISSION_IDGROUP BY p.PATIENT_ID, p.AGE, p.GENDER, p.ETHNICITY, p.BMI, p.SMOKING_STATUS, p.INSURANCE_TYPE, p.HAS_DIABETES, p.HAS_HYPERTENSION, p.HAS_HEART_DISEASE, p.HAS_COPD, p.HAS_KIDNEY_DISEASEOnce this view is created, you will have a data structure that covers all 3 tables, and provides the ability to easily analyze the data that the view contains.

Determining Mortality Risk
Now that we have your unified view of the data, we are start looking at some of the outcomes we can drive with it.
In critical care, predicting patient mortality is a classification problem: given a set of input features, how likely is a patient to survive? APACHE II works like a batch scoring model, ingesting 12 physiological features (heart rate, temperature, creatinine, and others) alongside age and chronic health flags, then mapping the aggregated score to a predicted mortality probability. A score of 25 can correspond to a mortality rate of 38 to 73% depending on the patient cohort, much like how a classifier's output probability shifts based on the distribution it was calibrated on.
SOFA is better understood as a time-series model. It scores dysfunction across six organ systems and is recalculated daily; a rising score signals escalating mortality risk, similar to feature drift signaling a degrading ML system. Tracked longitudinally, mean SOFA achieves an AUC of up to 0.97, comfortably outperforming APACHE II's static AUC of around 0.80. Time-series features almost always beat point-in-time snapshots, and ICU scoring systems are a clear illustration of why.
To perform analysis on the combined dataset, we could open a SQL file and use a prompt in Cortex Code:
Prompt: Identify ICU patients with highest mortality risk using APACHE/SOFA scores from PATIENT_ICU_SUMMARY
This runs in two steps: identifying the top 20 highest mortality risk patients, then reviewing the risk distribution summary. Even from this synthetic dataset, correlations between ventilation requirements and vasopressor use emerge quickly. (A vasopressor is a drug that constricts blood vessels to raise dangerously low blood pressure, maintaining perfusion to vital organs.)
The output of the Cortex Code prompt is shown below:

These findings could allow clinicians to start looking at the care plans to see where improvements could be made. And now with the combined data view now in place, you can write a number of potential prompts to drill down further into the data set. These could include prompts like:
- Identify patients at highest risk of 30-day readmission based on length of stay, SOFA score, and comorbidities
- Compare average ICU length of stay across insurance types, broken down by primary diagnosis group
- Analyze ventilator utilization rates by age group and comorbidity profile to support capacity planning
- Identify which chronic conditions most strongly correlate with vasopressor use in ICU patients
- Segment patients by ethnicity and smoking status to surface disparities in ICU outcomes across different population cohorts
Snowflake Cortex Code makes it easy to ask natural language questions, and when combined with a view across both local and federated data sources, make it easy to get results fast.
No Data Movement Required
It is worth being explicit about what happened across all of the analysis above. Every query, from simple schema exploration through to multi-table joins, was executed against data that remained in its original location. The patient_demographics table stayed in Snowflake. The icu_admissions and clinical_measurements tables stayed in Amazon S3 Tables, registered in the Glue Data Catalog. No ETL pipeline was built. No data warehouse was populated. No copies were made.
For healthcare environments where data residency and access controls are tightly managed, this is a meaningful architectural advantage. The analytical surface area expands without the data footprint expanding with it. Access policies on the source systems remain the authoritative control plane; the federation layer simply brokers read access at query time, scoped to exactly the files Iceberg's metadata says are needed.
Summary
By federating across Snowflake and Amazon S3 Tables, care teams and data scientists gain access to a complete patient picture without copying data or building fragile ETL pipelines. With demographics, ICU admissions, and continuous clinical measurements queryable in a single SQL statement, it becomes practical to operationalize models that directly affect patient outcomes: ICU deterioration scoring, readmission risk stratification, ventilator demand forecasting, and cohort-level outcome analysis. These are the building blocks of a proactive clinical data platform, not incremental reporting improvements.
The tight integration between the two platforms makes this possible without compromising either system. S3 Tables handles high-frequency clinical telemetry at scale, Snowflake provides the governed analytical layer, and the Amazon SageMaker Lakehouse Iceberg REST endpoint bridges them without any data movement. Snowflake's query planner treats remote Iceberg tables as first-class objects, pushing predicates through the manifest layer to read only the data each query actually needs. Clinical data stays where it belongs, access controls remain on the source systems, and the analytical layer expands to meet the question.
Additional Sources
Amazon S3 Tables & SageMaker Lakehouse
Amazon S3 Tables Documentation
Official AWS documentation for S3 Tables, including setup and configuration
Amazon SageMaker Lakehouse Documentation
Overview of the SageMaker Lakehouse and the Iceberg REST catalog endpoint
Snowflake & Iceberg Integration
Snowflake Iceberg Tables Documentation
Official Snowflake documentation on creating and managing Iceberg tables, including external catalog integrations
Snowflake Cortex Documentation
Overview of Snowflake Cortex AI and natural language query capabilities
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article