From 2b533be3ba50327949abf119adf9b388c85263bf Mon Sep 17 00:00:00 2001 From: Daniel Zhang Date: Thu, 14 Oct 2021 13:28:02 -0400 Subject: [PATCH] Hetu-Docs Changes: Preagg Chinese Translation Document Additions --- hetu-docs/en/index.md | 3 - hetu-docs/en/preagg/overview.md | 42 ++-- hetu-docs/en/preagg/statements.md | 10 +- .../zh/images/cube-logical-plan-optimizer.png | Bin 0 -> 38432 bytes hetu-docs/zh/index.md | 4 + hetu-docs/zh/preagg/overview.md | 221 ++++++++++++++++++ hetu-docs/zh/preagg/statements.md | 175 ++++++++++++++ 7 files changed, 426 insertions(+), 29 deletions(-) create mode 100644 hetu-docs/zh/images/cube-logical-plan-optimizer.png create mode 100644 hetu-docs/zh/preagg/overview.md create mode 100644 hetu-docs/zh/preagg/statements.md diff --git a/hetu-docs/en/index.md b/hetu-docs/en/index.md index 61214ebf0..a8d01d4a7 100644 --- a/hetu-docs/en/index.md +++ b/hetu-docs/en/index.md @@ -158,8 +158,6 @@ headless: true - [GRANT ROLES]({{< relref "./docs/sql/grant-roles.md" >}}) - [INSERT]({{< relref "./docs/sql/insert.md" >}}) - [INSERT OVERWRITE]({{< relref "./docs/sql/insert-overwrite.md" >}}) - - [INSERT CUBE]({{< relref "./docs/sql/insert-cube.md" >}}) - - [INSERT OVERWRITE CUBE]({{< relref "./docs/sql/insert-overwrite-cube.md" >}}) - [JMX]({{< relref "./docs/sql/jmx.md" >}}) - [PREPARE]({{< relref "./docs/sql/prepare.md" >}}) - [RESET SESSION]({{< relref "./docs/sql/reset-session.md" >}}) @@ -174,7 +172,6 @@ headless: true - [SHOW COLUMNS]({{< relref "./docs/sql/show-columns.md" >}}) - [SHOW CREATE TABLE]({{< relref "./docs/sql/show-create-table.md" >}}) - [SHOW CREATE VIEW]({{< relref "./docs/sql/show-create-view.md" >}}) - - [SHOW CUBES]({{< relref "./docs/sql/show-cubes.md" >}}) - [SHOW FUNCTIONS]({{< relref "./docs/sql/show-functions.md" >}}) - [SHOW EXTERNAL FUNCTION]({{< relref "./docs/sql/show-external-function.md" >}}) - [SHOW GRANTS]({{< relref "./docs/sql/show-grants.md" >}}) diff --git a/hetu-docs/en/preagg/overview.md b/hetu-docs/en/preagg/overview.md index 7456ba6fd..b4dd72c12 100644 --- a/hetu-docs/en/preagg/overview.md +++ b/hetu-docs/en/preagg/overview.md @@ -37,7 +37,7 @@ The following picture depicts the change in the logical plan after the optimizat ![img](../images/cube-logical-plan-optimizer.png) ## Recommended Usage -1. Cubes are mose useful for iceberg queries that takes huge input and produces small input +1. Cubes are most useful for iceberg queries that takes huge input and produces small input. 2. Query performance is best when size of the Cube is less that on the actual table on which Cube was built. 3. Cubes need to be rebuilt if the source table is updated. @@ -47,7 +47,7 @@ operation on the update is considered as a change in the existing data even if o can't be differentiated, Cubes can't be used as it might result in incorrect result. We are working on a solution to overcome this limitation. ## Supported Connectors -The following are supported Connectors for storing a cube +The following are supported Connectors for storing a Cube 1. Hive 2. Memory 3. Clickhouse @@ -58,7 +58,7 @@ The following are supported Connectors for storing a cube 2.1. Overcome the limitation of Creating Cube for larger dataset. - 2.2. Update cube if source table has been updated. + 2.2. Update Cube if source table has been updated. ## Enabling and Disabling StarTree Cube To enable: @@ -73,13 +73,13 @@ SET SESSION enable_star_tree_index=false; ## Configuration Properties | Property Name | Default Value | Required| Description| |---------------------------------------------------|---------------------|---------|--------------| -| optimizer.enable-star-tree-index | false | No | Enables StarTree Cube| -| cube.metadata-cache-size | 50 | No | The maximum number of metadata for StarTree Cubes that could be loaded into cache before eviction happens| +| optimizer.enable-star-tree-index | false | No | Enables StarTree Cube | +| cube.metadata-cache-size | 50 | No | The maximum number of metadata for StarTree Cubes that could be loaded into cache before eviction happens | | cube.metadata-cache-ttl | 1h | No | The maximum time to live of StarTree Cubes that are be loaded into cache before eviction happens | ## Dependencies -StarTree Cube relies on Hetu metastore to store the Cube related metadata. +StarTree Cube relies on Hetu Metastore to store the Cube related metadata. Please check [Hetu Metastore](../admin/meta-store.md) for more information. ## Examples @@ -118,11 +118,11 @@ SELECT nationkey, avg(nationkey), max(regionkey) FROM nation WHERE nationkey >= Since the data inserted into the Cube was for `nationkey >= 5`, only queries matching this condition will utilize the Cube. Queries not matching the condition would continue to work but won't use the Cube. -## Building Cube for Large dataset +## Building Cube for Large Dataset One of the limitations with the current implementation is that Cube cannot be built for a larger dataset at once. This is due to the cluster memory limitation. Processing large number of rows requires more memory than cluster is configured with. This results in query failing with message **Query exceeded per-node user memory -limit**. To overcome this issue, **INSERT INTO CUBE** sql support was added. The user has ability to build a Cube for larger data by executing multiple -insert into cube statements. The insert statement accepts a where clause, and it can be used to limit the number of processed and inserted into Cube. +limit**. To overcome this issue, **INSERT INTO CUBE** SQL support was added. The user has ability to build a Cube for larger data by executing multiple +insert into Cube statements. The insert statement accepts a where clause, and it can be used to limit the number of processed and inserted into Cube. This section explains the steps to build a Cube for larger dataset. @@ -146,7 +146,7 @@ INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk BETWEEN 2451911 AND 2422 ``` ### Solution 1) -To overcome this issue, multiple insert statements can be used into process rows and insert into cube and the number of rows can be limited by using where clause; +To overcome this issue, multiple insert statements can be used to process rows and insert into Cube and the number of rows can be limited by using where clause; ```sql INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk BETWEEN 2451911 AND 2452010; @@ -157,8 +157,8 @@ INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk BETWEEN 2452211 AND 2452 ### Solution 2) CLI has been modified to support creating Cubes for larger dataset and without need for multiple insert statements. CLI internally handles this process. -Once the user runs create cube statement with where clause, the CLI takes care of creating the cube as well as inserting the data into it. This process improves the user experience and -improves the memory footprint based on the cluster memory limits. CLI internally parses the converts the statement into one create cube statement followed by +Once the user runs create Cube statement with where clause, the CLI takes care of creating the Cube as well as inserting the data into it. This process improves the user experience and +improves the memory footprint based on the cluster memory limits. CLI internally parses the converts the statement into one create Cube statement followed by one or more insert statements. This change is only works if user executes the command from CLI and not via any other means i.e. JDBC, etc... ```sql @@ -183,13 +183,13 @@ SHOW CUBES; `Integer, TinyInt, SmallInt, BigInt, Date` For other data types, it is difficult to identify if two predicates are continuous therefore they cannot be merged together. And because of this issue, there is - possibility that particular cube may not be used during query optimization even if the cube has all the required data. For example, + possibility that particular Cube may not be used during query optimization even if the Cube has all the required data. For example, ```sql INSERT INTO CUBE store_sales_cube WHERE store_id BETWEEN 'A01' AND 'A10'; INSERT INTO CUBE store_sales_cube WHERE store_id BETWEEN 'A11' AND 'A20'; ``` - Here these two predicates cannot be merged into store_id BETWEEN 'A01' AND 'A20'; So the cube won't be used + Here these two predicates cannot be merged into store_id BETWEEN 'A01' AND 'A20'; So the Cube won't be used for queries that are spanning over two the predicates; ```sql @@ -199,23 +199,23 @@ SHOW CUBES; ```sql INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk > 2451911; -``` - The predicate is rewriten as ss_sold_date_sk >= 2451912 to be prepare for merging continous predicates. +``` + The predicate is rewritten as ss_sold_date_sk >= 2451912 to be prepare for merging continous predicates. Since the predicate is rewritten, they query using ss_sold_date_sk > 2451911 predicate will not match with Cube predicate so Cube won't be used to optimize the query. The same is applicable for predicates with <= operator. ie. ss_sold_date_sk <= 2451911 is rewritten as ss_sold_date_sk < 2451912 ```sql SELECT ss_sold_date_sk, .... FROM hive.tpcds_sf1.store_sales WHERE ss_sold_date_sk > 2451911 ``` -3. Only Single column predicates can be merged. +3. Only single column predicates can be merged. ## Open issues and Limitations 1. StarTree Cube is only effective when the group by cardinality is considerably fewer than the number of rows in source table. 2. A significant amount of user effort required in maintaining Cubes for large datasets. -3. Only incremental insert into cube is supported. Cannot delete specific rows from Cube. +3. Only incremental insert into Cube is supported. Cannot delete specific rows from Cube. 4. Cubes created on a transaction table may expire automatically even if the source table has not been updated. This is due to the compaction policy which merges delta files into single large ORC file which in turn changes the last modified of time of the table. Cube status is determined by comparing last modified - timestamp of table when cube was created with the last modified time of the table when queries are executed. -5. Openlookeng CLI has been modified to ease the process of creating Cubes for larger datasets. But still there are limitations with this implementation - as the process involves merging multiple cube predicates into one. Only cube predicates defined on Integer, Long and Date types can be merged properly. Support for Char, + timestamp of table when Cube was created with the last modified time of the table when queries are executed. +5. OpenLooKeng CLI has been modified to ease the process of creating Cubes for larger datasets. But still there are limitations with this implementation + as the process involves merging multiple Cube predicates into one. Only Cube predicates defined on Integer, Long and Date types can be merged properly. Support for Char, String types still need to be implemented. \ No newline at end of file diff --git a/hetu-docs/en/preagg/statements.md b/hetu-docs/en/preagg/statements.md index 5b423ac13..e071dd7ea 100644 --- a/hetu-docs/en/preagg/statements.md +++ b/hetu-docs/en/preagg/statements.md @@ -44,7 +44,7 @@ Create a new partitioned Cube `orders_cube`: partitioned_by = ARRAY['orderdate'] ) -Create a new Cube `orders_cube` with some source data filter +Create a new Cube `orders_cube` with some source data filter: CREATE CUBE orders_cube ON orders WITH ( AGGREGATIONS = ( SUM(totalprice), COUNT DISTINCT(orderid) ), @@ -52,7 +52,7 @@ Create a new Cube `orders_cube` with some source data filter FILTER = (orderdate BETWEEN 2512450 AND 2512460) ) -Create a new Cube `orders_cube` with some additional predicate on Cube columns +Create a new Cube `orders_cube` with some additional predicate on Cube columns: CREATE CUBE orders_cube ON orders WITH ( AGGREGATIONS = ( SUM(totalprice), COUNT DISTINCT(orderid) ), @@ -60,7 +60,7 @@ Create a new Cube `orders_cube` with some additional predicate on Cube columns FILTER = (orderdate BETWEEN 2512450 AND 2512460) ) WHERE orderstatus = 'PENDING'; -This is same as following +This is same as following: CREATE CUBE orders_cube ON orders WITH ( AGGREGATIONS = ( SUM(totalprice), COUNT DISTINCT(orderid) ), @@ -86,12 +86,12 @@ INSERT INTO CUBE cube_name [WHERE condition] ``` ### Description -`CREATE CUBE` statement creates Cube without any data. To insert data into Cube, use `INSERT INTO CUBE` sql. +`CREATE CUBE` statement creates Cube without any data. To insert data into Cube, use `INSERT INTO CUBE` SQL. The `WHERE` clause is optional. If predicate is provided, only data matching the given predicate are processed from the source table and inserted into the Cube. Otherwise, entire data from the source table is processed and inserted into Cube. ### Examples -Insert data into the `orders_cube` Cube +Insert data into the `orders_cube` Cube: INSERT INTO CUBE orders_cube WHERE orderdate > date '1999-01-01'; INSERT INTO CUBE order_all_cube; diff --git a/hetu-docs/zh/images/cube-logical-plan-optimizer.png b/hetu-docs/zh/images/cube-logical-plan-optimizer.png new file mode 100644 index 0000000000000000000000000000000000000000..9ad984d519191eb1ffb4132eeb8d838d0961abaa GIT binary patch literal 38432 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

