Lý Thành
07/14/2026, 3:04 AMRocky
07/14/2026, 3:05 AMLý Thành
07/14/2026, 3:33 AMlosrep.application by name SELECT * FROM dmapp.losrep_application a where a.id > (select MAX(b.id ) from losrep.application b) or a.last_updated_date > (select DATE_SUB(MAX(c.last_updated_date), INTERVAL 1 DAY) from losrep.application c)
this query will run correct if application tabe have same colume as source losrep_application table, if source table add more colume, then this will failled, how to by pass new colume from source, but still using select *Rocky
07/14/2026, 3:34 AMINSERT INTO ... BY NAME query is resilient to new columns added to the source table while still using a wildcard-style selection, you have a few options depending on your StarRocks version.
*1. Use SELECT * EXCLUDE (StarRocks v4.0+)*
Starting with StarRocks v4.0, you can use the EXCLUDE keyword to select all columns except for the ones you want to ignore. This allows you to "bypass" specific new columns without listing every single desired column:
sql
INSERT INTO `losrep`.`application` BY NAME
SELECT * EXCLUDE (new_column_name)
FROM `dmapp`.`losrep_application` a
WHERE a.id > (SELECT MAX(b.id) FROM `losrep`.`application` b)
OR a.last_updated_date > (SELECT DATE_SUB(MAX(c.last_updated_date), INTERVAL 1 DAY) FROM losrep.application c);
2. Explicit Column Listing (Recommended Best Practice)
For versions prior to v4.0, StarRocks' BY NAME clause requires the columns produced by the SELECT statement to have a corresponding home in the target table. If SELECT * returns more columns than the target table contains, the query will likely fail with a column count mismatch or mapping error.
The most robust way to prevent failures when the source schema changes is to explicitly list the columns in the SELECT statement. This ensures that only the columns that exist in both tables are ever pulled:
sql
INSERT INTO `losrep`.`application` BY NAME
SELECT id, last_updated_date, col1, col2 -- explicitly list columns
FROM `dmapp`.`losrep_application` a
...
3. Note on FILES() vs. Internal Tables
If you were loading data from external files (like S3/HDFS) using the FILES() function, StarRocks provides a property called enable_push_down_schema. When set to true, SELECT * is automatically "reshaped" to only include columns that exist in the target table:
sql
-- This works only for FILES(), not currently for table-to-table
INSERT INTO target_table PROPERTIES ("enable_push_down_schema" = "true")
SELECT * FROM FILES(...);
For internal table-to-table transfers (as in your case), this automatic "reshaping" is not yet standard for regular SELECT *.
Summary Recommendation
* If you are on v4.0+: Use SELECT * EXCLUDE (names_of_new_columns).
* If you want full automation: There is currently no "auto-trim" property for table-to-table INSERT that ignores unknown columns in a SELECT *. You should list the specific columns you need. This is generally preferred for production pipelines as it prevents silent data loss or unexpected behavior when schemas evolve.
* Alternative: You can create a View on the source table that only exposes the columns currently in the destination. If the source table adds columns, the View remains stable and the INSERT won't fail.
References
• administration/management/FE_configuration.md
• loading/InsertInto.md
• sql-reference/sql-functions/table-functions/files.mdLý Thành
07/14/2026, 3:59 AMSELECT * EXCLUDE (names_of_new_columns), this way not work, as i don't known new colume name in futureRocky
07/14/2026, 4:00 AMSELECT * style query without it failing, there are a few strategies in StarRocks.
The Problem
When you use INSERT INTO ... BY NAME SELECT _ FROM source_table, StarRocks expands the _ to all columns currently in the source table. If the source table adds a new column that does not exist in the target table, the query will fail because the result set of the SELECT statement contains columns that the INSERT engine doesn't know where to place.
Solution 1: Use a Stable View (Recommended for Automation)
If you want to avoid updating your SQL query every time the source changes, you can create a View that acts as a filter. This allows your INSERT query to remain SELECT * while the View handles the column mapping.
1. Create a view that selects only the columns that currently exist in your destination table:
sql
CREATE VIEW losrep.v_application_source AS
SELECT id, last_updated_date, col1, col2 -- list only destination columns
FROM dmapp.losrep_application;
2. Change your query to select from the view:
sql
INSERT INTO `losrep`.`application` BY NAME
SELECT * FROM `losrep`.`v_application_source` a
WHERE a.id > (...) OR a.last_updated_date > (...);
Why this works: If the source table dmapp.losrep_application adds a new column, the View remains stable and ignores it. Your INSERT query will continue to run correctly. You only need to update the View definition if you actually want to start including a new column in your destination.
Solution 2: Explicit Column Selection (Best Practice)
In production ETL pipelines, it is generally considered a best practice to explicitly list the columns in your SELECT statement rather than using *. While it requires more typing initially, it makes your pipeline "immune" to schema changes in the source table.
sql
INSERT INTO `losrep`.`application` BY NAME
SELECT id, last_updated_date, col1, col2 -- Explicitly list columns
FROM `dmapp`.`losrep_application` a
...
*Solution 3: StarRocks v4.0+ `SELECT * EXCLUDE`*
If you are on StarRocks v4.0 or later, you can use the EXCLUDE keyword. However, this still requires you to know the names of the columns you want to ignore.
sql
SELECT * EXCLUDE (new_col1, new_col2) FROM ...
Since you mentioned you won't know the new column names in the future, Solution 1 (The View) is your best option for a "set and forget" approach.
Note on FILES() vs Internal Tables
It's worth noting that StarRocks has a feature for loading from external files (like S3/HDFS) where SELECT * is automatically trimmed to match the target table when using BY NAME. This behavior is not yet the default for internal table-to-table INSERT operations, which is why your query is currently failing.
Summary
* Best for automation: Create a View with a fixed column list and use SELECT * on that View.
* Most robust: Explicitly list your columns in the SELECT statement.
* Avoid: Using SELECT * directly on a table that is expected to grow its schema beyond the target table's schema.
References
• loading/InsertInto.mdLý Thành
07/14/2026, 4:03 AMRocky
07/14/2026, 4:04 AMORDER BY (or KEY) clause. It works best for range queries and prefix filtering.
* Rule: The first 36 bytes of the sort key are used to create this sparse index.
* Sample (during Table Creation):
sql
CREATE TABLE sales_records (
event_time DATETIME NOT NULL,
site_id INT DEFAULT '1',
city_code VARCHAR(20),
revenue BIGINT SUM
)
ENGINE=OLAP
AGGREGATE KEY(event_time, site_id, city_code) -- Sort Key definition
DISTRIBUTED BY HASH(site_id);
_In this example, filtering by event_time or event_time + site_id will be extremely fast._
──────────
2. Bitmap Index
Best for low-cardinality columns (columns with few unique values, like gender, province, or order_status).
* Sample (New Table):
sql
CREATE TABLE users (
user_id INT,
gender VARCHAR(10),
INDEX idx_gender (gender) USING BITMAP
) DISTRIBUTED BY HASH(user_id);
* Sample (Existing Table):
sql
CREATE INDEX idx_gender ON users (gender) USING BITMAP;
-- OR
ALTER TABLE users ADD INDEX idx_gender (gender) USING BITMAP;
──────────
3. Bloom Filter Index
Best for high-cardinality columns (columns with many unique values like user_id or order_id) where you perform many point queries (e.g., WHERE id = 123).
* Sample (New Table):
sql
CREATE TABLE orders (
order_id BIGINT,
customer_id BIGINT
)
DISTRIBUTED BY HASH(order_id)
PROPERTIES ("bloom_filter_columns" = "order_id, customer_id");
* Sample (Existing Table):
sql
ALTER TABLE orders SET ("bloom_filter_columns" = "order_id, customer_id");
──────────
4. Full-Text Inverted Index (GIN)
Available in newer versions (v3.3+), this is used for high-performance text searching, similar to Elasticsearch.
* Sample:
sql
CREATE TABLE logs (
id BIGINT,
log_message STRING,
INDEX idx_message (log_message) USING GIN ("parser" = "english")
) DISTRIBUTED BY HASH(id);
──────────
5. N-Gram Bloom Filter Index
Optimizes LIKE queries for string columns (e.g., WHERE col LIKE '%keyword%').
* Sample:
sql
ALTER TABLE my_table
ADD INDEX idx_ngram (column_name)
USING NGRAMBF ("gram_num" = "4", "bloom_filter_size" = "1024");
──────────
6. Vector Index
Used for AI and similarity search (e.g., finding similar images or text embeddings).
* Sample:
sql
CREATE TABLE image_vectors (
id BIGINT,
embedding ARRAY<FLOAT>,
INDEX idx_vector (embedding) USING VECTOR (
"index_type" = "hnsw",
"metric_type" = "l2_distance",
"dim" = "512"
)
) DISTRIBUTED BY HASH(id);
Summary Table
Index Type | Best For | Requirement
Prefix Index | Range queries on sort keys | Automatic (via ORDER BY)
Bitmap | Low-cardinality columns | USING BITMAP
Bloom Filter | High-cardinality point queries | PROPERTIES or SET
GIN | Full-text search | USING GIN
N-Gram | LIKE queries | USING NGRAMBF
Vector | Embedding similarity | USING VECTOR
Note: You can check existing indexes on a table using:
sql
SHOW INDEX FROM table_name;
-- or
SHOW CREATE TABLE table_name;
References
• sql-reference/sql-statements/table_bucket_part_index/CREATE_INDEX.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.mdRocky
07/14/2026, 4:04 AMLý Thành
07/15/2026, 2:32 AMRocky
07/15/2026, 2:32 AMSELECT ... INTO OUTFILE statement is used to synchronously export the results of a query to a file in remote storage (such as AWS S3, HDFS, or Azure Blob Storage).
Unlike the EXPORT command which runs in the background (asynchronously), INTO OUTFILE is a blocking operation—your SQL client will wait until the export is finished before returning a result.
1. Basic Syntax
sql
SELECT column1, column2, ...
FROM table_name
INTO OUTFILE "<s3://bucket/path/file_prefix_>"
[FORMAT AS CSV]
[PROPERTIES (
"key" = "value",
...
)];
2. Key Parameters
* Destination Path: A URI starting with s3://, hdfs://, ks3://, etc. If the path ends with a string (not a /), that string is used as the prefix for the generated files.
* FORMAT: Usually CSV. (Note: Some versions also support Parquet via newer unloading methods like INSERT INTO FILES).
* PROPERTIES: Used to define storage credentials and file formatting:
* Column Separator: "column_separator" = ","
* Line Delimiter: "line_delimiter" = "\n"
* Storage Credentials: Required to access your cloud storage (e.g., aws.s3.access_key).
3. Usage Examples
Exporting to AWS S3
This example exports query results as a CSV to an S3 bucket:
sql
SELECT id, name, price
FROM sales_data
INTO OUTFILE "<s3://my-bucket/exports/sales_2023_>"
FORMAT AS CSV
PROPERTIES (
"column_separator" = ",",
"line_delimiter" = "\n",
"aws.s3.access_key" = "your_access_key",
"aws.s3.secret_key" = "your_secret_key",
"aws.s3.region" = "us-east-1"
);
Exporting to HDFS
sql
SELECT * FROM users
INTO OUTFILE "<hdfs://namenode:9000/user/data/users_export_>"
FORMAT AS CSV
PROPERTIES (
"column_separator" = "\t",
"hadoop.security.authentication" = "simple",
"username" = "hdfs_user"
);
4. Important Considerations
* Synchronous: The connection remains open until the file is written. This is great for scripts but can timeout on very large exports. For massive datasets, use the EXPORT command or INSERT INTO FILES.
* File Splitting: If the result set is large, StarRocks will automatically split the data into multiple files (e.g., sales_2023_0.csv, sales_2023_1.csv).
* Overwrite: By default, StarRocks will not overwrite existing files. If files with the same name already exist in the destination, the query will fail.
* Local Files: You cannot export directly to a local disk on the FE/BE nodes via this command. To save to local disk, you typically use a standard SQL client's redirect (e.g., mysql -e "SELECT..." > file.csv) or mount a network drive (NFS) and treat it as a file protocol.
5. Modern Alternative (v3.2+)
If you are on StarRocks v3.2 or later, it is recommended to use INSERT INTO FILES(), which provides a more flexible interface for unloading data:
sql
INSERT INTO FILES(
"path" = "<s3://my-bucket/unload/>",
"format" = "parquet",
"aws.s3.access_key" = "xxx",
"aws.s3.secret_key" = "yyy"
)
SELECT * FROM my_table;
References
• unloading/unload_using_insert_into_files.md
• sql-reference/sql-statements/loading_unloading/unloading/EXPORT.md
• sql-reference/sql-functions/table-functions/files.md
References
• unloading/unload_using_insert_into_files.md
• sql-reference/sql-functions/table-functions/files.md
• sql-reference/sql-statements/loading_unloading/unloading/EXPORT.mdLý Thành
07/15/2026, 2:51 AMRocky
07/15/2026, 2:51 AMBACKUP command uploads these already-compressed segment files directly to MinIO.
* There is no additional compression performed during the backup process itself, but since the source files are already compressed, the storage in MinIO will reflect that same high compression ratio (typically 3:1 to 5:1 compared to raw text/CSV).
3. Metadata and Snapshots
The BACKUP process creates a snapshot. This includes:
* Segment files: The bulk of the data.
* Metadata files: Very small files (kilobytes to megabytes) describing the table schema and partition info.
* Version history: StarRocks backups are snapshots of a specific point in time. If you run multiple backups, each one is independent (unless using specific incremental partition strategies manually), so each full backup will take up the same amount of space.
Summary Table
Your Scenario | Physical Disk Usage (BEs) | Estimated MinIO Storage
10 TB Disk (3x Replicas) | 10 TB | ~3.3 TB
10 TB Logical Data | 30 TB | ~10 TB
Important Notes
* Granularity: As of recent versions (v3.4+), StarRocks supports backing up at the Database, Table, or Partition level. To back up a "full cluster," you would typically perform a backup at the database level for each of your databases.
* Shared-Data (Cloud-Native): If you are using the Shared-data mode (where data already lives in S3/MinIO), the BACKUP and RESTORE commands work differently or may not be supported in the same way, as the primary storage is already remote.
How to check your actual data size:
Run this query to see the logical data size (excluding replicas):
sql
SELECT sum(DATA_LENGTH) FROM information_schema.tables;
The result of this query is the best estimate for your backup size in MinIO.
References
• administration/management/Backup_and_restore.md
• deployment/plan_cluster.md
• sql-reference/sql-statements/backup_restore/BACKUP.md
References
• deployment/plan_cluster.md
• administration/management/Backup_and_restore.md
• faq/Exporting_faq.mdLý Thành
07/15/2026, 3:43 AMRocky
07/15/2026, 3:43 AMFROM clause, you simply separate the assignments in the SET clause with a comma.
Here is the updated SQL:
sql
UPDATE dmapp.dtm_customer_ld_summary
SET
max_customer_status_1_year = a.max_customer_status_1_year,
max_customer_status_3_year = a.max_customer_status_3_year -- Added this line
FROM dwh.dwh_customer_ld_summary_upd2 AS a
WHERE a.sys_run_date = dmapp.dtm_customer_ld_summary.sys_run_date
AND a.ld_no = dmapp.dtm_customer_ld_summary.ld_no;
Key Requirements for Update:
1. Table Type: This syntax works best on tables with a Primary Key model. If your table uses the Duplicate or Aggregate model, ensure your StarRocks version and configuration support multi-table updates (most recent versions do).
2. Join Columns: Ensure that sys_run_date and ld_no are part of the primary key for the best performance.
3. Column Names: Make sure the column max_customer_status_3_year exists in both the target and the source table (or adjust the alias a. if the source column name is different).
References
• sql-reference/sql-statements/data-manipulation/UPDATE.md
• table_design/table_types/primary_key_table.mdLý Thành
07/15/2026, 9:52 AMRocky
07/15/2026, 9:53 AMsp_merge_monthly_dpd_bymonth to run on the first day of every month instead of every day, you need to drop the existing task and resubmit it with the updated schedule. StarRocks (as of current versions) does not support a direct ALTER TASK ... SCHEDULE statement to change the interval.
Steps to modify the task:
1. Drop the existing task:
sql
DROP TASK sp_merge_monthly_dpd_bymonth;
2. Submit the task with the new monthly schedule:
Use EVERY(INTERVAL 1 MONTH) and set the START time to the 1st of the month (the scheduler will use this to align all future runs to the 1st).
sql
SUBMIT TASK sp_merge_monthly_dpd_bymonth
SCHEDULE START('2025-01-01 08:50:00') EVERY(INTERVAL 1 MONTH)
AS
INSERT INTO modelling.MONTHLY_DPD (
REPORT_MONTH,
LD_NO,
CONTRACT_NO,
RUN_DATE,
MAX_DPD_IN_MONTH,
MAX_CA_ACCT_BALANCE,
LAST_PAYMENT_DATE,
DUE_DATE_OF_MON
)
SELECT
DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 DAY), '%Y-%m') AS REPORT_MONTH,
LD_NO,
CONTRACT_NO,
NOW() AS RUN_DATE,
MAX(OVER_DUE_DAYS_LD) AS MAX_DPD_IN_MONTH,
MIN(CA_ACCT_BALANCE) AS MAX_CA_ACCT_BALANCE,
MAX(LAST_PAYMENT_DATE) AS LAST_PAYMENT_DATE,
MIN(DUE_DATE_OF_MON) AS DUE_DATE_OF_MON
FROM dmapp.DTM_CUSTOMER_LD_SUMMARY
WHERE SYS_RUN_DATE >= DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 DAY), '%Y-%m-01')
AND SYS_RUN_DATE <= DATE_SUB(CURDATE(), INTERVAL 1 DAY)
GROUP BY LD_NO, CONTRACT_NO;
Why this works:
* Schedule Alignment: By setting EVERY(INTERVAL 1 MONTH) and a START date that falls on the 1st (like 2025-01-01), the task will automatically run on the 1st of every subsequent month.
* Date Logic: Your existing WHERE clause is already perfect for a monthly run. When the task runs on the 1st of July, DATE_SUB(CURDATE(), INTERVAL 1 DAY) becomes the last day of June. The logic then filters SYS_RUN_DATE from the 1st of June to the 30th of June, correctly aggregating the entire previous month's data.
Notes:
* Version Note: If you are using StarRocks v4.1 or later, you can check the ALTER TASK command, but currently, it is primarily used to SUSPEND, RESUME, or modify session properties (like timeouts), not the core schedule interval.
* Verification: After submitting, you can verify the next scheduled run time by querying:
sql
SELECT * FROM INFORMATION_SCHEMA.tasks WHERE task_name = 'sp_merge_monthly_dpd_bymonth';
References
• sql-reference/sql-statements/loading_unloading/ETL/SUBMIT_TASK.md
• sql-reference/sql-statements/loading_unloading/ETL/ALTER_TASK.md
• administration/management/FE_configuration.md
References
• sql-reference/sql-statements/loading_unloading/ETL/ALTER_TASK.md
• administration/management/FE_configuration.md
• sql-reference/sql-statements/loading_unloading/ETL/SUBMIT_TASK.md
• release_notes/release-3.3.mdLý Thành
07/15/2026, 9:55 AMRocky
07/15/2026, 9:56 AMSECOND, MINUTE, HOUR, and DAY as units for the EVERY(INTERVAL ...) clause. The MONTH unit is currently not supported in the SUBMIT TASK syntax.
To achieve a monthly run on the first day of every month, you can use one of the following workarounds:
Workaround 1: Run Daily with "First of Month" Logic
You can keep the task running every day, but add a condition in your WHERE clause so that it only processes data if today is the 1st of the month. On all other days, the task will technically run but insert 0 rows, consuming negligible resources.
sql
SUBMIT TASK sp_merge_monthly_dpd_bymonth
SCHEDULE START('2025-01-01 08:50:00') EVERY(INTERVAL 1 DAY)
AS
INSERT INTO modelling.MONTHLY_DPD (
REPORT_MONTH, LD_NO, CONTRACT_NO, RUN_DATE,
MAX_DPD_IN_MONTH, MAX_CA_ACCT_BALANCE, LAST_PAYMENT_DATE, DUE_DATE_OF_MON
)
SELECT
DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 DAY), '%Y-%m') AS REPORT_MONTH,
LD_NO, CONTRACT_NO, NOW() AS RUN_DATE,
MAX(OVER_DUE_DAYS_LD), MIN(CA_ACCT_BALANCE), MAX(LAST_PAYMENT_DATE), MIN(DUE_DATE_OF_MON)
FROM dmapp.DTM_CUSTOMER_LD_SUMMARY
WHERE DAY(CURDATE()) = 1 -- This ensures the query only selects data on the 1st of the month
AND SYS_RUN_DATE >= DATE_FORMAT(DATE_SUB(CURDATE(), INTERVAL 1 DAY), '%Y-%m-01')
AND SYS_RUN_DATE <= DATE_SUB(CURDATE(), INTERVAL 1 DAY)
GROUP BY LD_NO, CONTRACT_NO;
Workaround 2: Use an External Scheduler
If you require a strict monthly schedule without daily "empty" runs, StarRocks' recommendation is to use an external orchestrator like Apache Airflow, DolphinScheduler, or a standard Linux crontab.
Example for a Linux crontab (runs at 08:50 on the 1st of every month):
bash
50 08 1 * * mysql -h <FE_IP> -P 9030 -u <user> -p<pass> -e "CALL your_stored_procedure_or_sql_logic;"
Why MONTH is not supported
The internal scheduler for SUBMIT TASK (and similarly for Materialized Views) is designed for fixed-length intervals. Since months have varying lengths (28, 30, 31 days), a simple interval-based "Every 1 Month" would drift relative to the calendar date.
Summary of Units
Unit | Valid in Task Schedule?
SECOND | Yes
MINUTE | Yes
HOUR | Yes
DAY | Yes
MONTH | No
YEAR | No
References
• sql-reference/sql-statements/loading_unloading/ETL/SUBMIT_TASK.md
• sql-reference/sql-functions/date-time-functions/day.md
• integrations/airflow.md
References
• administration/management/FE_configuration.md
• sql-reference/sql-statements/loading_unloading/ETL/SUBMIT_TASK.md
• using_starrocks/Cost_based_optimizer.md
• using_starrocks/caching/query_cache.mdLý Thành
07/15/2026, 10:19 AMRocky
07/15/2026, 10:19 AMsql
ALTER TABLE monthly_dpd RENAME monthly_dpd_old;
ALTER TABLE monthly_dpd_new RENAME monthly_dpd;
Pro Tip: Atomic Swap
If your goal is to replace an old table with a new one without any "downtime" (where the table name doesn't exist for a split second), StarRocks provides an atomic swap command. This is safer because it ensures that monthly_dpd is always available to queries:
sql
-- This one command replaces 'monthly_dpd' with 'monthly_dpd_new'
-- and moves the old data to 'monthly_dpd_new' simultaneously.
ALTER TABLE monthly_dpd SWAP WITH monthly_dpd_new;
After the SWAP, you can then rename the remaining monthly_dpd_new (which now contains the old data) to monthly_dpd_old if you wish to keep it as a backup.
Summary
* Your Method: Correct, but results in a tiny window where the table name is missing.
* SWAP Method: Recommended for production to avoid query failures during the rename process.
References
• sql-reference/sql-statements/table_bucket_part_index/ALTER_TABLE.md
References
• sql-reference/sql-statements/table_bucket_part_index/ALTER_TABLE.md
• sql-reference/sql-statements/Database/ALTER_DATABASE.mdLý Thành
07/16/2026, 3:33 AMep_transaction_loan (tran_id,
action,
action_type,
amount,
create_date,
create_user,
currentcy,
description,
edit_date,
edit_user,
fee_amount,
from_acount,
merchant_id,
partner_code,
product_id,
request_id,
request_message,
response_code,
response_des,
response_message,
status,
to_acount,
tran_no,
transaction_date,
type,
vat_amount,
partner_tran_no) SECURITY NONE AS
SELECT
dbmcman.ep_transaction.tran_id,
dbmcman.ep_transaction.action,
dbmcman.ep_transaction.action_type,
dbmcman.ep_transaction.amount,
dbmcman.ep_transaction.create_date,
dbmcman.ep_transaction.create_user,
dbmcman.ep_transaction.currentcy,
dbmcman.ep_transaction.description,
dbmcman.ep_transaction.edit_date,
dbmcman.ep_transaction.edit_user,
dbmcman.ep_transaction.fee_amount,
dbmcman.ep_transaction.from_acount,
dbmcman.ep_transaction.merchant_id,
dbmcman.ep_transaction.partner_code,
dbmcman.ep_transaction.product_id,
dbmcman.ep_transaction.request_id,
dbmcman.ep_transaction.request_message,
dbmcman.ep_transaction.response_code,
dbmcman.ep_transaction.response_des,
dbmcman.ep_transaction.response_message,
dbmcman.ep_transaction.status,
dbmcman.ep_transaction.to_acount,
dbmcman.ep_transaction.tran_no,
dbmcman.ep_transaction.transaction_date,
dbmcman.ep_transaction.type,
dbmcman.ep_transaction.vat_amount,
dbmcman.ep_transaction.partner_tran_no
FROM
dbmcman.ep_transaction
WHERE
(((dbmcman.ep_transaction.type = 'LOAN')
AND (dbmcman.ep_transaction.action_type = 'OUT'))
AND (dbmcman.ep_transaction.response_code = '00'))
AND (dbmcman.ep_transaction.action = 'EPG71');Rocky
07/16/2026, 3:33 AMtransaction_date) to improve performance and allow for incremental refreshes.
Option 1: Basic Asynchronous MV
This version refreshes the entire dataset based on the schedule you define.
sql
CREATE MATERIALIZED VIEW ep_transaction_loan_mv
REFRESH ASYNC EVERY (INTERVAL 1 HOUR)
DISTRIBUTED BY HASH(`tran_id`)
AS
SELECT
`tran_id`,
`action`,
`action_type`,
`amount`,
`create_date`,
`create_user`,
`currentcy`,
`description`,
`edit_date`,
`edit_user`,
`fee_amount`,
`from_acount`,
`merchant_id`,
`partner_code`,
`product_id`,
`request_id`,
`request_message`,
`response_code`,
`response_des`,
`response_message`,
`status`,
`to_acount`,
`tran_no`,
`transaction_date`,
`type`,
`vat_amount`,
`partner_tran_no`
FROM `dbmcman`.`ep_transaction`
WHERE `type` = 'LOAN'
AND `action_type` = 'OUT'
AND `response_code` = '00'
AND `action` = 'EPG71';
Option 2: Optimized (Partitioned) MV
If your base table dbmcman.ep_transaction is partitioned by transaction_date, you should also partition the Materialized View. This allows StarRocks to refresh only the changed days rather than the whole table.
sql
CREATE MATERIALIZED VIEW ep_transaction_loan_mv
PARTITION BY (`transaction_date`) -- Enables incremental refresh
REFRESH ASYNC EVERY (INTERVAL 1 HOUR)
DISTRIBUTED BY HASH(`tran_id`)
AS
SELECT
`tran_id`,
-- ... (include all other columns here)
`transaction_date`
FROM `dbmcman`.`ep_transaction`
WHERE `type` = 'LOAN'
AND `action_type` = 'OUT'
AND `response_code` = '00'
AND `action` = 'EPG71';
Key Differences from a Logical View:
1. Storage: Unlike a regular View, an MV stores the data on disk.
2. Refresh Strategy: REFRESH ASYNC tells StarRocks to update the data in the background. You can specify EVERY (INTERVAL 1 DAY), 1 HOUR, etc.
3. Distribution: You must specify DISTRIBUTED BY HASH(...) to define how data is spread across the cluster. tran_id is usually a good choice for a unique transaction ID.
4. Security: StarRocks MVs use the privileges of the user who created the MV. The SECURITY NONE syntax from your logical view is not used in StarRocks MV syntax.
How to check status:
After creating it, you can check the refresh status with:
sql
SELECT * FROM information_schema.materialized_views WHERE mv_name = 'ep_transaction_loan_mv'\G
-- To see refresh history:
SELECT * FROM information_schema.tasks_history WHERE task_name LIKE '%ep_transaction_loan_mv%';
```
## References
- [sql-reference/sql-statements/table_bucket_part_index/CREATE_MATERIALIZED_VIEW.md](https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_MATERIALIZED_VIEW)
- [using_starrocks/async_mv/use_cases.md](https://docs.starrocks.io/docs/using_starrocks/async_mv/use_cases)
- [using_starrocks/async_mv/materialized_view.md](https://docs.starrocks.io/docs/using_starrocks/async_mv/materialized_view)Lý Thành
07/16/2026, 3:36 AMRocky
07/16/2026, 3:37 AMJOIN or query multiple tables.
* Default Catalog: The base table must be in the default_catalog.
* Version: You must be on v3.1.8 or higher to use the WHERE clause in a Sync MV.
2. Syntax for your Query
Since your view is a filtered subset of a single table (dbmcman.ep_transaction), you can create a Sync MV like this:
sql
CREATE MATERIALIZED VIEW ep_transaction_loan_sync_mv
AS
SELECT
tran_id,
action,
action_type,
amount,
create_date,
create_user,
currentcy,
description,
edit_date,
edit_user,
fee_amount,
from_acount,
merchant_id,
partner_code,
product_id,
request_id,
request_message,
response_code,
response_des,
response_message,
status,
to_acount,
tran_no,
transaction_date,
type,
vat_amount,
partner_tran_no
FROM `dbmcman`.`ep_transaction`
WHERE `type` = 'LOAN'
AND `action_type` = 'OUT'
AND `response_code` = '00'
AND `action` = 'EPG71';
3. How it Works
* Auto-Update: You do not need to set a refresh schedule. StarRocks handles it automatically during every INSERT or LOAD to the ep_transaction table.
* Query Acceleration (Query Rewrite): You don't usually query the MV name directly. Instead, you continue querying your original table or view. StarRocks' optimizer will detect that the MV already contains the filtered results and automatically rewrite your query to read from the MV instead of the large base table.
* Direct Query: If you want to force a query to hit the Sync MV, use the hint:
sql
SELECT * FROM ep_transaction_loan_sync_mv [_SYNC_MV_];
Important Considerations
1. Loading Performance: Because the update is synchronous, adding this MV will slightly increase the time it takes to load data into the base table (as it has to write to two places at once).
2. Storage: This creates a filtered copy of the data on disk.
3. Check Status: Creating a Sync MV is an asynchronous task. You can check if it's finished building with:
sql
SHOW ALTER MATERIALIZED VIEW;
References
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
• using_starrocks/Materialized_view-single_table.md
• best_practices/query_tuning/query_planning.md
References
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
• using_starrocks/Materialized_view-single_table.md
• using_starrocks/async_mv/use_cases/query_rewrite_with_materialized_views.mdLý Thành
07/16/2026, 3:43 AMRocky
07/16/2026, 3:43 AMREFRESH ASYNC strategy. In StarRocks, REFRESH ASYNC (without a specific time interval) acts as an "on-change" trigger that refreshes the MV whenever data in the base table is updated.
Recommended Solution
Use the following syntax. Note that Asynchronous MVs require a DISTRIBUTED BY clause:
sql
CREATE MATERIALIZED VIEW ep_transaction_loan_mv
REFRESH ASYNC
DISTRIBUTED BY HASH(`tran_id`)
AS
SELECT
`tran_id`,
`action`,
`action_type`,
`amount`,
`create_date`,
`create_user`,
`currentcy`,
`description`,
`edit_date`,
`edit_user`,
`fee_amount`,
`from_acount`,
`merchant_id`,
`partner_code`,
`product_id`,
`request_id`,
`request_message`,
`response_code`,
`response_des`,
`response_message`,
`status`,
`to_acount`,
`tran_no`,
`transaction_date`,
`type`,
`vat_amount`,
`partner_tran_no`
FROM `dbmcman`.`ep_transaction`
WHERE `type` = 'LOAN'
AND `action_type` = 'OUT'
AND `response_code` = '00'
AND `action` = 'EPG71';
Why this works:
1. Primary Key Support: Asynchronous MVs support all StarRocks table types, including Primary Key.
2. Automatic Refresh: The REFRESH ASYNC clause tells StarRocks to automatically trigger a refresh task in the background shortly after it detects a data change in ep_transaction.
3. Background Processing: Unlike Synchronous MVs (which block your load until the view is updated), Asynchronous MVs update in the background, so they won't slow down your data ingestion speed.
Monitoring the refresh
Since the refresh happens in the background, you can check the status of the update tasks with:
sql
SELECT * FROM information_schema.task_runs
WHERE mv_name = 'ep_transaction_loan_mv'
ORDER BY create_time DESC;
References
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.md
• using_starrocks/Materialized_view-single_table.md
• sql-reference/sql-statements/materialized_view/CREATE_MATERIALIZED_VIEW.md
References
• using_starrocks/async_mv/use_cases/data_modeling_with_materialized_views.md
• using_starrocks/async_mv/Materialized_view.mdLý Thành
07/16/2026, 3:50 AMRocky
07/16/2026, 3:50 AMwarehouse).
* An Iceberg Metastore. The most common choice is the Hive Metastore (HMS) or a REST Catalog.
2. Connect StarRocks to Iceberg (MinIO)
In StarRocks, you create an External Catalog. This allows StarRocks to "see" the databases and tables in your Iceberg catalog.
Run this SQL in StarRocks (adjusting the endpoints and credentials):
sql
CREATE EXTERNAL CATALOG iceberg_minio
PROPERTIES
(
"type" = "iceberg",
"iceberg.catalog.type" = "hive", -- Or "rest" if using an Iceberg REST server
"hive.metastore.uris" = "<thrift://metastore-host:9083>",
"aws.s3.enable_ssl" = "false",
"aws.s3.enable_path_style_access" = "true",
"aws.s3.endpoint" = "<http://minio-host:9000>",
"aws.s3.access_key" = "your_minio_access_key",
"aws.s3.secret_key" = "your_minio_secret_key"
);
3. Querying and Writing Data
Once the catalog is created, you can interact with your data lake directly:
* View tables:
sql
SHOW DATABASES FROM iceberg_minio;
SET CATALOG iceberg_minio;
USE some_database;
SHOW TABLES;
* Query data:
sql
SELECT * FROM my_iceberg_table LIMIT 10;
* Create & Insert (Lakehouse mode):
If your StarRocks version is 3.1+, you can create tables and insert data directly into the Iceberg lake:
sql
CREATE TABLE iceberg_minio.db_name.new_table (
id INT,
name STRING
) ENGINE=iceberg;
INSERT INTO iceberg_minio.db_name.new_table VALUES (1, 'Rocky');
4. Performance Optimization: Data Cache
Since data lives in MinIO (remote storage), query latency might be higher than local storage. StarRocks features a Data Cache that stores hot data on the BE (Backend) local disks (SSD recommended).
To enable it, ensure your BE configuration (be.conf) has:
properties
block_cache_enable = true
block_cache_disk_path = /path/to/cache/disk
block_cache_mem_size = 4G
block_cache_max_disk_size = 100G
Key Considerations for MinIO
* Path-style access: Always set "aws.s3.enable_path_style_access" = "true" for MinIO.
* Connectivity: Ensure the StarRocks FE and BE nodes can both reach the MinIO endpoint and the Metastore URI.
References
• data_source/catalog/iceberg/iceberg_catalog.md
• quick_start/iceberg.md
• using_starrocks/data_cache.md
References
• quick_start/iceberg.md
• data_source/catalog/iceberg/iceberg_rest_s3.md
• data_source/catalog/iceberg/iceberg_catalog.md
• data_source/catalog/hive_catalog.mdLý Thành
07/16/2026, 3:53 AMRocky
07/16/2026, 3:54 AM<s3://bucket/table/metadata/v1.metadata.json>.
* Pros: Very mature, supported by almost every data tool (Spark, Trino, Flink).
* Cons: Can be heavy to maintain; it was originally designed for Hive, not modern cloud-native formats.
* StarRocks Config: Uses the thrift:// protocol (typically port 9083).
2. REST Catalog
This is the modern, "cloud-native" way to manage Iceberg tables. Instead of a dedicated database service like HMS, it uses a simple Web API (HTTP).
* How it works: StarRocks sends an HTTP request to a REST server to get table information. The REST server can be backed by anything (a database, a cloud service, or even a file system).
* Popular Implementations:
* Polaris: An open-source REST catalog by Snowflake.
* Project Nessie: Provides "Git-like" version control (branching/merging) for your data lake.
* Gravitron: A unified metadata lake.
* Pros: Easier to secure (uses OAuth2/Tokens), better for multi-cloud setups, and much lighter than HMS.
* StarRocks Config: Uses an http:// or https:// endpoint.
──────────
Which one should you choose?
Feature | Hive Metastore (HMS) | REST Catalog
Setup Complexity | High (Needs MySQL + HMS Service) | Low to Medium (Depends on the server)
Modern Features | Limited | High (Atomic transactions, branching)
Security | Kerberos (Complex) | OAuth2 / JWT (Standard Web Security)
Recommendation | Use if you have a legacy Hadoop/Hive environment. | Recommended for new "Greenfield" projects.
How to get started with MinIO?
If you are starting from scratch with MinIO, the easiest way to get a Metastore is to run Polaris or Nessie in a Docker container alongside your MinIO.
Example StarRocks Catalog for REST (Polaris):
sql
CREATE EXTERNAL CATALOG iceberg_rest
PROPERTIES (
"type" = "iceberg",
"iceberg.catalog.type" = "rest",
"iceberg.catalog.uri" = "<http://polaris-host:8181/api/catalog>",
"iceberg.catalog.warehouse" = "minio_warehouse",
"aws.s3.endpoint" = "<http://minio-host:9000>",
"aws.s3.access_key" = "admin",
"aws.s3.secret_key" = "password",
"aws.s3.enable_path_style_access" = "true"
);
References
• data_source/catalog/iceberg/iceberg_catalog.md
• data_source/catalog/iceberg/iceberg_rest_s3.md
• quick_start/iceberg.md
References
• data_source/catalog/iceberg/iceberg_catalog.md
• data_source/catalog/iceberg/iceberg_rest_s3.md
• quick_start/iceberg.md
• data_source/catalog/hive_catalog.md
-catalog)
References
• data_source/catalog/iceberg/iceberg_catalog.mdLý Thành
07/16/2026, 7:52 AMstrategy_version varchar(150) NULL COMMENT "Phiên bản chiến lược"
) ENGINE=OLAP
PRIMARY KEY(id)
ORDER BY (created_date, app_code)
COMMENT "Thông tin phê duyệt khoản vay LOS"
DISTRIBUTED BY HASH(app_code) BUCKETS 8
PROPERTIES (
"compression" = "LZ4",
"fast_schema_evolution" = "true",
"replicated_storage" = "true",
"replication_num" = "2"
);Rocky
07/16/2026, 7:52 AMDISTRIBUTED BY HASH(...) must be a subset of the Primary Key.
In your snippet:
* Primary Key: id
* Distribution Key: app_code
This will cause an error unless app_code is also part of the primary key. To fix this, you have two options:
1. Change Distribution: Use DISTRIBUTED BY HASH(id) (Recommended if id is a unique ID).
2. Change Primary Key: Use PRIMARY KEY(id, app_code) (Only if the combination defines uniqueness).
Breakdown of your Properties
* ENGINE=OLAP: Correct, the standard engine.
* ORDER BY (created_date, app_code): This is excellent. Since v3.0, StarRocks allows the Sort Key (ORDER BY) to be different from the Primary Key. Sorting by created_date will significantly speed up queries filtered by time.
* replicated_storage = "true": This is a good choice. It enables "single leader replication" (available since v2.5), which reduces CPU and IO usage during data ingestion by only writing to the primary replica and then syncing.
* replication_num = "2": Valid, but note that for production environments, 3 is the recommended standard for high availability.
* fast_schema_evolution = "true": Correct, this allows for near-instant column additions/deletions.
Corrected Example
Assuming id is your unique identifier, the corrected structure would look like this:
sql
CREATE TABLE your_table_name (
`id` bigint NOT NULL,
`app_code` varchar(50) NOT NULL,
`created_date` datetime NOT NULL,
-- ... other columns ...
`strategy_name` varchar(150) NULL COMMENT "Tên chiến lược",
`strategy_version` varchar(150) NULL COMMENT "Phiên bản chiến lược"
) ENGINE=OLAP
PRIMARY KEY(`id`)
ORDER BY (created_date, app_code)
COMMENT "Thông tin phê duyệt khoản vay LOS"
DISTRIBUTED BY HASH(`id`) -- Changed to match Primary Key
BUCKETS 8
PROPERTIES (
"compression" = "LZ4",
"fast_schema_evolution" = "true",
"replicated_storage" = "true",
"replication_num" = "2"
);
One Final Tip: Persistent Index
If this table will be very large (hundreds of millions of rows), consider adding "enable_persistent_index" = "true" to your PROPERTIES. This prevents the Primary Key index from consuming too much RAM by storing it on disk.
References
• table_design/table_types/primary_key_table.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• table_design/table_types/primary_key_table.md
• table_design/StarRocks_table_design.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
References
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• table_design/StarRocks_table_design.md
• table_design/table_types/primary_key_table.mdLý Thành
07/16/2026, 7:54 AMRocky
07/16/2026, 7:55 AMCREATE TABLE statement. Specifically, the COMMENT and DISTRIBUTED BY clauses must come before the ORDER BY clause.
Additionally, as mentioned previously, your Distribution Key (app_code) must be a subset of your Primary Key (id).
The Corrected SQL Structure
Here is the correct order for your statement:
sql
CREATE TABLE your_table_name (
`id` bigint NOT NULL,
`app_code` varchar(50) NOT NULL,
`created_date` datetime NOT NULL,
-- ... other columns ...
`strategy_name` varchar(150) NULL COMMENT "Tên chiến lược",
`strategy_version` varchar(150) NULL COMMENT "Phiên bản chiến lược"
) ENGINE=OLAP
PRIMARY KEY(`id`)
COMMENT "Thông tin phê duyệt khoản vay LOS" -- 1. Comment comes after Key
DISTRIBUTED BY HASH(`id`) -- 2. Distributed By comes next (must use 'id')
BUCKETS 8
ORDER BY (created_date, app_code) -- 3. Order By comes after Distribution
PROPERTIES ( -- 4. Properties is always last
"compression" = "LZ4",
"fast_schema_evolution" = "true",
"replicated_storage" = "true",
"replication_num" = "2"
);
Why this fix works:
1. Clause Order: StarRocks follows this sequence: [PRIMARY KEY] -> [COMMENT] -> [PARTITION BY] -> [DISTRIBUTED BY] -> [ORDER BY] -> [PROPERTIES].
2. PK Constraint: In a Primary Key table, the column in DISTRIBUTED BY HASH(...) must be one of the columns defined in the PRIMARY KEY(...). Since your PK is only id, you must distribute by id.
References
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• table_design/table_types/primary_key_table.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• table_design/table_types/primary_key_table.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
References
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• table_design/StarRocks_table_design.md
• table_design/table_types/primary_key_table.mdLý Thành
07/16/2026, 8:49 AMRocky
07/16/2026, 8:49 AMa in your DELETE statement. StarRocks does not support assigning an alias directly after the table name in the FROM clause of a DELETE statement.
The Fix
Simply remove the alias a and reference the column directly:
Incorrect:
sql
delete from dmapp.DTM_CUSTOMER_ADDRESS a WHERE a.sys_run_date >= date '2010-01-01';
Correct:
sql
delete from dmapp.DTM_CUSTOMER_ADDRESS WHERE sys_run_date >= date '2010-01-01';
Why this happened
In StarRocks, the DELETE syntax expects the table name to be followed immediately by the WHERE clause (or a PARTITION / USING clause). It does not recognize the table_name alias pattern common in some other SQL dialects.
* For simple deletes: Reference column names directly in the WHERE clause.
* For deletes with joins (Primary Key tables only): If you need to join other tables to determine which rows to delete, use the USING clause (available from v3.0+).
References
• sql-reference/sql-statements/table_bucket_part_index/DELETE.md
index/DELETE)
References
• sql-reference/sql-statements/table_bucket_part_index/DELETE.mdLý Thành
07/16/2026, 9:01 AMRocky
07/16/2026, 9:01 AMTO_CHAR, TRUNC) with StarRocks-native date functions.
Query 1
This query is already mostly compatible with StarRocks. DATE_SUB, CURDATE, and INTERVAL are supported as-is.
sql
SELECT *
FROM losrep.application_status b
WHERE b.current_decision = 'DISBURSED'
AND app_code IN (
SELECT DISTINCT a.app_code
FROM losrep.application a
WHERE a.product_category LIKE 'ZALO%'
AND a.created_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
);
Query 2
For this query, you need to replace TO_CHAR with DATE_FORMAT and TRUNC with the DATE() function.
sql
SELECT
DATE_FORMAT(a.CREATED_DATE, '%H') AS hour, -- HH24 equivalent
DATE(a.CREATED_DATE) AS created_date, -- TRUNC equivalent
a.*
FROM dmapp.DTM_CUS_APPLICATIONS a
WHERE DATE(a.CREATED_DATE) >= DATE '2026-07-06'
AND DATE(a.CREATED_DATE) < DATE '2026-07-16'
AND a.PRODUCT_CATEGORY LIKE 'ZALO%'
AND a.STEP_CODE = 'DONE';
Key Changes Summary:
1. TO_CHAR(date, 'HH24') $\rightarrow$ DATE_FORMAT(date, '%H'): StarRocks uses C-style format strings where %H represents the 24-hour format.
2. TRUNC(date) $\rightarrow$ DATE(date): In StarRocks, the DATE() function extracts the date part and removes the time component, serving the same purpose as TRUNC on a timestamp.
3. Date Literals: StarRocks supports the DATE 'YYYY-MM-DD' syntax used in your second query.
References
• sql-reference/sql-functions/date-time-functions/date_format.md
• sql-reference/sql-functions/date-time-functions/date.md
• sql-reference/sql-functions/date-time-functions/date_sub.md
References
• sql-reference/sql-functions/date-time-functions/date_sub.md
• sql-reference/sql-functions/date-time-functions/days_sub.md
• sql-reference/sql-functions/date-time-functions/minutes_sub.md
• sql-reference/sql-functions/date-time-functions/seconds_sub.md
• sql-reference/sql-functions/date-time-functions/weeks_sub.md
• sql-reference/sql-functions/date-time-functions/hours_sub.md
• sql-reference/sql-functions/date-time-functions/years_sub.md
• sql-reference/sql-functions/date-time-functions/months_sub.mdLý Thành
07/17/2026, 4:39 AMRocky
07/17/2026, 4:40 AMID comes from Kafka, StarRocks will replace the old one with the new one. In a Duplicate Key table, both records would be stored as separate rows.
2. Real-time Streaming: Primary Key tables are optimized for high-frequency real-time updates and deletes from systems like Kafka.
3. Query Performance: Because it uses a "Delete+Insert" strategy instead of "Merge-on-Read," query performance is typically 3–10 times faster than the older Unique Key model.
Recommended Configuration for 69M Rows
With ~70 million rows, you should ensure the Persistent Index is enabled (it is true by default in recent versions) to keep memory usage low by storing the index on disk (preferably SSD).
Here is the recommended StarRocks SQL for your table:
sql
CREATE TABLE APPLICATION_HISTORY (
ID BIGINT NOT NULL,
PROCESS_UID VARCHAR(50),
APP_UID VARCHAR(50),
APP_NUMBER BIGINT,
APP_CODE VARCHAR(50),
TASK_UID VARCHAR(50),
TASK_INDEX INT,
DELEGATE_DATE DATETIME,
INIT_DATE DATETIME,
FINISH_DATE DATETIME,
CURRENT_USER VARCHAR(50),
CURRENT_DECISION VARCHAR(50),
TASK_STATUS VARCHAR(50),
SYS_ACTION VARCHAR(50),
PARENT_TASK_UID VARCHAR(50),
STEP_CODE VARCHAR(50),
STEP_CODE_DETAIL VARCHAR(50),
STEP_CODE_DETAIL_LBL VARCHAR(250),
CUSTOMIZE_INDEX BIGINT,
PREVIOUS_STEP VARCHAR(50)
) ENGINE=OLAP
PRIMARY KEY(ID) -- Ensures no duplicates based on ID
DISTRIBUTED BY HASH(ID) BUCKETS 16 -- Distribute by PK for even data spread
ORDER BY (INIT_DATE, APP_CODE) -- Define sort key for query acceleration
PROPERTIES (
"enable_persistent_index" = "true", -- Keeps memory usage low for 69M rows
"compression" = "LZ4"
);
Key Considerations:
* Data Types: Oracle's NUMBER(20,0) should be mapped to BIGINT. DATE should be mapped to DATETIME or DATE depending on whether you need the time component.
* Bucketing: For 69 million rows, starting with 16 to 32 buckets is a good rule of thumb. If you expect this to grow into the hundreds of millions, you can increase the bucket count.
* Sort Key (ORDER BY): Choose columns often used in your WHERE clauses (e.g., INIT_DATE) to speed up your specific queries.
References
• table_design/table_types/primary_key_table.md
• loading/Kafka-connector-starrocks.md
• best_practices/primarykey_table.md
References
• table_design/table_types/primary_key_table.md
• table_design/table_types/duplicate_key_table.md
• table_design/StarRocks_table_design.md
• table_design/table_types/table_types.mdLý Thành
07/20/2026, 3:30 AMdtm_application_history (
id bigint(20) NOT NULL COMMENT "PK",
app_uid varchar(50) NULL COMMENT "",
app_number bigint(20) NULL COMMENT "",
app_code varchar(50) NULL COMMENT "",
task_uid varchar(50) NULL COMMENT "",
delegate_date datetime NULL COMMENT "",
process_uid varchar(50) NULL COMMENT "",
task_index smallint(6) NULL COMMENT "",
init_date datetime NULL COMMENT "",
finish_date datetime NULL COMMENT "",
current_user_1 varchar(50) NULL COMMENT "",
current_decision varchar(50) NULL COMMENT "",
task_status varchar(50) NULL COMMENT "",
sys_action varchar(50) NULL COMMENT "",
parent_task_uid varchar(50) NULL COMMENT "",
step_code varchar(50) NULL COMMENT "",
step_code_detail varchar(50) NULL COMMENT "",
step_code_detail_lbl varchar(250) NULL COMMENT "",
customize_index bigint(20) NULL COMMENT "",
previous_step varchar(50) NULL COMMENT "",
INDEX idx_task_status (task_status) USING BITMAP COMMENT 'Filter by task_status',
INDEX idx_app_code (app_code) USING BITMAP COMMENT 'Filter by app_code'
)
redessign this 70 milion rows table, 5GB data.Rocky
07/20/2026, 3:30 AMsql
CREATE TABLE `dtm_application_history` (
`id` BIGINT NOT NULL COMMENT "Natural Primary Key",
`app_uid` VARCHAR(50) NULL,
`app_number` BIGINT NULL,
`app_code` VARCHAR(50) NULL,
`task_uid` VARCHAR(50) NULL,
`delegate_date` DATETIME NULL,
`process_uid` VARCHAR(50) NULL,
`task_index` SMALLINT NULL,
`init_date` DATETIME NOT NULL COMMENT "Partition column must be NOT NULL",
`finish_date` DATETIME NULL,
`current_user_1` VARCHAR(50) NULL,
`current_decision` VARCHAR(50) NULL,
`task_status` VARCHAR(50) NULL,
`sys_action` VARCHAR(50) NULL,
`parent_task_uid` VARCHAR(50) NULL,
`step_code` VARCHAR(50) NULL,
`step_code_detail` VARCHAR(50) NULL,
`step_code_detail_lbl` VARCHAR(250) NULL,
`customize_index` BIGINT NULL,
`previous_step` VARCHAR(50) NULL,
-- Bitmap indices for low-cardinality filtering
INDEX idx_task_status (`task_status`) USING BITMAP,
INDEX idx_current_decision (`current_decision`) USING BITMAP
) ENGINE=OLAP
PRIMARY KEY(`id`)
-- Partitioning by time is critical for managing 70M+ rows
PARTITION BY RANGE(`init_date`) (
START ("2024-01-01") END ("2026-01-01") EVERY (INTERVAL 1 MONTH)
)
DISTRIBUTED BY HASH(`id`) BUCKETS 16
-- Sort Key decoupled from PK to optimize specific query filters
ORDER BY (`init_date`, `app_code`)
PROPERTIES (
"enable_persistent_index" = "true",
"replication_num" = "3",
"compression" = "LZ4"
);
Key Design Improvements:
1. Primary Key Model: Using PRIMARY KEY(id) allows the Kafka stream to perform upserts. If an ID already exists, StarRocks will update the row instead of creating a duplicate.
2. Persistent Index: Enabled via "enable_persistent_index" = "true". For 70 million rows, this keeps the primary key index on disk (SSD) rather than consuming several GBs of RAM.
3. Range Partitioning: Partitioning by init_date (Monthly) allows StarRocks to "prune" data during queries. If you search for data from July, it will only scan the July partition, ignoring the other 4.5GB of data.
4. Decoupled Sort Key: By using ORDER BY (init_date, app_code), you tell StarRocks to store the data physically sorted by these columns. This creates a Prefix Index that makes filtering by date or application code extremely fast.
5. Bitmap Indices:
* Keep task_status as a Bitmap index if it has low cardinality (e.g., 'PENDING', 'COMPLETED').
* I also added current_decision as a Bitmap index candidate based on your previous queries.
_ _Note: Do not create Bitmap indices on high-cardinality columns (like IDs or unique codes) as the prefix/primary index handles those more efficiently.*
6. Bucketing: 16 buckets for 5GB of data results in tablet sizes of ~300MB, which is within the recommended 100MB–1GB range for StarRocks.
Tips for Kafka Loading:
* Use the StarRocks Kafka Connector or Routine Load.
* Since this is a Primary Key table, ensure your Kafka source provides the id column.
* If your Kafka stream can contain deletes, you can set the __op column in your load job to handle row deletions automatically.
References
• table_design/table_types/primary_key_table.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• best_practices/primarykey_table.md
• loading/RoutineLoad.md
ReferencesRocky
07/20/2026, 3:30 AMLý Thành
07/20/2026, 3:33 AMinit_date) will auto create new partion?Rocky
07/20/2026, 3:33 AMPARTITION BY RANGE does not automatically create new partitions by itself. In StarRocks, you have two ways to handle automatic partition creation:
1. The Recommended Way: Expression Partitioning (v3.0+)
If you want StarRocks to automatically create a new partition whenever data for a new month arrives (e.g., from your Kafka stream), you should use Expression Partitioning. This is much simpler than the legacy method.
Replace PARTITION BY RANGE(init_date) with:
sql
PARTITION BY date_trunc('month', init_date)
Example DDL update:
sql
CREATE TABLE `dtm_application_history` (
...
`init_date` DATETIME NOT NULL,
...
) ENGINE=OLAP
PRIMARY KEY(`id`)
-- This will automatically create partitions like p202401, p202402, etc. as data arrives
PARTITION BY date_trunc('month', init_date)
DISTRIBUTED BY HASH(`id`) BUCKETS 16
ORDER BY (`init_date`, `app_code`)
PROPERTIES (
"partition_live_number" = "24", -- Optional: automatically drops partitions older than 24 months
"enable_persistent_index" = "true"
);
2. The Legacy Way: Dynamic Partitioning
If you must use PARTITION BY RANGE, you have to enable the dynamic_partition properties in the PROPERTIES section. This method requires you to define at least one initial partition manually and then StarRocks will manage future ones.
sql
...
PARTITION BY RANGE(init_date) (
PARTITION p202401 VALUES LESS THAN ("2024-02-01")
)
DISTRIBUTED BY HASH(`id`) BUCKETS 16
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "MONTH",
"dynamic_partition.end" = "3", -- Create partitions for the next 3 months in advance
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "16"
);
Why use Expression Partitioning instead?
* Zero Maintenance: You don't need to configure complex properties.
* Handles Backfills: If you suddenly load data from 2 years ago, it will create those partitions automatically. The "Legacy" dynamic partitioning usually only creates partitions for the "future" relative to the current time.
* Simplified DDL: Much cleaner and less prone to configuration errors.
Recommendation: Since you are setting this up for a real-time Kafka stream with 70M rows, use Expression Partitioning (date_trunc). It is more robust for high-velocity streaming data.
References
• table_design/data_distribution/expression_partitioning.md
• table_design/data_distribution/dynamic_partitioning.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
References
• table_design/data_distribution/expression_partitioning.mdLý Thành
07/20/2026, 3:35 AMRocky
07/20/2026, 3:36 AMpartition_live_number, StarRocks will not automatically delete any partitions.
Here is what will happen as your Kafka data flows in:
1. Unlimited Growth
Partitions will continue to be created for every new month (or whatever time unit you chose) as data arrives. They will persist in the system forever unless you manually delete them using ALTER TABLE ... DROP PARTITION.
2. System Limits
While there is no "automatic cleanup," there are safety limits to prevent the system from crashing:
* Table Limit: By default, StarRocks (v3.3.2+) limits a single table to 100,000 partitions (max_partition_number_per_table).
* Batch Limit: A single load job (like a batch from Kafka) can only create up to 4,096 partitions at once. This prevents a single typo in a date (e.g., year 0025 instead of 2025) from accidentally creating thousands of partitions.
3. Performance Impact
If you accumulate too many partitions (e.g., several thousand):
* FE Memory: The FrontEnd (FE) stores metadata for every partition. Having tens of thousands of partitions can increase memory usage and slow down the FE.
* Query Planning: Even though StarRocks is great at partition pruning, having an excessive number of partitions makes the "planning" phase of a query slower.
Recommendation
For your table with 70 million rows and monthly partitioning:
* 70M rows / 12 months = ~5.8M rows per partition. This is a very healthy size for StarRocks.
* If you need to keep history forever: Leave partition_live_number unset. You will have ~120 partitions after 10 years, which is perfectly fine.
* If you only need a rolling window: Set partition_live_number (e.g., 24 for two years). This keeps the database lean and automatically handles your data lifecycle.
Summary: If you don't set it, they stay forever. You only need to set it if you want StarRocks to act as a "TTL" (Time To Live) mechanism to automatically purge old data.
References
• faq/operation_maintenance_faq.md
• administration/management/FE_configuration.md
• best_practices/partitioning.md
• table_design/data_distribution/expression_partitioning.md
• best_practices/query_tuning/schema_tuning.md
• knowledge/admin_and_operation/shared_data_compaction.mdLý Thành
07/20/2026, 3:40 AMRocky
07/20/2026, 3:41 AMinit_date for partitioning, you must include it in your PRIMARY KEY definition.
Optimized DDL with the fix:
sql
CREATE TABLE `dtm_application_history` (
`id` bigint(20) NOT NULL COMMENT "PK",
`app_uid` varchar(50) NULL,
`app_number` bigint(20) NULL,
`app_code` varchar(50) NULL,
`task_uid` varchar(50) NULL,
`delegate_date` datetime NULL,
`process_uid` varchar(50) NULL,
`task_index` smallint(6) NULL,
`init_date` datetime NOT NULL, -- Must be NOT NULL
`finish_date` datetime NULL,
`current_user_1` varchar(50) NULL,
`current_decision` varchar(50) NULL,
`task_status` varchar(50) NULL,
`sys_action` varchar(50) NULL,
`parent_task_uid` varchar(50) NULL,
`step_code` varchar(50) NULL,
`step_code_detail` varchar(50) NULL,
`step_code_detail_lbl` varchar(250) NULL,
`customize_index` bigint(20) NULL,
`previous_step` varchar(50) NULL,
INDEX idx_task_status (`task_status`) USING BITMAP,
INDEX idx_app_code (`app_code`) USING BITMAP
) ENGINE=OLAP
-- FIX: init_date must be here
PRIMARY KEY(`id`, `init_date`)
-- Automatic partitioning
PARTITION BY date_trunc('month', `init_date`)
DISTRIBUTED BY HASH(`id`) BUCKETS 16
ORDER BY (`init_date`, `app_code`)
PROPERTIES (
"enable_persistent_index" = "true"
);
Important Considerations:
1. Uniqueness Scope: By adding init_date to the Primary Key, the id is technically only guaranteed to be unique within that specific month. However, if your application logic guarantees that id is always unique (like a UUID or auto-incrementing sequence), this change won't cause any issues.
2. Updates: If you ever update the init_date of an existing row, StarRocks will treat it as a delete + insert because the row is moving to a different partition.
3. NOT NULL Requirement: Partition columns in a Primary Key table cannot contain NULL values. Ensure init_date is marked as NOT NULL.
What if I can't add init_date to the PK?
If your id is not unique across dates and you cannot include the date in the PK, you have two options:
* Remove Partitioning: For only 5GB of data, partitioning is actually optional. StarRocks can handle 70M rows in a single "unpartitioned" table very easily using just the id as the PK and DISTRIBUTED BY HASH(id).
* Duplicate ID: If you have the same id occurring in different months, the version with PRIMARY KEY(id, init_date) will keep both rows. If you only want the "latest" version regardless of the date, you must either remove partitioning or use a different table model (like the Duplicate Key model, though you lose the upsert capability).
References
• table_design/table_types/primary_key_table.md
• faq/operation_maintenance_faq.md
• table_design/data_distribution/expression_partitioning.md
References
• table_design/table_types/primary_key_table.md
• faq/operation_maintenance_faq.md
• table_design/data_distribution/expression_partitioning.mdLý Thành
07/20/2026, 4:41 AMRocky
07/20/2026, 4:42 AMCURRENT_USER is a reserved keyword in StarRocks (it is a built-in function that returns the name of the current user).
When you use it in a Stream Load without escaping it, the StarRocks parser thinks you are trying to call the function instead of identifying a column.
Solution 1: Escape the column name (Recommended)
You must wrap the column name in backticks (`` ` ``) in both your Table DDL and your Stream Load columns configuration.
1. Update your Table DDL:
sql
CREATE TABLE ... (
...
`current_user` varchar(50) NULL, -- Wrap in backticks
...
)
2. Update your Stream Load command:
If you are using a curl command or a script, find the columns header and add backticks:
bash
-H "columns: id, process_uid, ..., `current_user`, current_decision, ..."
_Note: If you are using a shell script, you may need to escape the backticks like `\`current_user\``._
──────────
Solution 2: Rename the column
To avoid potential issues with other tools or future queries, it is often easier to rename the column to something that isn't a reserved keyword, such as user_id or app_user.
Updated Oracle SQL for extraction:
sql
select
ID,
...
CURRENT_USER as APP_USER, -- Rename during extract
...
from losrep.application_history ...
Updated StarRocks DDL:
sql
CREATE TABLE `dtm_application_history` (
...
`app_user` varchar(50) NULL, -- Use the new name
...
)
Why this happened
In StarRocks, keywords like SELECT, TABLE, CURRENT_USER, and TIMESTAMP have special meanings. If you want to use them as names for your own tables or columns, the system requires backticks to distinguish them from the built-in commands.
References
• sql-reference/sql-statements/keywords.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• loading/StreamLoad.md
References
• sql-reference/sql-statements/keywords.md
• release_notes/release-2.5.md
• release_notes/release-3.0.md
• administration/management/FE_configuration.md
• sql-reference/sql-statements/account-management/CREATE_USER.md
• sql-reference/System_limit.md
• sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE.md
• administration/user_privs/authorization/privilege_item.md