跳至主要内容
跳至主要内容

分析器

在 ClickHouse 24.3 版本中,新的查询分析器默认启用。 您可以在 这里 了解更多关于其工作原理的详细信息。

已知不兼容性

虽然修复了大量错误并引入了新的优化,但它也引入了一些 ClickHouse 行为上的破坏性更改。 请阅读以下更改,以确定如何重写您的查询以适应分析器。

无效查询不再被优化

以前的查询计划基础设施在查询验证步骤之前应用 AST 级别的优化。 优化可以重写初始查询使其有效并可执行。

在分析器中,查询验证发生在优化步骤之前。 这意味着以前可以执行的无效查询,现在不再受支持。 在这种情况下,必须手动修复查询。

示例 1

以下查询在聚合后仅可用 toString(number) 时,在投影列表中使用列 number。 在旧分析器中,GROUP BY toString(number) 被优化为 GROUP BY number, 使查询有效。

SELECT number
FROM numbers(1)
GROUP BY toString(number)

示例 2

此查询也存在相同的问题。 列 number 在聚合后与另一个键一起使用。 先前的查询分析器通过将 number > 5 过滤器从 HAVING 子句移动到 WHERE 子句来修复此查询。

SELECT
    number % 2 AS n,
    sum(number)
FROM numbers(10)
GROUP BY n
HAVING number > 5

要修复查询,您应该将所有适用于非聚合列的条件移动到 WHERE 部分,以符合标准的 SQL 语法

SELECT
    number % 2 AS n,
    sum(number)
FROM numbers(10)
WHERE number > 5
GROUP BY n

使用无效查询的 CREATE VIEW

分析器始终执行类型检查。 以前,可以创建一个包含无效 SELECT 查询的 VIEW。 然后它会在第一次 SELECTINSERT(对于 MATERIALIZED VIEW)期间失败。

现在不可能以这种方式创建 VIEW

示例

CREATE TABLE source (data String)
ENGINE=MergeTree
ORDER BY tuple();

CREATE VIEW some_view
AS SELECT JSONExtract(data, 'test', 'DateTime64(3)')
FROM source;

JOIN 子句的已知不兼容性

使用投影中的列进行 JOIN

默认情况下,SELECT 列表中的别名不能用作 JOIN USING 键。

一个新的设置,analyzer_compatibility_join_using_top_level_identifier,在启用时,会改变 JOIN USING 的行为,使其更倾向于基于 SELECT 查询的投影列表中的表达式来解析标识符,而不是直接使用左表中的列。

例如

SELECT a + 1 AS b, t2.s
FROM VALUES('a UInt64, b UInt64', (1, 1)) AS t1
JOIN VALUES('b UInt64, s String', (1, 'one'), (2, 'two')) t2
USING (b);

analyzer_compatibility_join_using_top_level_identifier 设置为 true 时,连接条件被解释为 t1.a + 1 = t2.b,匹配早期版本的行为。 结果将是 2, 'two'。 当设置是 false 时,连接条件默认为 t1.b = t2.b,查询将返回 2, 'one'。 如果 b 不存在于 t1 中,查询将因错误而失败。

使用 JOIN USINGALIAS/MATERIALIZED 列的行为变化

在分析器中,在涉及 ALIASMATERIALIZED 列的 JOIN USING 查询中使用 * 将默认包含这些列在结果集中。

例如

CREATE TABLE t1 (id UInt64, payload ALIAS sipHash64(id)) ENGINE = MergeTree ORDER BY id;
INSERT INTO t1 VALUES (1), (2);

CREATE TABLE t2 (id UInt64, payload ALIAS sipHash64(id)) ENGINE = MergeTree ORDER BY id;
INSERT INTO t2 VALUES (2), (3);

SELECT * FROM t1
FULL JOIN t2 USING (payload);

