Max Kemel
07/14/2026, 1:48 PMRocky
07/14/2026, 1:48 PMcatalog_name.database_name.table_name.
* Example:
sql
SELECT
ice.user_id,
sr.username,
ice.action
FROM iceberg_catalog.sales_db.logs AS ice
JOIN default_catalog.local_db.users AS sr
ON ice.user_id = sr.id
WHERE ice.event_date = '2023-10-01';
* Schema Differences:
* Data Types: StarRocks automatically maps Iceberg data types to StarRocks data types (e.g., Iceberg string to StarRocks varchar, long to bigint).
* Column Names: If names differ, use Aliases (AS) in your query or create a Unified View:
sql
CREATE VIEW unified_users AS
SELECT id, username FROM local_db.users
UNION ALL
SELECT user_id AS id, name AS username FROM iceberg_catalog.db.users;
2. How Filtering Works (Predicate Pushdown)
StarRocks does not just "download" the whole Iceberg table. It uses Predicate Pushdown to make queries efficient:
* Metadata Filtering: StarRocks reads Iceberg's metadata (manifest files) to skip entire files or partitions that don't match your WHERE clause.
* Storage-level Filtering: For Parquet or ORC files in Iceberg, StarRocks pushes filters down to the data files, reading only the necessary columns and rows (using min/max stats).
* Supported Operators: Standard filters like =, >, <, IN, BETWEEN, IS NULL, and AND/OR are all pushed down.
3. How Sorting Works
* Execution: Sorting (ORDER BY) is generally performed by StarRocks' BE (Backend) nodes.
* Optimization: If you join a local table with an Iceberg table, StarRocks' Cost-Based Optimizer (CBO) decides whether to sort before or after the join to minimize memory usage.
* Hidden Partitioning: StarRocks supports Iceberg's hidden partitioning, so if you sort or filter by a partition transformed column (like days(timestamp)), StarRocks can still optimize the scan.
4. How Pagination Works (LIMIT and OFFSET)
* Limit Pushdown: If you use LIMIT 10, StarRocks pushes this down to the scan level. Once the BE nodes have read 10 rows from the Iceberg files that satisfy your filters, they stop reading further.
* Offset: OFFSET is handled by the StarRocks execution engine. For example, LIMIT 10 OFFSET 100 tells StarRocks to read 110 rows and discard the first 100.
* Best Practice: For large-scale pagination (deep paging), avoid high OFFSET values. Instead, use a "seek" or "keyset" method (e.g., WHERE id > last_id_from_previous_page ORDER BY id LIMIT 10) for better performance.
5. Schema Evolution
If the schema in Iceberg changes (e.g., you added a column or renamed one in the Iceberg source), StarRocks handles this automatically:
* Automatic Sync: The Iceberg Catalog periodically refreshes its metadata.
* Consistency: You don't need to run ALTER TABLE in StarRocks; it will see the new schema from the Iceberg metadata automatically. You can control the refresh frequency using the property metastore_cache_refresh_interval_sec (default is 60s).
References
• knowledge/usage/best-practice.md
• data_source/External_table.md
• data_source/catalog/elasticsearch_catalog.mdMax Kemel
07/14/2026, 1:51 PMSELECT
ice.user_id,
sr.username,
ice.action
FROM iceberg_catalog.sales_db.logs AS ice
JOIN default_catalog.local_db.users AS sr
ON ice.user_id = sr.id
WHERE ice.event_date = '2023-10-01';
lets say in sr the field name is event_start_date, and in ice its event_date
how would I approach this where clause? I want it to filter from both tables
@RockyRocky
07/14/2026, 1:52 PMWHERE clause.
sql
SELECT
ice.user_id,
sr.username,
ice.action
FROM iceberg_catalog.sales_db.logs AS ice
JOIN default_catalog.local_db.users AS sr
ON ice.user_id = sr.id
WHERE ice.event_date = '2023-10-01' -- Pushed down to Iceberg (S3/HDFS)
AND sr.event_start_date = '2023-10-01'; -- Pushed down to StarRocks local storage
Why this works: StarRocks' optimizer sees two independent filters and pushes them both to their respective data sources. This minimizes the number of rows sent to the join engine.
──────────
Option 2: Join on the Date Column
If your logic implies that you only want to join rows where the dates match, you can include the date columns in the JOIN condition. StarRocks’ Cost-Based Optimizer (CBO) will then perform Predicate Propagation.
sql
SELECT
ice.user_id,
sr.username,
ice.action
FROM iceberg_catalog.sales_db.logs AS ice
JOIN default_catalog.local_db.users AS sr
ON ice.user_id = sr.id
AND ice.event_date = sr.event_start_date -- Logic: Dates must match
WHERE ice.event_date = '2023-10-01';
Why this works: Because of the equality ice.event_date = sr.event_start_date, the optimizer automatically infers that sr.event_start_date must also be '2023-10-01'. It will push that filter down to the local table automatically.
──────────
Option 3: Using a CTE (Cleanest for Schema Differences)
If you have many queries and want to hide the different column names, use a Common Table Expression (CTE). This makes the query logic look "unified" while still allowing the optimizer to push filters down.
sql
WITH unified_logs AS (
SELECT user_id, event_date, action FROM iceberg_catalog.sales_db.logs
),
unified_users AS (
SELECT id, event_start_date AS event_date, username FROM default_catalog.local_db.users
)
SELECT
l.user_id,
u.username,
l.action
FROM unified_logs l
JOIN unified_users u ON l.user_id = u.id
WHERE l.event_date = '2023-10-01'
AND u.event_date = '2023-10-01';
Summary of Performance Behaviors:
* Predicate Pushdown: In all three options, StarRocks will use Iceberg manifest files to skip irrelevant data files and use StarRocks' zone maps/indexes to skip local data.
* Runtime Filters: If one table is significantly smaller, StarRocks may generate a "Runtime Filter" (like a Bloom Filter) based on the user_id from the smaller table and push it to the scan of the larger table to further reduce I/O.
References
• using_starrocks/accelerate-cardinality-preserving-joins.mdMax Kemel
07/14/2026, 1:56 PMRocky
07/14/2026, 1:57 PMUNION ALL to combine data from StarRocks (hot data) and Iceberg (cold data). This is a common "Hot/Cold" architecture pattern in StarRocks.
1. Using UNION ALL for Time-Based Split
You can create a unified view that handles the logic of where the data comes from. For your 7-day/23-day split, you should define the time boundaries clearly in the view to ensure StarRocks only queries the necessary source.
Example View Definition:
sql
CREATE VIEW unified_events AS
-- Last 7 days from StarRocks
SELECT
user_id,
event_start_date AS start_date, -- Alias here
action
FROM default_catalog.local_db.users
WHERE event_start_date >= CURRENT_DATE() - INTERVAL 7 DAY
UNION ALL
-- Rest (days 8 to 30) from Iceberg
SELECT
user_id,
event_date AS start_date, -- Same alias here
action
FROM iceberg_catalog.sales_db.logs
WHERE event_date >= CURRENT_DATE() - INTERVAL 30 DAY
AND event_date < CURRENT_DATE() - INTERVAL 7 DAY;
2. How Filtering and Aliases Work
When you query this view with a WHERE clause on the alias start_date, StarRocks' Cost-Based Optimizer (CBO) performs Predicate Pushdown:
* Logic: If you run SELECT * FROM unified_events WHERE start_date = '2023-10-01', the engine "unwraps" the view.
* Translation: It translates the filter back to the original columns. It will apply event_start_date = '2023-10-01' to the StarRocks branch and event_date = '2023-10-01' to the Iceberg branch.
* Pruning: If the filter is '2023-10-01' and that date only falls into the Iceberg range (e.g., it's 20 days ago), StarRocks is smart enough to prune the StarRocks branch entirely and only scan Iceberg.
3. Sorting and Pagination across Union
* Sorting (ORDER BY): When you sort the results of a UNION ALL, StarRocks first fetches the filtered results from both sources and then performs a merge-sort or a global sort in the BE (Backend) nodes.
* Pagination (LIMIT & OFFSET):
* Limit Pushdown: If you use LIMIT 10, StarRocks pushes LIMIT 10 down to both the Iceberg and StarRocks scans.
* Efficiency: Each source only sends back 10 rows. The StarRocks coordinator then takes those 20 rows (10 from each), sorts them if needed, and returns the final top 10.
* Offset: Just like a single table, OFFSET 100 LIMIT 10 will require the engine to process 110 rows from the unioned set and discard the first 100.
4. Summary of Why This Works Well
1. Schema Decoupling: The View acts as a mapping layer. You can change the underlying table names or column names in Iceberg, and you only need to update the View definition once.
2. Performance: Because StarRocks treats the Iceberg catalog as a first-class citizen, it uses the same vectorized execution engine for both branches.
3. Transparency: Your BI tools or applications just see one table (unified_events) and don't need to know that data is physically split between local disks and S3/HDFS.
Pro-Tip: If you find the Iceberg part of the query is slower than you'd like, you can create an Asynchronous Materialized View on top of the Iceberg table to cache the "cold" data in StarRocks' native format while still keeping it logically separated.
References
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.md
• using_starrocks/async_mv/use_cases/create_partitioned_materialized_view.md
• data_source/feature-support-data-lake-analytics.md
• using_starrocks/async_mv/use_cases/data_lake_query_acceleration_with_materialized_views.mdMax Kemel
07/14/2026, 1:59 PMevent_date AS start_date,
I dont see the usage of the alias in the where clause, why so?Rocky
07/14/2026, 1:59 PMstart_date used in the WHERE clauses inside the View definition is due to the standard SQL Order of Operations.
1. Why you use original names inside the View
In SQL, the WHERE clause is evaluated before the SELECT clause.
• The engine first filters the data (WHERE).
• Then it picks and renames the columns (SELECT ... AS ...).
Because the alias start_date doesn't "exist" yet when the WHERE filter is running, you must use the original column names from the physical tables (event_date and event_start_date).
sql
-- Inside the view definition, you use original names:
CREATE VIEW unified_events AS
SELECT event_start_date AS start_date FROM local_table
WHERE event_start_date > '2023-01-01' -- Alias 'start_date' not yet available
UNION ALL
SELECT event_date AS start_date FROM iceberg_table
WHERE event_date > '2023-01-01'; -- Alias 'start_date' not yet available
2. Why you use the alias when querying the View
Once the view is created, start_date becomes a "real" column name for that view. When you query the view, you must use the alias because that is the only name the view exposes.
sql
-- When querying the view, you use the alias:
SELECT *
FROM unified_events
WHERE start_date = '2023-10-01'; -- Here you use the alias!
3. How StarRocks handles this (Predicate Pushdown)
Even though you query using the alias start_date, StarRocks is smart enough to "map" that filter back down to the original columns in the underlying tables.
1. You run: SELECT ... WHERE start_date = '2023-10-01'
2. StarRocks looks at the View definition.
3. It sees start_date comes from event_start_date in the local table and event_date in the Iceberg table.
4. It pushes the filter down as:
• WHERE event_start_date = '2023-10-01' for the StarRocks scan.
• WHERE event_date = '2023-10-01' for the Iceberg scan.
Summary
* Defining the View: Use original names in WHERE.
* Querying the View: Use the alias in WHERE.
* Result: Efficient filtering happens at the source level (S3/Local Disk) for maximum performance.
References
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdMax Kemel
07/14/2026, 2:09 PMsql
-- Inside the view definition, you use original names:
CREATE VIEW unified_events AS
SELECT event_start_date AS start_date FROM local_table
WHERE event_start_date > '2023-01-01' -- Alias 'start_date' not yet available
UNION ALL
SELECT event_date AS start_date FROM iceberg_table
WHERE event_date > '2023-01-01'; -- Alias 'start_date' not yet available
does the select from local_table
and from iceberg_table
should be the same for the union to succeed?
in starrocks we have a JSON field and in iceberg the table is this JSON but flattened into columnsRocky
07/14/2026, 2:09 PMUNION ALL to succeed, the number of columns and the data types of those columns must match (or be compatible). The column names do not have to match (the view will use the names from the first SELECT statement).
Since your StarRocks table uses a JSON field and your Iceberg table is flattened, you have two ways to "sync" them.
Option 1: Flatten the StarRocks JSON (Recommended)
If you want to treat the data as individual columns, you can extract fields from the StarRocks JSON using the -> operator or get_json_int/string functions.
sql
CREATE VIEW unified_events AS
-- StarRocks: Extract fields from JSON to match Iceberg columns
SELECT
user_id,
event_start_date AS start_date,
data_json->'action' AS action, -- Extracting from JSON
CAST(data_json->'amount' AS INT) AS amount
FROM default_catalog.local_db.users
WHERE event_start_date >= CURRENT_DATE() - INTERVAL 7 DAY
UNION ALL
-- Iceberg: Already flattened
SELECT
user_id,
event_date AS start_date,
action, -- Already a column
amount -- Already a column
FROM iceberg_catalog.sales_db.logs
WHERE event_date < CURRENT_DATE() - INTERVAL 7 DAY;
Option 2: Package Iceberg into JSON
If your application expects a single JSON blob, you can use the json_object function to package the Iceberg columns into a JSON format that matches StarRocks.
sql
CREATE VIEW unified_events AS
-- StarRocks: Use the native JSON field
SELECT
user_id,
event_start_date AS start_date,
data_json
FROM default_catalog.local_db.users
UNION ALL
-- Iceberg: Create a JSON object from columns
SELECT
user_id,
event_date AS start_date,
-- Creates {"action": "login", "amount": 100}
json_object('action', action, 'amount', amount) AS data_json
FROM iceberg_catalog.sales_db.logs;
Which one should you choose?
* Choose Option 1 (Flattening) if you frequently filter or aggregate by specific fields (like amount). StarRocks can optimize these extracted fields much better.
* Choose Option 2 (Packaging) if your schema changes very often and you want the flexibility of JSON without updating the view every time a new column is added to Iceberg.
Key Rules for `UNION ALL`:
1. Column Count: Must be identical in both SELECT blocks.
2. Order: The engine matches columns by position (1st to 1st, 2nd to 2nd), not by name.
3. Data Types: If one side is a VARCHAR and the other is an INT, StarRocks will try to convert them to a common type (like VARCHAR). However, complex types like JSON generally need to be matched with other JSON types or converted to VARCHAR.
References
• sql-reference/sql-statements/table_bucket_part_index/SELECT.md
• using_starrocks/Flat_json.md
References
• using_starrocks/Flat_json.mdMax Kemel
07/14/2026, 2:13 PMRocky
07/14/2026, 2:13 PMSTRUCT (row) types in a UNION ALL, but you must ensure the data types match between the two sides of the union.
Since your StarRocks side uses a JSON column and your Iceberg side has a nested STRUCT, you have two primary ways to align them:
Option 1: Cast the Iceberg STRUCT to JSON (Easiest)
If your StarRocks table stores everything in a single JSON column, the simplest approach is to cast the Iceberg nested struct to the JSON type. StarRocks will automatically convert the nested structure into a JSON object.
sql
CREATE VIEW unified_events AS
-- StarRocks side (already JSON)
SELECT
event_id,
metadata -- This is a JSON column
FROM local_db.events
UNION ALL
-- Iceberg side (nested STRUCT)
SELECT
event_id,
CAST(user_details AS JSON) -- Converts STRUCT to JSON
FROM iceberg_catalog.db.events;
Option 2: Define a Matching STRUCT on both sides
If you prefer strict typing (which is faster for performance), you can define the specific structure on both sides. StarRocks supports the STRUCT data type.
sql
CREATE VIEW unified_events AS
-- StarRocks: Cast JSON to a specific STRUCT
SELECT
event_id,
CAST(metadata AS STRUCT<user_id INT, info STRUCT<city STRING, zip INT>>) as user_info
FROM local_db.events
UNION ALL
-- Iceberg: Use the native STRUCT (ensure the sub-field names/types match)
SELECT
event_id,
user_details -- This is already a nested STRUCT in Iceberg
FROM iceberg_catalog.db.events;
Important Considerations:
1. Field Order in Structs: When unioning two STRUCT types, StarRocks matches them by position and type, not necessarily by the name of the sub-fields. Ensure the order of fields in your CAST or Iceberg schema is identical.
2. Accessing Nested Data: Once you've unioned them into a view, you can access the nested fields using the dot notation regardless of whether the underlying source was JSON or STRUCT:
* SELECT user_info.info.city FROM unified_view
3. Variant Type (v3.3+): If your Iceberg table uses very complex or evolving schemas, StarRocks 3.3+ introduced the VARIANT type. You can cast both the StarRocks JSON and the Iceberg Struct to VARIANT for a completely schema-less union.
Recommendation: If the schema of the nested struct is stable, Option 2 (STRUCT) is faster. If the schema changes often or has many optional fields, Option 1 (JSON) is much more flexible.
References
• sql-reference/data-types/semi_structured/VARIANT.md
• sql-reference/data-types/semi_structured/JSON.md
• using_starrocks/Flat_json.md
• data_source/iceberg_catalog.md
-source/iceberg_catalog)
References
• using_starrocks/Flat_json.md
• sql-reference/data-types/semi_structured/VARIANT.md
• sql-reference/data-types/semi_structured/JSON.mdEliezer Talker
07/14/2026, 8:56 PMRocky
07/14/2026, 8:56 PMCREATE PIPE feature) is designed specifically for loading data into internal StarRocks tables (OLAP tables). It cannot be used to load data into an external Iceberg table.
Here is the breakdown of how these features work and what you can do instead:
1. Why PIPE doesn't work for Iceberg
The Pipe feature (CREATE PIPE) is an automated background management object. In StarRocks, "loading" refers to the process of ingesting data into its own high-performance storage engine. Writing data to an external catalog like Iceberg is technically a "Sink" or "Unload" operation.
* The PIPE mechanism tracks state (which files are loaded) specifically for Internal tables.
* The StarRocks documentation explicitly states that INSERT INTO ... SELECT FROM FILES for loading targets a destination internal table.
2. The Alternative: One-off INSERT INTO
While you cannot use a PIPE, you can manually run a standard INSERT INTO to move data from Parquet files on S3 into an Iceberg table. StarRocks acts as the compute engine to process the Parquet and write it to the Iceberg format.
Example:
sql
-- This works as a manual command, but NOT inside a CREATE PIPE
INSERT INTO iceberg_catalog.target_db.target_table
SELECT * FROM FILES(
"path" = "<s3://my-bucket/incoming-parquet/*.parquet>",
"format" = "parquet",
"aws.s3.region" = "us-east-1",
"aws.s3.access_key" = "...",
"aws.s3.secret_key" = "..."
);
3. Summary of Support
Feature | Target: Internal Table | Target: Iceberg Table
INSERT INTO ... SELECT FROM FILES | ✅ Supported | ✅ Supported (v3.1+)
CREATE PIPE | ✅ Supported | ❌ Not Supported
Continuous Auto-Ingest | ✅ Supported (via Pipe) | ❌ Not Supported
Recommendation
If you need to continuously move data from S3 to Iceberg, you typically wouldn't use StarRocks' PIPE. Instead, you would:
1. Use an external orchestrator (like Airflow or a Cron job) to trigger the INSERT INTO iceberg_table SELECT FROM FILES() command periodically.
2. Load into StarRocks first: If you need the extreme performance of StarRocks for queries, use the PIPE to load into a local table, and then occasionally "sync" that data to Iceberg if needed for a data lake.
References
• loading/loading_introduction/feature-support-loading-and-unloading.md
• sql-reference/sql-statements/loading_unloading/pipe/CREATE_PIPE.md
• data_source/catalog/iceberg/DML.md
• loading/InsertInto.md
References
• loading/loading_introduction/feature-support-loading-and-unloading.md
• sql-reference/sql-statements/loading_unloading/pipe/CREATE_PIPE.md
• data_source/External_table.md
• sql-reference/information_schema/pipes.md