This topic describes how to use the CREATE DYNAMIC TABLE statement to create a Dynamic Table.
Important notes
For limitations on using Dynamic Tables, see Dynamic Table support and limitations.
Hologres V3.1 and later requires the new syntax to create Dynamic Tables. You can still perform ALTER operations on tables created with the V3.0 syntax, but you cannot create new ones. For non-partitioned tables, you can use the syntax conversion command to convert the legacy syntax to the new syntax. For partitioned tables, recreate them manually.
Upgrading to Hologres V3.1 and later requires recreation of existing Incremental Dynamic Tables. You can use the syntax conversion command to do so.
Starting from Hologres V3.1, the engine adaptively optimizes the refresh process for Dynamic Tables to improve stability. Consequently, negative query IDs for Refresh operations are normal.
Syntax
V3.1+ (new syntax)
Hologres V3.1 and later only supports the new syntax for creating Dynamic Tables.
Create Dynamic Table syntax
In Hologres V3.1 and later, use the following syntax to create a Dynamic Table:
CREATE DYNAMIC TABLE [ IF NOT EXISTS ] [<schema_name>.]<table_name>
[ (<col_name> [, ...] ) ]
[LOGICAL PARTITION BY LIST(<partition_key>)]
WITH (
-- Dynamic Table properties
freshness = '<num> {minutes | hours}', -- Required
[auto_refresh_enable = {true | false},] -- Optional
[auto_refresh_mode = {'full' | 'incremental' | 'auto'},] -- Optional
[base_table_cdc_format = {'stream' | 'binlog'},] -- Optional
[auto_refresh_partition_active_time = '<num> {minutes | hours | days}',] -- Optional
[partition_key_time_format = {'YYYYMMDDHH24' | 'YYYY-MM-DD-HH24' | 'YYYY-MM-DD_HH24' | 'YYYYMMDD' | 'YYYY-MM-DD' | 'YYYYMM' | 'YYYY-MM' | 'YYYY'},] --Optional
[computing_resource = {'local' | 'serverless' | '<warehouse_name>'},] -- Optional. The warehouse_name value is supported only in Hologres V4.0.7 and later.
[refresh_guc_hg_experimental_serverless_computing_required_cores=xxx,] --Optional. Specifies the required computing cores for Serverless.
[refresh_guc_<guc_name> = '<guc_value>',] -- Optional
-- General properties
[orientation = {'column' | 'row' | 'row,column'},]
[table_group = '<tableGroupName>',]
[distribution_key = '<columnName>[,...]]',]
[clustering_key = '<columnName>[:asc] [,...]',]
[event_time_column = '<columnName> [,...]',]
[bitmap_columns = '<columnName> [,...]',]
[dictionary_encoding_columns = '<columnName> [,...]',]
[time_to_live_in_seconds = '<non_negative_literal>',]
[storage_mode = {'hot' | 'cold'},]
)
AS
<query>; -- Query definition.Parameters
Refresh mode & resources
Parameter | Description | Required | Default |
| Specifies the target Data Freshness in minutes or hours. The minimum value is 1 minute. The engine automatically schedules the next refresh based on the time of the previous refresh and the | Yes | None |
| The refresh mode. Valid values:
| No | auto |
| Specifies whether to enable automatic refresh. Valid values:
| No | true |
| Specifies how to consume data changes from the base table during an incremental refresh.
Note
| No | stream |
| For more information, see Parameters. | No | serverless |
| You can set GUC parameters for refresh operations. For a list of supported GUCs, see GUC parameters. | No | None |
Partitioned tables
Logical partitioned table
Parameter | Description | Required | Default |
| Creates logical partitioned Dynamic Table. You must also set the | No | None |
| The refresh scope for partitions, specified in minutes, hours, or days. Hologres traces back historical partitions from the current time based on this setting and automatically refreshes partitions within this scope. Active partitions are those are those for which the elapsed time since the partition's start (which is derived from its name) is less than the duration specified by the Note
| Yes | Defaults to This provides a 1-hour buffer to account for potential data delays from the base table. For example, with daily partitioning, the default becomes 25 hours (1 day + 1 hour). |
| The partition format, based on which Hologres generates partition names. Valid options:
| Yes | None |
Physical partitioned tables
Parameter | Description | Required | Default |
| Creates a physical partitioned Dynamic Table. Compared to logical partitioned Dynamic Tables, a physical partitioned Dynamic Table lacks dynamic partitioning capabilities and have certain usage limitations. We recommend using logical partitions instead. For differences, see CREATE LOGICAL PARTITION TABLE. Important Hologres V3.1+ do not support creating a Dynamic Table as a physical partitioned table. | No | None |
Table properties
Parameter | Description | Required | Default value | |
Full refresh mode | Incremental refresh mode | |||
| The column name in the Dynamic Table. You can explicitly specify the column names for a Dynamic Table, but not their attributes or data types, as the engine infers them automatically. Note Specifying column attributes and data types can lead to incorrect engine inference. | No | Query column name | Query column name |
| Specifies the storage format for the Dynamic Table. | No |
|
|
| Specifies the Table Group for the Dynamic Table. It defaults to the default Table Group of the current database. For more information, see Manage Table Groups and shard counts. | No | Default Table Group name | Default Table Group name |
| Specifies the distribution key for the Dynamic Table. For more information, see Distribution key. | No | (none) | (none) |
| Specifies the clustering key for the Dynamic Table. For more information, see Clustering key. | No | Allowed, with a default inferred value. | Allowed, with a default inferred value. |
| No | (none) | (none) | |
| Specifies the bitmap column for the Dynamic Table. For more information, see Bitmap index. | No | TEXT type fields | TEXT type fields |
| See Dictionary encoding. | No | TEXT type fields | TEXT type fields |
| Specifies the Time to Live (TTL) for data in the Dynamic Table. | No | No expiration | No expiration |
| The storage tier for the Dynamic Table. Valid values:
Note For details, see Tiered storage of hot data and cold data. | No |
|
|
| Specifies whether to enable binlog for the Dynamic Table. For details, see Subscribe to Hologres Binlog. Note
| No |
|
|
| The binlog TTL. | No |
|
|
Query
The query that generates the data in the Dynamic Table. The supported query and base table types vary depending on the refresh mode. For more information, see Dynamic Table scope and limits.
V3.0 (legacy syntax)
Create Dynamic Table syntax
CREATE DYNAMIC TABLE [IF NOT EXISTS] <schema.tablename>(
[col_name],
[col_name]
) [PARTITION BY LIST (col_name)]
WITH (
[refresh_mode='[full|incremental]',]
[auto_refresh_enable='[true|false',]
--Incremental refresh parameters:
[incremental_auto_refresh_schd_start_time='[immediate|<timestamptz>]',]
[incremental_auto_refresh_interval='[<num> {minute|minutes|hour|hours]',]
[incremental_guc_hg_computing_resource='[ local | serverless]',]
[incremental_guc_hg_experimental_serverless_computing_required_cores='<num>',]
--Full refresh parameters:
[full_auto_refresh_schd_start_time='[immediate|<timestamptz>]',]
[full_auto_refresh_interval='[<num> {minute|minutes|hour|hours]',]
[full_guc_hg_computing_resource='[ local | serverless]',]--hg_full_refresh_computing_resource defaults to serverless, can be set at the DB level, and is optional for users.
[full_guc_hg_experimental_serverless_computing_required_cores='<num>',]
--Shared parameters, GUCs are allowed:
[refresh_guc_<guc>='xxx]',]
-- General Dynamic Table properties:
[orientation = '[column]',]
[table_group = '[tableGroupName]',]
[distribution_key = 'columnName[,...]]',]
[clustering_key = '[columnName{:asc]} [,...]]',]
[event_time_column = '[columnName [,...]]',]
[bitmap_columns = '[columnName [,...]]',]
[dictionary_encoding_columns = '[columnName [,...]]',]
[time_to_live_in_seconds = '<non_negative_literal>',]
[storage_mode = '[hot | cold]']
)
AS
<query> --Query definitionParameters
Refresh mode and resources
Category | Parameter | Description | Required | Default |
Shared refresh parameters |
| Specifies the data refresh mode. Valid values: If it's not set, no refresh is performed. | No | (none) |
| Specifies whether to enable automatic refresh. Valid values:
| No | false | |
| You can set GUC parameters for refresh operations. For a list of supported GUCs, see GUC parameters. Note For example, to set the timezone GUC, use | No | (none) | |
Incremental refresh |
| The start time for incremental refresh. Valid values:
| No | immediate |
| The interval for incremental refresh. Unit: minute (minutes) and hour (hours).
| No | (none) | |
| Specifies the computing resources for incremental refresh. Valid values:
Note To set the computing resources at the DB level, run | No | local | |
| Specifies Serverless Computing resources used for refresh. Note Serverless Computing resource quotas vary by instance specifications. For more information, see Manage Serverless Computing resources. | No | (none) | |
Full refresh |
| The start time for full refresh. Valid values:
| No | immediate |
| The interval for full refresh. Unit: minute (minutes) and hour(hours).
| No | (none) | |
| Specifies the computing resources for full refresh. Valid values:
Note To set the computing resources at the DB level, run | No | local | |
| Specifies Serverless Computing resources used for refresh. Note Serverless Computing resource quotas vary by instance specifications. For more information, see Manage Serverless Computing resources. | No | (none) |
Table properties
Parameter | Description | Required | Default | |
full | incremental | |||
| The column name in the Dynamic Table. You can explicitly specify the column names for a Dynamic Table, but not their attributes or data types, as the engine infers them automatically. Note Specifying column attributes and data types can lead to incorrect engine inference. | No | Query column name | Query column name |
| Specifies the storage mode for the Dynamic Table. The value | No |
|
|
| Specifies the Table Group for the Dynamic Table. Defaults to the default Table Group of the current database. For more information, see Manage Table Groups and shard counts. | No | Default Table Group name | Default Table Group name |
| Specifies the distribution key for the Dynamic Table. For more information, see Distribution key. | No | (none) | (none) |
| Specifies the clustering key for the Dynamic Table. For more information, see Clustering key. | No | Allowed, with a default inferred value. | Allowed, with a default inferred value. |
| Specifies the segment key for the Dynamic Table. For more information, see Event time column (segment key). | No | (none) | (none) |
| Specifies the bitmap columns for the Dynamic Table. For more information, see Bitmap index. | No | TEXT type fields | TEXT type fields |
| See Dictionary encoding. | No | TEXT type fields | TEXT type fields |
| Specifies data TTL in the Dynamic Table. | No | No expiration | No expiration |
| The storage tier for the Dynamic Table. Valid values:
Note | No |
|
|
| Specifies whether it is a partitioned table. You can create a partitioned Dynamic Table, which is used similarly to a regular partitioned table. Different partitions can have different refresh modes to meet various data refreshness requirements. | No | Non-partitioned table | Non-partitioned table |
Query
The query that generates the data in the `Dynamic Table`. The supported query and base table types vary depending on the refresh mode. For more information, see Dynamic Table support and limitations.
Incremental refresh
Incremental refresh automatically detects changes in the base table's data and writes the incremental data from the query into the Dynamic Table. Compared to a full refresh, incremental refresh processes a smaller amount of data, resulting in less processing time. For near-real-time data query needs at the minute level, we recommend using incremental refresh. Take note of the following:
Base table limitations:
Hologres V3.1 uses the
streammode by default to consume incremental data. If your base table had Binlog enabled in V3.0, we recommend disabling it to prevent increased storage costs.In V3.0, you must enable binlog for the base table, except for a dimension table involved in a join. Enabling binlog for a base table incurs some storage overhead. Check the binlog storage usage by referring to Query the storage details of a table.
In incremental refresh mode, Hologres generates a state table in the background to record intermediate aggregation results (See Dynamic Table for details). The state table also consumes some space for state storage. To view storage usage, see Query the structure and lineage of a dynamic table.
For details on the queries and operators currently supported by incremental refresh, see Dynamic Table support and limitations.
Stream-stream joins
Stream-stream JOINs have the same semantics as OLAP queries and are implemented based on HASH JOIN. It supports INNER JOIN, LEFT/RIGHT/FULL OUTER JOIN.
V3.1
Starting from Hologres V3.1, the GUC for stream-stream JOIN is enabled by default.
Example:
CREATE TABLE users (
user_id INT,
user_name TEXT,
PRIMARY KEY (user_id)
);
INSERT INTO users VALUES(1, 'hologres');
CREATE TABLE orders (
order_id INT,
user_id INT,
PRIMARY KEY (order_id)
);
INSERT INTO orders VALUES(1, 1);
CREATE DYNAMIC TABLE dt WITH (
auto_refresh_mode = 'incremental',
freshness='10 minutes'
)
AS
SELECT order_id, orders.user_id, user_name
FROM orders LEFT JOIN users ON orders.user_id = users.user_id;
-- After refresh, one joined record is visible
REFRESH TABLE dt;
SELECT * FROM dt;
order_id | user_id | user_name
----------+---------+-----------
1 | 1 | hologres
(1 row)
UPDATE users SET user_name = 'dynamic table' WHERE user_id = 1;
INSERT INTO orders VALUES(4, 1);
-- After refresh, two joined records are visible. Dimension table updates affect all data and can correct previously joined records.
REFRESH TABLE dt;
SELECT * FROM dt;Result:
order_id | user_id | user_name
----------+---------+---------------
1 | 1 | dynamic table
4 | 1 | dynamic table
(2 rows)V3.0
Stream-stream JOINs are supported in V3.0.26. To enable this feature, upgrade your instance and enable the GUC:
-- Enable at session level
SET hg_experimental_incremental_dynamic_table_enable_hash_join TO ON;
-- Enable at DB level (takes effect for new connections)
ALTER database <db_name> SET hg_experimental_incremental_dynamic_table_enable_hash_join TO ON;Example:
CREATE TABLE users (
user_id INT,
user_name TEXT,
PRIMARY KEY (user_id)
) WITH (binlog_level = 'replica');
INSERT INTO users VALUES(1, 'hologres');
CREATE TABLE orders (
order_id INT,
user_id INT,
PRIMARY KEY (order_id)
) WITH (binlog_level = 'replica');
INSERT INTO orders VALUES(1, 1);
CREATE DYNAMIC TABLE dt WITH (refresh_mode = 'incremental')
AS
SELECT order_id, orders.user_id, user_name
FROM orders LEFT JOIN users ON orders.user_id = users.user_id;
-- After refresh, one joined record is visible
REFRESH TABLE dt;
SELECT * FROM dt;
order_id | user_id | user_name
----------+---------+-----------
1 | 1 | hologres
(1 row)
UPDATE users SET user_name = 'dynamic table' WHERE user_id = 1;
INSERT INTO orders VALUES(4, 1);
-- After refresh, two joined records are visible. Dimension table updates affect all data and can correct previously joined records.
REFRESH TABLE dt;
SELECT * FROM dt;Result:
order_id | user_id | user_name
----------+---------+---------------
1 | 1 | dynamic table
4 | 1 | dynamic table
(2 rows)Dimension table joins
Each record in a data stream is joined with a snapshot of the dimension table, reflecting its latest state at the exact moment of processing. This means the JOIN operation is performed strictly at processing time. Consequently, any additions, updates, or deletions to the dimension table that occur after a JOIN will not impact the already-processed data.
The dimension table JOIN operation is independent of the table's data volume; it is determined by the JOIN statement.
V3.1
CREATE TABLE users (
user_id INT,
user_name TEXT,
PRIMARY KEY (user_id)
);
INSERT INTO users VALUES(1, 'hologres');
CREATE TABLE orders (
order_id INT,
user_id INT,
PRIMARY KEY (order_id)
) WITH (binlog_level = 'replica');
INSERT INTO orders VALUES(1, 1);
CREATE DYNAMIC TABLE dt_join_2 WITH (
auto_refresh_mode = 'incremental',
freshness='10 minutes')
AS
SELECT order_id, orders.user_id, user_name
-- FOR SYSTEM_TIME AS OF PROCTIME() identifies 'users' as a dimension table
FROM orders LEFT JOIN users FOR SYSTEM_TIME AS OF PROCTIME()
ON orders.user_id = users.user_id;
-- After refresh, one joined record is visible
REFRESH TABLE dt_join_2;
SELECT * FROM dt_join_2;
order_id | user_id | user_name
----------+---------+-----------
1 | 1 | hologres
(1 row)
UPDATE users SET user_name = 'dynamic table' WHERE user_id = 1;
INSERT INTO orders VALUES(4, 1);
-- After refresh, two joined records are visible. Dimension table updates only affect new data and cannot correct previously joined data.
REFRESH TABLE dt_join_2;
SELECT * FROM dt_join_2;Result:
order_id | user_id | user_name
----------+---------+---------------
1 | 1 | hologres
4 | 1 | dynamic table
(2 rows)V3.0
CREATE TABLE users (
user_id INT,
user_name TEXT,
PRIMARY KEY (user_id)
);
INSERT INTO users VALUES(1, 'hologres');
CREATE TABLE orders (
order_id INT,
user_id INT,
PRIMARY KEY (order_id)
) WITH (binlog_level = 'replica');
INSERT INTO orders VALUES(1, 1);
CREATE DYNAMIC TABLE dt_join_2 WITH (refresh_mode = 'incremental')
AS
SELECT order_id, orders.user_id, user_name
-- FOR SYSTEM_TIME AS OF PROCTIME() identifies 'users' as a dimension table
FROM orders LEFT JOIN users FOR SYSTEM_TIME AS OF PROCTIME()
ON orders.user_id = users.user_id;
-- After refresh, one joined record is visible
REFRESH TABLE dt_join_2;
SELECT * FROM dt_join_2;
order_id | user_id | user_name
----------+---------+-----------
1 | 1 | hologres
(1 row)
UPDATE users SET user_name = 'dynamic table' WHERE user_id = 1;
INSERT INTO orders VALUES(4, 1);
-- After refresh, two joined records are visible. Dimension table updates only affect new data and cannot correct previously joined data.
REFRESH TABLE dt_join_2;
SELECT * FROM dt_join_2;Result:
order_id | user_id | user_name
----------+---------+---------------
1 | 1 | hologres
4 | 1 | dynamic table
(2 rows)Incremental consumption of Paimon lake tables
Incremental refresh can be used for consuming Paimon tables, enabling data lakehousing.
External Dynamic Tables enhance the lakehouse data experience by offering incremental reading and seamless write-back capabilities. This combination simplifies data processing, accelerates query performance, and ultimately reduces operational costs. See External Dynamic Table introduction.
Hybrid consumption
Currently, an Incremental Dynamic Table also supports a hybrid consumption model. This model first performs an initial full load of all existing data from the base table, and then continuously processes only new, incremental data.
V3.1
V3.1 enables the hybrid refresh model by default. Example:
--Prepare the base table and insert data
CREATE TABLE base_sales(
day TEXT NOT NULL,
hour INT,
user_id BIGINT,
ts TIMESTAMPTZ,
amount FLOAT,
pk text NOT NULL PRIMARY KEY
);
-- Import data into the base table
INSERT INTO base_sales values ('2024-08-29',1,222222,'2024-08-29 16:41:19.141528+08',5,'ddd');
-- Import more data
INSERT INTO base_sales VALUES ('2024-08-29',2,3333,'2024-08-29 17:44:19.141528+08',100,'aaaaa');
-- Create an incremental Dynamic Table
CREATE DYNAMIC TABLE sales_incremental
WITH (
auto_refresh_mode='incremental',
freshness='10 minutes'
)
AS
SELECT day, hour, SUM(amount), COUNT(1)
FROM base_sales
GROUP BY day, hour;Check data consistency:
Query the base table
SELECT day, hour, SUM(amount), COUNT(1) FROM base_sales GROUP BY day, hour;Result:
day hour sum count 2024-08-29 2 100 1 2024-08-29 1 5 1Query the Dynamic Table
SELECT * FROM sales_incremental;Result:
day hour sum count 2024-08-29 1 5 1 2024-08-29 2 100 1
V3.0
In V3.0, to use hybrid consumption, manually enable the GUC incremental_guc_hg_experimental_enable_hybrid_incremental_mode. Example:
--Prepare the base table, enable Binlog, and insert data
CREATE TABLE base_sales(
day TEXT NOT NULL,
hour INT,
user_id BIGINT,
ts TIMESTAMPTZ,
amount FLOAT,
pk text NOT NULL PRIMARY KEY
);
-- Import data into the base table
INSERT INTO base_sales values ('2024-08-29',1,222222,'2024-08-29 16:41:19.141528+08',5,'ddd');
-- Enable Binlog for the base table
ALTER TABLE base_sales SET (binlog_level = replica);
-- Import incremental data into the base table
INSERT INTO base_sales VALUES ('2024-08-29',2,3333,'2024-08-29 17:44:19.141528+08',100,'aaaaa');
-- Create an auto-refreshing incremental Dynamic Table and enable the GUC for hybrid consumption
CREATE DYNAMIC TABLE sales_incremental
WITH (
refresh_mode='incremental',
incremental_auto_refresh_schd_start_time = 'immediate',
incremental_auto_refresh_interval = '3 minutes',
incremental_guc_hg_experimental_enable_hybrid_incremental_mode= 'true'
)
AS
SELECT day, hour, SUM(amount), COUNT(1)
FROM base_sales
GROUP BY day, hour;Check data consistency:
Query the base table
SELECT day, hour, SUM(amount), COUNT(1) FROM base_sales GROUP BY day, hour;Result:
day hour sum count 2024-08-29 2 100 1 2024-08-29 1 5 1Query the Dynamic Table
SELECT * FROM sales_incremental;Result:
day hour sum count 2024-08-29 1 5 1 2024-08-29 2 100 1
Full refresh
Full refresh writes the entire dataset from the query into the Dynamic Table. Compared to incremental refresh, its advantages are:
Supports more base table types.
Supports a richer set of query types and operators.
Full refresh processes more data and may consume more resources than incremental refresh. Therefore, it is better suited for scenarios like periodic reporting and regular data backfills.
For more information, see Full refresh.
Examples
V3.1
Example 1: Create a regular Incremental Dynamic Table
Before proceeding, import the tpch_10g public dataset into Hologres by following the guide at Import public datasets.
Before creating an Incremental Dynamic Table, enable Binlog for the base table (not required for dimension tables).
-- Create an Incremental Dynamic Table that refreshes every 3 minutes.
CREATE DYNAMIC TABLE public.tpch_q1_incremental
WITH (
auto_refresh_mode='incremental',
freshness='3 minutes'
) AS SELECT
l_returnflag,
l_linestatus,
COUNT(*) AS count_order
FROM
hologres_dataset_tpch_10g.lineitem
WHERE
l_shipdate <= DATE '1998-12-01' - INTERVAL '120' DAY
GROUP BY
l_returnflag,
l_linestatus;Example 2: Create an Incremental Dynamic Table from stream-stream joins
Before proceeding, import the tpch_10g public dataset into Hologres by following the guide at Import public datasets.
Before creating an Incremental Dynamic Table, enable binlog for the base table (not required for the dimension table).
-- Create an Incremental Dynamic Table from stream-stream joins.
CREATE DYNAMIC TABLE dt_join
WITH (
auto_refresh_mode='incremental',
freshness='30 minutes'
)
AS
SELECT
l_shipmode,
SUM(CASE
WHEN o_orderpriority = '1-URGENT'
OR o_orderpriority = '2-HIGH'
THEN 1
ELSE 0
END) AS high_line_count,
SUM(CASE
WHEN o_orderpriority <> '1-URGENT'
AND o_orderpriority <> '2-HIGH'
THEN 1
ELSE 0
END) AS low_line_count
FROM
hologres_dataset_tpch_10g.orders,
hologres_dataset_tpch_10g.lineitem
WHERE
o_orderkey = l_orderkey
AND l_shipmode IN ('FOB', 'AIR')
AND l_commitdate < l_receiptdate
AND l_shipdate < l_commitdate
AND l_receiptdate >= DATE '1997-01-01'
AND l_receiptdate < DATE '1997-01-01' + INTERVAL '1' YEAR
GROUP BY
l_shipmode;Example 3: Create an Auto-refresh Dynamic Table
Set the refresh mode to auto, allowing the engine to automatically select the refresh mode. It will prioritize incremental refresh and fall back to full refresh if incremental is not supported.
Before proceeding, import the tpch_10g public dataset into Hologres by following the guide at Import public datasets.
-- Create an Auto-refresh Dynamic Table that intelligent decides the refresh mode. In this example, incremental refresh is the result.
CREATE DYNAMIC TABLE thch_q6_auto
WITH (
auto_refresh_mode='auto',
freshness='1 hours'
)
AS
SELECT
SUM(l_extendedprice * l_discount) AS revenue
FROM
hologres_dataset_tpch_100g.lineitem
WHERE
l_shipdate >= DATE '1996-01-01'
AND l_shipdate < DATE '1996-01-01' + INTERVAL '1' YEAR
AND l_discount BETWEEN 0.02 - 0.01 AND 0.02 + 0.01
AND l_quantity < 24;Example 4: Create a logical partitioned Dynamic Table
For a real-time transaction dashboard, there's often a need for both near-real-time viewing of current data and correction of historical data, which requires an integrated real-time and offline analysis solution (see Business and data cognition). We typically use `Dynamic Table` logical partitions for this scenario. The approach is as follows:
The base table is partitioned by day. The latest partition is written to by Flink in real-time/near-real-time, while historical partitions are written from MaxCompute.
The Dynamic Table is created as a logical partitioned table. The latest two partitions are active and refreshed incrementally for near-real-time data analytics.
Historical partitions are inactive and use full refresh. If the base table's historical partitions have been corrected or backfilled, they can be refreshed using a full refresh.
This example uses a public dataset from GitHub.
Prepare the base table.
Use Flink to write the latest data to the base table. For detailed steps, see Integration of offline and real-time processing of data in GitHub public event datasets.
DROP TABLE IF EXISTS gh_realtime_data; BEGIN; CREATE TABLE gh_realtime_data ( id BIGINT, actor_id BIGINT, actor_login TEXT, repo_id BIGINT, repo_name TEXT, org_id BIGINT, org_login TEXT, type TEXT, created_at timestamp with time zone NOT NULL, action TEXT, iss_or_pr_id BIGINT, number BIGINT, comment_id BIGINT, commit_id TEXT, member_id BIGINT, rev_or_push_or_rel_id BIGINT, ref TEXT, ref_type TEXT, state TEXT, author_association TEXT, language TEXT, merged BOOLEAN, merged_at TIMESTAMP WITH TIME ZONE, additions BIGINT, deletions BIGINT, changed_files BIGINT, push_size BIGINT, push_distinct_size BIGINT, hr TEXT, month TEXT, year TEXT, ds TEXT, PRIMARY KEY (id,ds) ) PARTITION BY LIST (ds); CALL set_table_property('public.gh_realtime_data', 'distribution_key', 'id'); CALL set_table_property('public.gh_realtime_data', 'event_time_column', 'created_at'); CALL set_table_property('public.gh_realtime_data', 'clustering_key', 'created_at'); COMMENT ON COLUMN public.gh_realtime_data.id IS 'Event ID'; COMMENT ON COLUMN public.gh_realtime_data.actor_id IS 'Event actor ID'; COMMENT ON COLUMN public.gh_realtime_data.actor_login IS 'Event actor login name'; COMMENT ON COLUMN public.gh_realtime_data.repo_id IS 'Repo ID'; COMMENT ON COLUMN public.gh_realtime_data.repo_name IS 'Repo name'; COMMENT ON COLUMN public.gh_realtime_data.org_id IS 'Repo organization ID'; COMMENT ON COLUMN public.gh_realtime_data.org_login IS 'Repo organization name'; COMMENT ON COLUMN public.gh_realtime_data.type IS 'Event type'; COMMENT ON COLUMN public.gh_realtime_data.created_at IS 'Event time'; COMMENT ON COLUMN public.gh_realtime_data.action IS 'Event action'; COMMENT ON COLUMN public.gh_realtime_data.iss_or_pr_id IS 'Issue/pull_request ID'; COMMENT ON COLUMN public.gh_realtime_data.number IS 'Issue/pull_request number'; COMMENT ON COLUMN public.gh_realtime_data.comment_id IS 'Comment ID'; COMMENT ON COLUMN public.gh_realtime_data.commit_id IS 'Commit ID'; COMMENT ON COLUMN public.gh_realtime_data.member_id IS 'Member ID'; COMMENT ON COLUMN public.gh_realtime_data.rev_or_push_or_rel_id IS 'Review/push/release ID'; COMMENT ON COLUMN public.gh_realtime_data.ref IS 'Name of created/deleted resource'; COMMENT ON COLUMN public.gh_realtime_data.ref_type IS 'Type of created/deleted resource'; COMMENT ON COLUMN public.gh_realtime_data.state IS 'State of issue/pull_request/pull_request_review'; COMMENT ON COLUMN public.gh_realtime_data.author_association IS 'Relationship between actor and repo'; COMMENT ON COLUMN public.gh_realtime_data.language IS 'Programming language'; COMMENT ON COLUMN public.gh_realtime_data.merged IS 'Whether merged'; COMMENT ON COLUMN public.gh_realtime_data.merged_at IS 'Merge time'; COMMENT ON COLUMN public.gh_realtime_data.additions IS 'Number of added lines'; COMMENT ON COLUMN public.gh_realtime_data.deletions IS 'Number of deleted lines'; COMMENT ON COLUMN public.gh_realtime_data.changed_files IS 'Number of changed files in pull request'; COMMENT ON COLUMN public.gh_realtime_data.push_size IS 'Number of pushes'; COMMENT ON COLUMN public.gh_realtime_data.push_distinct_size IS 'Number of distinct pushes'; COMMENT ON COLUMN public.gh_realtime_data.hr IS 'Hour of event, e.g., 00 for 00:23'; COMMENT ON COLUMN public.gh_realtime_data.month IS 'Month of event, e.g., 2015-10 for Oct 2015'; COMMENT ON COLUMN public.gh_realtime_data.year IS 'Year of event, e.g., 2015'; COMMENT ON COLUMN public.gh_realtime_data.ds IS 'Date of event, ds=yyyy-mm-dd'; COMMIT;Create a logical partitioned Dynamic Table.
CREATE DYNAMIC TABLE ads_dt_github_event LOGICAL PARTITION BY LIST(ds) WITH ( -- Dynamic table properties freshness = '5 minutes', auto_refresh_mode = 'auto', auto_refresh_partition_active_time = '2 days' , partition_key_time_format = 'YYYY-MM-DD' ) AS SELECT repo_name, COUNT(*) AS events, ds FROM gh_realtime_data GROUP BY repo_name,dsQuery the Dynamic Table.
SELECT * FROM ads_dt_github_event ;Backfill a historical partition.
If historical data in the base table changes (e.g., data for '2025-04-01'), and the Dynamic Table needs to be updated, set the historical partition to full refresh mode and trigger a refresh, preferably with Serverless Computing resources.
REFRESH OVERWRITE DYNAMIC TABLE ads_dt_github_event PARTITION (ds = '2025-04-01') WITH ( refresh_mode = 'full' );
Example 5: Calculate UVs with Incremental Dynamic Table
Starting from Hologres V3.1, an Incremental Dynamic Table supports the RB_BUILD_AGG function for calculations like the number of UVs. Compared to pre-aggregation, this offers:
Faster performance: Computes only incremental data.
Lower cost: Reduced data volume and resource usage, enabling calculations over longer periods.
Example:
Prepare a user detail table.
BEGIN; CREATE TABLE IF NOT EXISTS ods_app_detail ( uid INT, country TEXT, prov TEXT, city TEXT, channel TEXT, operator TEXT, brand TEXT, ip TEXT, click_time TEXT, year TEXT, month TEXT, day TEXT, ymd TEXT NOT NULL ); CALL set_table_property('ods_app_detail', 'orientation', 'column'); CALL set_table_property('ods_app_detail', 'bitmap_columns', 'country,prov,city,channel,operator,brand,ip,click_time, year, month, day, ymd'); -- Set distribution_key based on query needs for optimal sharding effect. CALL set_table_property('ods_app_detail', 'distribution_key', 'uid'); -- For a field with full date-time used in WHERE filters, setting it as clustering_key and event_time_column is recommended. CALL set_table_property('ods_app_detail', 'clustering_key', 'ymd'); CALL set_table_property('ods_app_detail', 'event_time_column', 'ymd'); COMMIT;Calculate UV using an Incremental Dynamic Table.
CREATE DYNAMIC TABLE ads_uv_dt WITH ( freshness = '5 minutes', auto_refresh_mode = 'incremental') AS SELECT RB_BUILD_AGG(uid), country, prov, city, ymd, COUNT(1) FROM ods_app_detail WHERE ymd >= '20231201' AND ymd <='20240502' GROUP BY country,prov,city,ymd;Query UV for a specific day.
SELECT RB_CARDINALITY(RB_OR_AGG(rb_uid)) AS uv, country, prov, city, SUM(pv) AS pv FROM ads_uv_dt WHERE ymd = '20240329' GROUP BY country,prov,city;
V3.0
Example 1: Create an auto-starting full-refresh dynamic table
Before proceeding, import the tpch_10g public dataset into Hologres by following the guide at Import public datasets.
--Create "test" Schema
CREATE SCHEMA test;
--Create a single-table full refresh dynamic table, starting immediately and refreshing every hour.
CREATE DYNAMIC TABLE test.thch_q1_full
WITH (
refresh_mode='full',
auto_refresh_enable='true',
full_auto_refresh_interval='1 hours',
full_guc_hg_computing_resource='serverless',
full_guc_hg_experimental_serverless_computing_required_cores='32'
)
AS
SELECT
l_returnflag,
l_linestatus,
SUM(l_quantity) AS sum_qty,
SUM(l_extendedprice) AS sum_base_price,
SUM(l_extendedprice * (1 - l_discount)) AS sum_disc_price,
SUM(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge,
AVG(l_quantity) AS avg_qty,
AVG(l_extendedprice) AS avg_price,
AVG(l_discount) AS avg_disc,
COUNT(*) AS count_order
FROM
hologres_dataset_tpch_10g.lineitem
WHERE
l_shipdate <= DATE '1998-12-01' - INTERVAL '120' DAY
GROUP BY
l_returnflag,
l_linestatus;Example 2: Create an incremental dynamic table with a start time
Before proceeding, import the tpch_10g public dataset into Hologres by following the guide at Import public datasets.
Example:
Before creating an incremental `Dynamic Table`, you must enable Binlog for the base table (not required for dimension tables).
--Enable binlog for the base table:
BEGIN;
CALL set_table_property('hologres_dataset_tpch_10g.lineitem', 'binlog.level', 'replica');
COMMIT;
--Create a single-table incremental refresh dynamic table, specifying a start time and a 3-minute refresh interval.
CREATE DYNAMIC TABLE public.tpch_q1_incremental
WITH (
refresh_mode='incremental',
auto_refresh_enable='true',
incremental_auto_refresh_schd_start_time='2024-09-15 23:50:0',
incremental_auto_refresh_interval='3 minutes',
incremental_guc_hg_computing_resource='serverless',
incremental_guc_hg_experimental_serverless_computing_required_cores='30'
) AS SELECT
l_returnflag,
l_linestatus,
COUNT(*) AS count_order
FROM
hologres_dataset_tpch_10g.lineitem
WHERE
l_shipdate <= DATE '1998-12-01' - INTERVAL '120' DAY
GROUP BY
l_returnflag,
l_linestatus
;Example 3: Create a multi-join full-refresh dynamic table
--Create a dynamic table with a multi-table join query, using full refresh mode every 3 hours.
CREATE DYNAMIC TABLE dt_q_full
WITH (
refresh_mode='full',
auto_refresh_enable='true',
full_auto_refresh_schd_start_time='immediate',
full_auto_refresh_interval='3 hours',
full_guc_hg_computing_resource='serverless',
full_guc_hg_experimental_serverless_computing_required_cores='64'
)
AS
SELECT
o_orderpriority,
COUNT(*) AS order_count
FROM
hologres_dataset_tpch_10g.orders
WHERE
o_orderdate >= DATE '1996-07-01'
AND o_orderdate < DATE '1996-07-01' + INTERVAL '3' MONTH
AND EXISTS (
SELECT
*
FROM
hologres_dataset_tpch_10g.lineitem
WHERE
l_orderkey = o_orderkey
AND l_commitdate < l_receiptdate
)
GROUP BY
o_orderpriority;Example 4: Create a dimension-join incremental dynamic table
Example:
Before creating an incremental `Dynamic Table`, you must enable Binlog for the base table (not required for dimension tables).
The semantics of a dimension table JOIN are that each record is joined only with the latest version of the dimension table data at that time, i.e., the JOIN occurs at processing time. If the dimension table data changes (add, update, or delete) after the JOIN, the already joined data is not updated. SQL example:
--Detail table
BEGIN;
CREATE TABLE public.sale_detail(
app_id TEXT,
uid TEXT,
product TEXT,
gmv BIGINT,
order_time TIMESTAMPTZ
);
--Enable binlog for the base table; dimension tables do not require it.
CALL set_table_property('public.sale_detail', 'binlog.level', 'replica');
COMMIT;
--Property table
CREATE TABLE public.user_info(
uid TEXT,
province TEXT,
city TEXT
);
CREATE DYNAMIC TABLE public.dt_sales_incremental
WITH (
refresh_mode='incremental',
auto_refresh_enable='true',
incremental_auto_refresh_schd_start_time='2024-09-15 00:00:00',
incremental_auto_refresh_interval='5 minutes',
incremental_guc_hg_computing_resource='serverless',
incremental_guc_hg_experimental_serverless_computing_required_cores='128')
AS
SELECT
sale_detail.app_id,
sale_detail.uid,
product,
SUM(sale_detail.gmv) AS sum_gmv,
sale_detail.order_time,
user_info.province,
user_info.city
FROM public.sale_detail
INNER JOIN public.user_info FOR SYSTEM_TIME AS OF PROCTIME()
ON sale_detail.uid =user_info.uid
GROUP BY sale_detail.app_id,sale_detail.uid,sale_detail.product,sale_detail.order_time,user_info.province,user_info.city;Example 5: Create a partitioned dynamic table
For a real-time transaction dashboard, there's often a need for both near-real-time viewing of current data and correction of historical data. This can be achieved using a combination of `Dynamic Table` incremental and full refresh. The approach is as follows:
Create a partitioned base table where the latest partition is written in real-time/near-real-time, and historical partitions are occasionally corrected.
Create a `Dynamic Table` as a partitioned parent table. Use incremental refresh for the latest partition to meet near-real-time analysis needs.
Switch historical partitions to full refresh mode. If the source table's historical partitions have been corrected, the `Dynamic Table`'s partitions can also be backfilled using full refresh, preferably with Serverless to speed it up.
Example:
Prepare the base table and data.
The base table is a partitioned table, with the latest partition receiving real-time data.
-- Create a partitioned source table CREATE TABLE base_sales( uid INT, opreate_time TIMESTAMPTZ, amount FLOAT, tt TEXT NOT NULL, ds TEXT, PRIMARY KEY(ds) ) PARTITION BY LIST (ds) ; --Historical partition CREATE TABLE base_sales_20240615 PARTITION OF base_sales FOR VALUES IN ('20240615'); INSERT INTO base_sales_20240615 VALUES (2,'2024-06-15 16:18:25.387466+08','111','2','20240615'); --Latest partition, typically for real-time writes CREATE TABLE base_sales_20240616 PARTITION OF base_sales FOR VALUES IN ('20240616'); INSERT INTO base_sales_20240616 VALUES (1,'2024-06-16 16:08:25.387466+08','2','1','20240616');Create a partitioned `Dynamic Table` parent table, defining only the query without a refresh mode.
--Create extension CREATE EXTENSION roaringbitmap; CREATE DYNAMIC TABLE partition_dt_base_sales PARTITION BY LIST (ds) as SELECT public.RB_BUILD_AGG(uid), opreate_time, amount, tt, ds, COUNT(1) FROM base_sales GROUP BY opreate_time ,amount,tt,ds;Create sub-tables and set their refresh modes.
You can create `Dynamic Table` sub-partitions manually or dynamically using DataWorks. Set the latest partition to incremental refresh and historical partitions to full refresh.
-- Enable Binlog for the base table ALTER TABLE base_sales SET (binlog_level = replica); -- Assume the historical Dynamic Table sub-partition is as follows: CREATE DYNAMIC TABLE partition_dt_base_sales_20240615 PARTITION OF partition_dt_base_sales FOR VALUES IN ('20240615') WITH ( refresh_mode='incremental', auto_refresh_enable='true', incremental_auto_refresh_schd_start_time='immediate', incremental_auto_refresh_interval='30 minutes' ); -- Create a new Dynamic Table sub-partition, set its refresh mode to incremental, start immediately, refresh every 30 minutes, and use instance resources. CREATE DYNAMIC TABLE partition_dt_base_sales_20240616 PARTITION OF partition_dt_base_sales FOR VALUES IN ('20240616') WITH ( refresh_mode='incremental', auto_refresh_enable='true', incremental_auto_refresh_schd_start_time='immediate', incremental_auto_refresh_interval='30 minutes' ); --Switch the historical partition to full refresh mode ALTER DYNAMIC TABLE partition_dt_base_sales_20240615 SET (refresh_mode = 'full'); --If historical partition data needs correction, execute a refresh, preferably with serverless. SET hg_computing_resource = 'serverless'; REFRESH DYNAMIC TABLE partition_dt_base_sales_20240615;
Convert legacy to new syntax
Starting from Hologres V3.1, the syntax for creating Dynamic Tables has been changed. After upgrading from V3.0, you must recreate Dynamic Tables using the V3.1 syntax. Hologres provides a syntax conversion tool to simplify this process.
Scenarios
Incremental Dynamic Tables must be recreated with new syntax.
Syntax incompatibilities found during the upgrade check. Refer to your upgrade check report for details.
Except for the scenarios above, Dynamic Tables from V3.0 do not require recreation in Hologres V3.1. However, only ALTER DYNAMIC TABLE can be performed on them. CREATE DYNAMIC TABLE (old syntax) is not supported in V3.1+.
Limitations
The syntax conversion command is restricted to non-partitioned tables (both Incremental and Full-refresh). For partitioned Dynamic Tables from V3.0, recreate them manually.
View Dynamic Tables requiring syntax conversion
Find tables in your instance that need conversion after an upgrade:
Non-partitioned tables
SELECT DISTINCT
p.dynamic_table_namespace as table_namespace,
p.dynamic_table_name as table_name
FROM hologres.hg_dynamic_table_properties p
JOIN pg_class c ON c.relname = p.dynamic_table_name
JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = p.dynamic_table_namespace
WHERE p.property_key = 'refresh_mode'
AND p.property_value = 'incremental'
AND c.relispartition = false
AND c.relkind != 'p';Partitioned tables
SELECT DISTINCT
pn.nspname as parent_schema,
pc.relname as parent_name
FROM hologres.hg_dynamic_table_properties p
JOIN pg_class c ON c.relname = p.dynamic_table_name
JOIN pg_namespace n ON n.oid = c.relnamespace AND n.nspname = p.dynamic_table_namespace
JOIN pg_inherits i ON c.oid = i.inhrelid
JOIN pg_class pc ON pc.oid = i.inhparent
JOIN pg_namespace pn ON pn.oid = pc.relnamespace
WHERE p.property_key = 'refresh_mode'
AND p.property_value = 'incremental'
AND c.relispartition = true
AND c.relkind != 'p';Perform syntax conversion
Take note of the following:
Version requirement: V3.1.11 and later.
Role requirement: Superuser.
Post-conversion behavior changes:
Automatic refresh starts immediately if the refresh mode is
auto. Ensure the operation occurs during off-peak hours to avoid resource contention. For better isolation, use Serverless Computing resources.Resource usage changes for virtual warehouse instances:
V3.1/V3.2 (new syntax): Refreshing Dynamic Tables use resources from the primary virtual warehouses of base and dynamic tables' Table Groups.
V3.0 (legacy syntax) and V4.1 (new syntax): Refreshing Dynamic Tables use resources from the primary virtual warehouse of the Dynamic Table's Table Group.
New syntax requires more connections: This enables a stable and efficient scheduling mechanism, adding one connection per Dynamic Table. If your instance has high connection usage and hundreds of or more Dynamic Tables, clear idle connections first.
Commands:
-- Only for non-partitioned tables (full and incremental).
-- Convert a single Dynamic Table
call hg_dynamic_table_config_upgrade('<table_name>');
-- Convert all Dynamic Tables. Use with caution.
call hg_upgrade_all_normal_dynamic_tables();These commands convert Dynamic Tables (old syntax) in the current database to the new syntax.
Syntax parameter mappings
The command maps V3.0 and V3.1 parameters and values as follows:
Legacy syntax (V3.0) | New syntax (V3.1+) | Description |
|
| The parameter value is preserved after conversion. For example, |
|
| The parameter value is preserved after conversion. |
|
| The E.g., |
| ||
|
| The parameter value is preserved after conversion. E.g., |
|
| The parameter value is preserved after conversion. |
|
| The parameter value is preserved after conversion. For example, |
Table properties (e.g., | Table properties (e.g., | Basic table properties remain unchanged. |
References
FAQ
Q: How do I fix the error with a null segment or clustering key? Example:
ERROR: commit ddl phase1 failed: the index partition key "xxx" should not be nullableCause: A Dynamic Table's segment or clustering key is non-nullable. For rules on setting these keys, see Event time column (segment key).
Solution:
Clustering key error: For Hologres earlier than V3.1.26, V3.2.9, V4.0.0, upgrade your instance and modify the following GUC to allow a nullable clustering key:
-- For V3.1 and later ALTER DYNAMIC TABLE [ IF EXISTS ] [<schema>.]<table_name> SET (refresh_guc_hg_experimental_enable_nullable_segment_key=true);Segment key error: Run the following command to allow a nullable segment key. Starting from Hologres V4.1, segment keys are nullable allowed by default, so we recommend upgrading your instance to fix the error.
--For V3.1 and later, allows a nullable clustering key ALTER DYNAMIC TABLE [ IF EXISTS ] [<schema>.]<table_name> SET (refresh_guc_hg_experimental_enable_nullable_clustering_key=true);