在分析器中,此查询的结果将包含来自两个表的 payload 列以及 id。 相比之下,以前的分析器只有在启用特定设置(asterisk_include_alias_columnsasterisk_include_materialized_columns)时才会包含这些 ALIAS 列,并且列的顺序可能不同。

为了确保一致且可预期的结果,尤其是在将旧查询迁移到分析器时,建议在 SELECT 子句中显式指定列,而不是使用 *

USING 子句中列的类型修饰符处理

在分析器的最新版本中,确定 USING 子句中指定的列的公共超类型的规则已标准化,以产生更可预测的结果,尤其是在处理 LowCardinalityNullable 等类型修饰符时。

  • LowCardinality(T)T:当类型为 LowCardinality(T) 的列与类型为 T 的列连接时,生成的公共超类型将为 T,从而有效地丢弃 LowCardinality 修饰符。
  • Nullable(T)T:当类型为 Nullable(T) 的列与类型为 T 的列连接时,生成的公共超类型将为 Nullable(T),从而确保保留可为空属性。

例如

SELECT id, toTypeName(id)
FROM VALUES('id LowCardinality(String)', ('a')) AS t1
FULL OUTER JOIN VALUES('id String', ('b')) AS t2
USING (id);

在此查询中,id 的公共超类型被确定为 String,从而丢弃了 t1LowCardinality 修饰符。

投影列名更改

在投影名称计算期间,别名不会被替换。

SELECT
    1 + 1 AS x,
    x + 1
SETTINGS enable_analyzer = 0
FORMAT PrettyCompact

   ┌─x─┬─plus(plus(1, 1), 1)─┐
1. │ 2 │                   3 │
   └───┴─────────────────────┘

SELECT
    1 + 1 AS x,
    x + 1
SETTINGS enable_analyzer = 1
FORMAT PrettyCompact

   ┌─x─┬─plus(x, 1)─┐
1. │ 2 │          3 │
   └───┴────────────┘

函数参数类型不兼容

在分析器中,类型推断发生在初始查询分析期间。 这种变化意味着类型检查在短路评估之前完成;因此,if 函数的参数必须始终具有公共超类型。

例如,以下查询失败,并显示 There is no supertype for types Array(UInt8), String because some of them are Array and some of them are not

SELECT toTypeName(if(0, [2, 3, 4], 'String'))

异构集群

分析器显著改变了集群中服务器之间的通信协议。 因此,在具有不同 enable_analyzer 设置值的服务器上运行分布式查询是不可能的。

变异由先前的分析器解释

变异仍然使用旧分析器。 这意味着一些新的 ClickHouse SQL 功能不能在变异中使用。 例如,QUALIFY 子句。 状态可以查看 这里

不支持的功能

分析器当前不支持的功能列表如下

  • Annoy 索引。
  • 假设索引。 正在进行中 这里
  • 窗口视图不受支持。 未来没有计划支持它。

云迁移

我们正在所有当前禁用它的实例上启用新的查询分析器,以支持新的功能和性能优化。 此更改强制执行更严格的 SQL 作用域规则,要求客户手动更新不合规的查询。

迁移工作流程

  1. 通过过滤 system.query_log 使用 normalized_query_hash 识别查询
SELECT query 
FROM clusterAllReplicas(default, system.query_log)
WHERE normalized_query_hash='{hash}' 
LIMIT 1 
SETTINGS skip_unavailable_shards=1
  1. 通过添加以下设置,在启用分析器的情况下运行查询。
SETTINGS
    enable_analyzer=1,
    analyzer_compatibility_join_using_top_level_identifier=1
  1. 重构并验证查询结果,以确保它们与禁用分析器时生成的输出匹配。

请参考在内部测试期间遇到的最常见的不兼容性。

未知的表达式标识符

错误:Unknown expression identifier ... in scope ... (UNKNOWN_IDENTIFIER)。 异常代码:47

原因:依赖于非标准、宽松的旧行为的查询,例如在过滤器中引用计算的别名、模棱两可的子查询投影或“动态” CTE 作用域,现在被正确识别为无效并立即拒绝。