Cfbd=R3x-DB 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 LUM$kS_vHTooxV@* literal 0 HcmV?d00001 diff --git a/hetu-docs/zh/index.md b/hetu-docs/zh/index.md index 6a16cedd8..170efd9c3 100644 --- a/hetu-docs/zh/index.md +++ b/hetu-docs/zh/index.md @@ -62,6 +62,10 @@ headless: true - [HIndex语句]({{< relref "./docs/indexer/hindex-statements.md" >}}) - [new-index]({{< relref "./docs/indexer/new-index.md" >}}) +- [Star Tree多维数据集](#) + - [概述] ({{< relref "./docs/preagg/overview.md" >}}>) + - [语句] ({{< relref "./docs/preagg/statements.md" >}}) + - [连接器]({{< relref "./docs/connector/_index.md" >}}) - [CarbonData]({{< relref "./docs/connector/carbondata.md" >}}) - [ClickHouse]({{< relref "./docs/connector/clickhouse.md" >}}) diff --git a/hetu-docs/zh/preagg/overview.md b/hetu-docs/zh/preagg/overview.md new file mode 100644 index 000000000..79e55fe2e --- /dev/null +++ b/hetu-docs/zh/preagg/overview.md @@ -0,0 +1,221 @@ +# StarTree多维数据集 +## 介绍 +StarTree Cube,作为多维数据集,是存储为表格的物化预聚合结果。该技术旨在优化低延迟冰山查询。 +冰山查询是涉及**GROUP BY**和**HAVING**子句的SQL查询的一种特殊情况,其中答案集相对于扫描的数据大小而言较小。 +查询的特点是输入量大,输出量小。 + +此技术允许用户在现有表上构建Cubes,其中包含旨在优化特定查询的聚合和维度。 +Cubes是汇总预聚合,与原始表相比,其维度和行数更少。 +较少的行数意味着花费在表扫描上的时间显着减少,从而减少查询延迟。 +如果查询是预聚合表的维度和度量的子集, +那么Cube可以用来计算查询,而无需访问原始表。 + +Cube有以下几个属性 + - Cubes以表格格式存储 + - 一般来说,可以为任何连接器中的任何表创建Cubes并存储在另一个连接器中 + - 通过重写逻辑计划以使用Cube而不是原始表来减少查询延迟。 + +## Cube的多维数据集优化器规则 +作为逻辑计划优化的一部分,Cube优化器规则使用Cubes分析和优化逻辑计划的聚合子树。 +该规则查找通常如下所示的聚合子树 + +``` +AggregationNode +|- ProjectNode[Optional] + |- ProjectNode[Optional] + |- FilterNode[Optional] + |- ProjectNode[Optional] + |- TableScanNode +``` + +规则通过子树解析,识别出与Cube元数据匹配的表名、聚合函数、where子句、group by子句 +识别任何可以帮助优化查询的Cube。在多个匹配的情况下,选择最近创建的Cube进行优化。如果找到任何匹配项,则整个 +使用Cube重写聚合子树。此优化器使用TupleDomain构造来匹配查询中提供的谓词是否可以被 +立方体。 + +下图描绘了优化后逻辑计划的变化。 + +![img](../images/cube-logical-plan-optimizer.png) + +## 推荐用法 +1. Cubes对于需要大量输入并产生少量输入的冰山查询最有用。 +2. 当Cube的大小小于构建Cube的实际表上的大小时,查询性能最佳。 +3. 如果源表更新,则需要重建Cubes。 + +**注意:** +如果在构建Cubes后更新源表,Cube优化器将忽略在表上创建的Cubes。原因是,任何 +即使在原始表中只插入了新行,对更新的操作也被视为对现有数据的更改。由于插入和更新 +不能区分,不能使用Cubes,因为它可能会导致不正确的结果。我们正在研究解决此限制的解决方案。 + +## 支持的连接器 +以下是用于存储Cube的支持的连接器 +1. Hive +2. Memory +3. Clickhouse + +## 未来的工作 +1. 支持更多JDBC连接器 +2. 简化Cube管理 + + 2.1. 克服为更大的数据集创建Cube的限制。 + + 2.2. 如果源表已更新,则更新Cube。 + +## 启用和禁用StarTree Cube +启用: +```sql +SET SESSION enable_star_tree_index=true; +``` +禁用: +```sql +SET SESSION enable_star_tree_index=false; +``` + +## 配置属性 +| 属性名称 | 默认值 | 是否必要 | 描述 | +|---------------------------------------------------|---------------------|---------|--------------| +| optimizer.enable-star-tree-index | false | 否 | 启动StarTree Cube | +| cube.metadata-cache-size | 50 | 否 | 在驱逐发生之前可以加载到缓存中的 StarTree Cube 的最大元数据数 | +| cube.metadata-cache-ttl | 1h | 否 | 在驱逐发生之前加载到缓存中的 StarTree Cube 的最大生存时间 | + +## 依赖关系 + +StarTree Cube依赖于Hetu Metastore来存储Cube相关的元数据。 +请查看[Hetu Metastore](../admin/meta-store.md)以获取更多信息。 + +## 例子 + +创建StarTree Cube: +```sql +CREATE CUBE nation_cube +ON nation +WITH (AGGREGATIONS=(count(*), count(distinct regionkey), avg(nationkey), max(regionkey)), +GROUP=(nationkey), +format='orc', partitioned_by=ARRAY['nationkey']); +``` +接下来,将数据添加到Cube: +```sql +INSERT INTO CUBE nation_cube WHERE nationkey >= 5; +``` +使用WHERE子句创建StarTree Cube: +请注意,以下查询仅通过CLI支持 + +```sql +CREATE CUBE nation_cube +ON nation +WITH (AGGREGATIONS=(count(*), count(distinct regionkey), avg(nationkey), max(regionkey)), +GROUP=(nationkey), +format='orc', partitioned_by=ARRAY['nationkey']) +WHERE nationkey >= 5; +``` + +当需要使用新的Cube时,只需使用包含在Cube中的聚合查询原始表: + +```sql +SELECT count(*) FROM nation WHERE nationkey >= 5 GROUP BY nationkey; +SELECT nationkey, avg(nationkey), max(regionkey) FROM nation WHERE nationkey >= 5 GROUP BY nationkey; +``` + +由于插入Cube的数据是为`nationkey >= 5`,只有匹配此条件的查询才会使用Cube。 +不符合条件的查询将继续工作,但不会使用Cube。 + +## 为大型数据集构建Cube +当前实现的限制之一是不能一次为更大的数据集构建Cube。这是由于集群内存限制。 +处理大量行需要比集群配置更多的内存。这会导致查询失败并显示消息**Query exceeded per-node user memory limit**,也就是警告查询超出每节点用户内存限制。为了克服这个问题,**INSERT INTO CUBE** SQL支持被添加了。 +用户可以通过执行多个操作来为更大的数据构建一个Cube插入到Cube语句中。insert语句接受一个where子句,它可以用来限制处理和插入到Cube中的数量。 + +本节介绍为更大的数据集构建Cube的步骤。 + +让我们以TPCDS数据集和`store_sales`表为例。该表有10年的数据, +用户想要构建2001年的Cube,由于集群内存限制,无法一次处理2001年的整个数据集。 + +```sql +CREATE CUBE store_sales_cube ON store_sales WITH (AGGREGATIONS = (sum(ss_net_paid), sum(ss_sales_price), sum(ss_quantity)), GROUP = (ss_sold_date_sk, ss_store_sk)); + +SELECT min(d_date_sk) as year_start, max(d_date_sk) as year_end FROM date_dim WHERE d_year = 2001; + year_start | year_end +------------+---------- + 2451911 | 2452275 +(1 row) +``` +如果需要处理的行数很大并且查询内存超过为集群配置的限制, +则以下查询可能会导致失败。 + +```sql +INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk BETWEEN 2451911 AND 242275; +``` + +### 解决方案1) +为了克服这个问题,可以使用多个insert语句来处理行并插入cube中,并且可以使用where子句来限制行数; + +```sql +INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk BETWEEN 2451911 AND 2452010; +INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk >= 2452011 AND ss_sold_date_sk <= 2452110; +INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk BETWEEN 2452111 AND 2452210; +INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk BETWEEN 2452211 AND 2452275; +``` + +### 解决方案2) +CLI已被修改以支持为更大的数据集创建Cubes,而不需要多个插入语句。CLI在内部处理这个过程。 +一旦用户运行带有where子句的create cube语句,CLI就会负责创建Cube并将数据插入其中。 +此过程改善了用户体验并改善了基于集群内存限制的内存占用。CLI在内部将转换语句解析为一个create Cube语句,然后是一个或多个insert语句。 +此更改仅在用户从CLI而非通过任何其他方式(例如JDBC等)执行命令时才有效。 + +```sql +CREATE CUBE store_sales_cube ON store_sales WITH (AGGREGATIONS = (sum(ss_net_paid), sum(ss_sales_price), sum(ss_quantity)), GROUP = (ss_sold_date_sk, ss_store_sk)) WHERE ss_sold_date_sk BETWEEN 2451911 AND 242275; +``` + +系统内部会重写所有连续的范围谓词并将其合并为单个谓词; + +```sql +SHOW CUBES; + + Cube Name | Table Name | Status | Dimensions | Aggregations | Where Clause +---------------------------------+----------------------------+--------+-----------------------------+-------------------------------------------------------+-------------------------------------------------------+------------------------------ + hive.tpcds_sf1.store_sales_cube | hive.tpcds_sf1.store_sales | Active | ss_sold_date_sk,ss_store_sk | sum(ss_sales_price),sum(ss_net_paid),sum(ss_quantity) | (("ss_sold_date_sk" >= BIGINT '2451911') AND ("ss_sold_date_sk" < BIGINT '2452276')) +``` + +**注意:** +1. 系统将尝试将所有类型的Predicates重写为Range以查看它们是否可以合并在一起。 + 所有连续谓词将合并为单个范围谓词,其余谓词保持不变。 + + 仅支持以下类型并且可以合并在一起。 + `Integer, TinyInt, SmallInt, BigInt, Date` + + 对于其他数据类型,很难确定两个谓词是否连续,因此它们不能合并在一起。 + 由于这个问题,即使Cube具有所有必需的数据,在查询优化期间也可能不会使用特定Cube。例如, + +```sql + INSERT INTO CUBE store_sales_cube WHERE store_id BETWEEN 'A01' AND 'A10'; + INSERT INTO CUBE store_sales_cube WHERE store_id BETWEEN 'A11' AND 'A20'; +``` + 这里这两个谓词不能合并到store_id BETWEEN 'A01' AND 'A20'; + 因此,Cube不会用于跨越两个谓词的查询; + +```sql + SELECT ss_store_id, sum(ss_sales_price) WHERE ss_store_id BETWEEN 'A05' AND 'A15'; - Cube won't be used for optimizing this query. This is a limitation as of now. +``` + 由于谓词重写,无法支持以下某些查询 + +```sql + INSERT INTO CUBE store_sales_cube WHERE ss_sold_date_sk > 2451911; +``` + 谓词重写为ss_sold_date_sk >= 2451912为合并连续谓词做准备。 + 由于谓词被重写,他们使用ss_sold_date_sk > 2451911谓词查询将与Cube谓词不匹配,因此不会使用Cube来优化查询。 + 这同样适用于带有<=运算符的谓词,例如,ss_sold_date_sk <= 2451911改写为ss_sold_date_sk < 2451912。 + +```sql + SELECT ss_sold_date_sk, .... FROM hive.tpcds_sf1.store_sales WHERE ss_sold_date_sk > 2451911 +``` +3. 只能合并单列谓词。 + +## 未解决的问题和限制 +1. StarTree Cube仅在按基数分组的数量远小于源表中的行数时有效。 +2. 维护大型数据集的Cubes需要大量的用户工作。 +3. 仅支持增量插入Cube。无法从Cube中删除特定行。 +4. 即使源表尚未更新,在事务表上创建的Cubes也可能会自动过期。 + 这是由于压缩策略将delta文件合并为单个大型ORC文件,这反过来又更改了表的最后修改时间。 + Cube状态是通过比较创建Cube时表的最后修改时间戳与执行查询时表的最后修改时间来确定的。 +5. OpenLooKeng CLI已经过修改,以简化为更大的数据集创建Cubes的过程。 + 但是这种实现仍然存在局限性,因为该过程涉及将多个Cube谓词合并为一个。 + 只有定义在Integer、Long和Date类型上的Cube谓词才能正确合并。 对Char、String类型的支持仍需实现。 \ No newline at end of file diff --git a/hetu-docs/zh/preagg/statements.md b/hetu-docs/zh/preagg/statements.md new file mode 100644 index 000000000..96c134741 --- /dev/null +++ b/hetu-docs/zh/preagg/statements.md @@ -0,0 +1,175 @@ +# 用法 +可以使用任何受支持的客户端管理Cubes,例如位于安装中`bin`目录下的hetu-cli。 + +## CREATE CUBE +### 概要 + +``` sql +CREATE CUBE [ IF NOT EXISTS ] +cube_name ON table_name WITH ( + AGGREGATIONS = ( expression [, ...] ), + GROUP = ( column_name [, ...]) + [, FILTER = (expression)] + [, ( property_name = expression [, ...] ) ] +) +[WHERE predicate] +``` +### 描述 +使用指定的组和聚合创建一个新的空Cube。使用`INSERT INTO CUBE (see below)`来插入数据。 + +如果Cube已经存在,可选的`IF NOT EXISTS`子句会导致错误被抑制。 +可选的`property_name`部分可用于在新创建的Cube上设置属性。 + +要列出所有可用的表属性,请运行以下查询: + + SELECT * FROM system.metadata.table_properties + +**注意:** 这些属性仅限于为其创建Cube的连接器。 + +### 例子 +在`orders`上创建一个新的Cube`orders_cube`: + + CREATE CUBE orders_cube ON orders WITH ( + AGGREGATIONS = ( SUM(totalprice), AVG(totalprice) ), + GROUP = ( orderstatus, orderdate ), + format = 'ORC' + ) + +创建一个新的分区Cube`orders_cube`: + + CREATE CUBE orders_cube ON orders WITH ( + AGGREGATIONS = ( SUM(totalprice), AVG(totalprice) ), + GROUP = ( orderstatus, orderdate ), + format = 'ORC', + partitioned_by = ARRAY['orderdate'] + ) + +使用一些源数据过滤器创建一个新的Cube`orders_cube`: + + CREATE CUBE orders_cube ON orders WITH ( + AGGREGATIONS = ( SUM(totalprice), COUNT DISTINCT(orderid) ), + GROUP = ( orderstatus ), + FILTER = (orderdate BETWEEN 2512450 AND 2512460) + ) + +创建一个新的Cube`orders_cube`,并在Cube列上添加一些额外的谓词: + + CREATE CUBE orders_cube ON orders WITH ( + AGGREGATIONS = ( SUM(totalprice), COUNT DISTINCT(orderid) ), + GROUP = ( orderstatus ), + FILTER = (orderdate BETWEEN 2512450 AND 2512460) + ) WHERE orderstatus = 'PENDING'; + +这与以下内容相同: + + CREATE CUBE orders_cube ON orders WITH ( + AGGREGATIONS = ( SUM(totalprice), COUNT DISTINCT(orderid) ), + GROUP = ( orderstatus ), + FILTER = (orderdate BETWEEN 2512450 AND 2512460) + ); + INSERT INTO CUBE orders_cube WHERE orderstatus = 'PENDING'; + +`FILTER`属性可用于在构建Cube时从源表中过滤掉数据。 +在对源表应用`orderdate BETWEEN 2512450 AND 2512460`谓词后,Cube建立在数据上。过滤谓词中使用的列不得属于Cube。 + +### 限制 +- 可以仅使用以下聚合函数创建Cubes。 + 换句话说,使用以下函数的查询只能使用Cubes进行优化。 + **COUNT, COUNT DISTINCT, MIN, MAX, SUM, AVG** +- 不同的连接器可能支持不同的数据类型和不同的表/列属性。 + +## INSERT INTO CUBE + +### 概要 +``` sql +INSERT INTO CUBE cube_name [WHERE condition] +``` + +### 描述 +`CREATE CUBE`语句创建没有任何数据的Cube。要将数据插入Cube,请使用`INSERT INTO CUBE`SQL。 +`WHERE`子句是可选的。如果提供了谓词,则只有与给定谓词匹配的数据才会从源表中处理并插入到Cube中。 +否则,源表中的整个数据将被处理并插入到Cube中。 + +### 例子 +将数据插入`orders_cube`Cube: + + INSERT INTO CUBE orders_cube WHERE orderdate > date '1999-01-01'; + INSERT INTO CUBE order_all_cube; + +### 限制 +1. 对同一个Cube的后续插入需要使用相同的列集 + +```sql + CREATE CUBE orders_cube ON orders WITH (AGGREGATIONS = (count(*)), GROUP = (orderdate)); + + INSERT INTO CUBE orders_cube WHERE orderdate BETWEEN date '1999-01-01' AND date '1999-01-05'; + + -- This statement would fail because its possible the Cube already contain rows matching the given predicate. + INSERT INTO CUBE orders_cube WHERE location = 'Canada'; +``` +**注意:** 这意味着在第一个插入中使用的列必须在第一个插入后的每个插入谓词中使用,以避免插入重复数据。 + +## INSERT OVERWRITE CUBE + +### 概要 +``` sql +INSERT OVERWRITE CUBE cube_name [WHERE condition] +``` + +### 描述 +类似于INSERT INTO CUBE语句,但使用此语句覆盖现有数据。 +谓词是可选的。 + +### 例子 +根据条件插入数据到`orders_cube`Cube: + + INSERT OVERWRITE CUBE orders_cube WHERE orderdate > date '1999-01-01'; + INSERT OVERWRITE CUBE orders_cube; + +## SHOW CUBES + +### 概要 +```sql +SHOW CUBES [ FOR table_name ]; +``` + +### 描述 +`SHOW CUBES`列出所有立方体。添加可选的`table_name`仅列出该表的Cubes。 + +### 例子 + +显示所有Cubes: +```sql + SHOW CUBES; +``` + +显示`orders`表的Cubes: + +```sql + SHOW CUBES FOR orders; +``` + +## DROP CUBE + +### 概要 + +``` sql +DROP CUBE [ IF EXISTS ] cube_name +``` + +### 描述 +删除存在的Cube. + +如果Cube不存在,可选的`IF EXISTS`子句会抑制报错。 + +### 例子 + +删除Cube`orders_cube`: + + DROP CUBE orders_cube + +如果存在,则删除Cube`orders_cube`: + + DROP CUBE IF EXISTS orders_cube + +