解决方案:按照以下方式更新您的 SQL 模式

  • 过滤器逻辑:如果过滤结果,则将逻辑从 WHERE 移动到 HAVING;如果过滤源数据,则在 WHERE 中复制表达式。
  • 子查询作用域:显式选择外部查询所需的所有列。
  • JOIN 键:如果键是别名,请使用带有完整表达式的 ON 代替 USING。
  • 在外部查询中,引用 Subquery/CTE 本身,而不是其中的表。

GROUP BY 中未聚合的列

错误:Column ... is not under aggregate function and not in GROUP BY keys (NOT_AN_AGGREGATE)。 异常代码:215

原因:旧分析器允许选择不在 GROUP BY 子句中的列(通常选择任意值)。 分析器遵循标准 SQL:每个选择的列必须是聚合或分组键。

解决方案:将列包装在 any()argMax() 中,或将其添加到 GROUP BY。

/* ORIGINAL QUERY */
-- device_id is ambiguous
SELECT user_id, device_id FROM table GROUP BY user_id

/* FIXED QUERY */
SELECT user_id, any(device_id) FROM table GROUP BY user_id
-- OR
SELECT user_id, device_id FROM table GROUP BY user_id, device_id

重复的 CTE 名称

错误:CTE with name ... already exists (MULTIPLE_EXPRESSIONS_FOR_ALIAS)。 异常代码:179

原因:旧分析器允许定义多个具有相同名称的公共表表达式 (WITH...),从而遮蔽了较早的表达式。 分析器禁止这种歧义。

解决方案:重命名重复的 CTE,使其唯一。

/* ORIGINAL QUERY */
WITH 
  data AS (SELECT 1 AS id), 
  data AS (SELECT 2 AS id) -- Redefined
SELECT * FROM data;

/* FIXED QUERY */
WITH 
  raw_data AS (SELECT 1 AS id), 
  processed_data AS (SELECT 2 AS id)
SELECT * FROM processed_data;

模棱两可的列标识符

错误:JOIN [JOIN TYPE] ambiguous identifier ... (AMBIGUOUS_IDENTIFIER) 异常代码:207

原因:查询引用了在 JOIN 中多个表中存在的列名,而没有指定源表。 旧分析器通常基于内部逻辑猜测列,分析器需要显式名称。

解决方案:使用 table_alias.column_name 完全限定列。

/* ORIGINAL QUERY */
SELECT table1.ID AS ID FROM table1, table2 WHERE ID...

/* FIXED QUERY */
SELECT table1.ID AS ID_RENAMED FROM table1, table2 WHERE ID_RENAMED...

FINAL 的无效用法

错误:Table expression modifiers FINAL are not supported for subquery...Storage ... doesn't support FINAL (UNSUPPORTED_METHOD)。 异常代码:1, 181

原因:FINAL 是表存储的修饰符(特别是 [Shared]ReplacingMergeTree)。 分析器拒绝将 FINAL 应用于

  • 子查询或派生表(例如,FROM (SELECT ...) FINAL)。
  • 不支持它的表引擎(例如,SharedMergeTree)。

解决方案:仅将 FINAL 应用于子查询内部的源表,或者如果引擎不支持,则将其移除。

/* ORIGINAL QUERY */
SELECT * FROM (SELECT * FROM my_table) AS subquery FINAL ...

/* FIXED QUERY */
SELECT * FROM (SELECT * FROM my_table FINAL) AS subquery ...

countDistinct() 函数大小写敏感性

错误:Function with name countdistinct does not exist (UNKNOWN_FUNCTION)。异常代码:46

原因:函数名在分析器中区分大小写或严格映射。countdistinct(全部小写)不再自动解析。

解决方案:使用标准的 countDistinct(驼峰命名法)或 ClickHouse 特有的 uniq。

    © . This site is unofficial and not affiliated with ClickHouse, Inc.