Databricks Runtime 中的 ANSI 合规性

适用于:勾选“是” Databricks Runtime

本文介绍 Databricks Runtime 中的 ANSI 合规性。 有关 Databricks SQL 中的 ANSI 模式,请参阅 ANSI_MODE

Spark SQL 提供两个有助于符合 ANSI SQL 标准的选项:spark.sql.ansi.enabledspark.sql.storeAssignmentPolicy

spark.sql.ansi.enabled 设置为 true 时,Spark SQL 使用符合 ANSI 的方言(而不是符合 Hive)。 例如,如果 SQL 运算符/函数的输入无效,Spark 会在运行时引发异常,而不是返回 NULL 结果。 某些 ANSI 方言特征可能并非直接来自 ANSI SQL 标准,但它们的行为与 ANSI SQL 的风格一致。

此外,在表中存储行时,Spark SQL 提供独立的选项来控制隐式强制转换行为。 强制转换行为在标准为定义为存储分配规则。

spark.sql.storeAssignmentPolicy 设置为 ANSI 时,Spark SQL 将遵循 ANSI 存储分配规则。 这是一个单独的配置,因为其默认值为 ANSI,而配置 spark.sql.ansi.enabled 默认处于禁用状态。

下表对此行为进行了汇总:

属性名称 默认 含义
spark.sql.ansi.enabled 为 true 时,Spark 会尝试遵循 ANSI SQL 规范:
  • 如果在整数或十进制字段的任何作中发生溢出,则引发运行时异常。
  • 禁止使用 ANSI SQL 的保留关键字作为 SQL 分析器中的标识符。
spark.sql.storeAssignmentPolicy ANSI(美国国家标准协会) 向列中存储另一数据类型的值时,Spark 会执行类型转换。 类型强制转换规则有三种策略:ANSIlegacystrict
  • ANSI:Spark 根据 ANSI SQL 执行类型强制转换。 实际上,此行为与 PostgreSQL 基本相同。 它不允许某些不合理的类型转换,例如将字符串转换为 int 或将 double 转换为布尔。
  • legacy:Spark 允许类型强制转换,前提是它是有效的强制转换,这非常宽松。 例如,允许将字符串转换为 int 或将 double 转换为布尔。 它也是 Spark 2.x 中的唯一行为,并与 Hive 兼容。
  • strict:Spark 不允许在类型转换过程中出现任何可能的精度丢失或数据截断,例如,不允许将 double 转换为 int 或将 decimal 转换为 double。

以下各子部分显示了启用 ANSI 模式时算术运算、类型转换和 SQL 分析的行为变更。 Spark SQL 中的类型转换有三种,本文将一一介绍它们:强制转换、存储分配和类型强制转换。

算术运算

在 Spark SQL 中,默认情况下,不会检查对数值类型(十进制数除外)执行的算术运算是否溢出。 这意味着,如果某个操作导致溢出,则结果与 Java 或 Scala 程序中相应的操作结果相同(例如,如果 2 个整数的和大于可表示的最大值,则结果为负数)。 另一方面,对于十进制数溢出,Spark SQL 会返回 NULL。 将 spark.sql.ansi.enabled 设置为 true,并且数值和间隔算术运算中发生溢出时,它会在运行时引发算术异常。

-- `spark.sql.ansi.enabled=true`
> SELECT 2147483647 + 1;
 error: integer overflow

-- `spark.sql.ansi.enabled=false`
> SELECT 2147483647 + 1;
  -2147483648

强制转换

根据标准中定义的非法强制转换模式,将 spark.sql.ansi.enabled 设置为 true 时,通过 CAST 语法进行显式强制转换(例如将字符串转换为整数)会引发运行时异常。

Spark ANSI 模式的 CAST 子句遵循 ISO/IEC 9075-2:2011 信息技术 — 数据库语言 - SQL — 第 2 部分:Foundation (SQL/Foundation) 中 6.13 节“强制转换规范”的语法规则,但它特别允许 ANSI 标准禁止的以下简单类型转换:

  • 数字类型 <=> 布尔类型
  • 字符串类型 <=> 二进制类型

下表提供了 CAST 表达式中源数据类型和目标数据类型的有效组合。 “Y”表示组合在语法上有效(没有限制),“N”表示组合无效。

SourceTarget 数值 字符串 日期 时间戳 时间间隔 布尔 二进制 数组 映射 结构
数值 Y Y N N N Y N N N N
字符串 Y Y Y Y Y Y Y N N N
日期 N Y Y Y N N N N N N
时间戳 N Y Y Y N N N N N N
时间间隔 N Y N N Y N N N N N
布尔 Y Y N N N Y N N N N
二进制 Y N N N N N Y N N N
数组 N N N N N N N Y N N
映射 N N N N N N N N Y N
结构 N N N N N N N N N Y
-- Examples of explicit casting

-- `spark.sql.ansi.enabled=true`
> SELECT CAST('a' AS INT);
  ERROR: [CAST_INVALID_INPUT] The value 'a' of the type "STRING" cannot be cast to "INT" because it is malformed.

> SELECT CAST(2147483648L AS INT);
  ERROR: [CAST_OVERFLOW] The value 2147483648L of the type "BIGINT" cannot be cast to "INT" due to an overflow.

> SELECT CAST(DATE'2020-01-01' AS INT)
  ERROR: [DATATYPE_MISMATCH.CAST_WITH_FUNC_SUGGESTION] Cannot resolve "CAST(DATE '2020-01-01' AS INT)" due to data type mismatch: cannot cast "DATE" to "INT".

-- `spark.sql.ansi.enabled=false` (This is a default behavior)
> SELECT cast('a' AS INT);
  null

> SELECT CAST(2147483648L AS INT);
  -2147483648

> SELECT CAST(DATE'2020-01-01' AS INT);
  null

存储分配

此设置 spark.sql.storeAssignmentPolicy 默认设置为 ANSI. 使用此设置时,当源值的数据类型与目标列类型不匹配时,Spark SQL 会自动向 INSERT 语句添加 ANSI CAST 子句。 在此策略下插入表期间,Spark 会检查并拒绝无效强制转换,引发异常以确保数据质量。 这意味着,如果插入尝试由于类型不匹配而失败,则不会导致任何数据部分写入表中。

示例:

-- spark.sql.storeAssignmentPolicy=ANSI
> CREATE TABLE test(i INT);
> INSERT INTO test VALUES (2147483648L);
  ERROR: [CAST_OVERFLOW_IN_TABLE_INSERT] Fail to insert a value of "BIGINT" type into the "INT" type column `i` due to an overflow.

> INSERT INTO test VALUES ('a');
  ERROR: [CAST_INVALID_INPUT ERROR] The value 'a' of the type "STRING" cannot be cast to "INT" because it is malformed

这些示例显示 Spark SQL 会防止插入不兼容的数据,从而保持数据完整性。

spark.sql.storeAssignmentPolicy 设置为 LEGACY 时,Spark SQL 会回退到 Spark 2.x 之前普遍存在的行为。 在此模式下,它会应用旧的 CAST 操作,而不是使用 ANSI CAST。 在此策略下,在表插入期间无效强制转换会导致插入 NULL 值或错误值,而不是引发异常。 示例:

-- spark.sql.storeAssignmentPolicy=LEGACY
> CREATE TABLE test(i INT);
> INSERT INTO test VALUES (2147483648L);
> INSERT INTO test VALUES ('a');
> SELECT * FROM test;

-- Results
--  -2147483648 (incorrect value due to overflow)
--  null (cannot cast 'a' to INT)

类型强制

类型提升和优先级

spark.sql.ansi.enabled 设置为 true 时,Spark SQL 使用几个规则来管理解决数据类型之间冲突的方式。 此冲突解决方法的核心是类型优先级列表,该列表定义给定数据类型的值是否可以隐式提升为另一种数据类型。

数据类型 优先级列表(从低到高)
字节(Byte) 字节 -> 短整型 -> 整型 -> 长整型 -> 十进制 -> 单精度浮点数* -> 双精度浮点数
Short - Int -> Long ->> Decimal-> Float* -> Double
int Int - Long ->> Decimal -> Float* -> Double
长整型 Long - Decimal ->> Float* -> Double
小数 十进制 -> 浮点* -> 双精度
漂浮 Float -> Double
加倍 加倍
日期 日期 -> 时间戳
时间戳 时间戳
字符串 字符串
二进制 二进制
布尔 布尔
时间间隔 时间间隔
映射 地图**
数组 数组**
结构 结构**
  • 对于最不常见的类型解析,会跳过 Float 以避免精度损失。

** 对于复杂类型,优先级规则以递归顺序应用于其组件元素。

特殊规则适用于字符串类型和非类型化 NULL。 NULL 可以提升为任何其他类型,而字符串可以提升为任何简单数据类型。

这是对有向树形式的优先级列表的图形描述:优先级规则的图形表示形式

最不常见的类型解析

一组类型中最不常见的类型是在由该类型组的所有元素构成的优先级列表中可达到的最低优先级的类型。

最不常见的类型解析用于:

  • 确定是否可以使用优先级较低类型的实参调用需要某类型的形参的函数。
  • 为需要多个形参的共享实参类型的函数(例如 coalesce、least 或 greatest)派生实参类型。
  • 派生运算符(如算术运算符或比较运算符)的操作数类型。
  • 派生表达式(如 case 表达式)的结果类型。
  • 派生数组和映射构造函数的元素、键或值类型。

如果最不常见的类型解析为 FLOAT,则应用特殊规则。 使用 float 类型值时,如果任何类型为 INT、BIGINT 或 DECIMAL,则推出的最不常见类型均为 DOUBLE,以避免可能的数字丢失。

-- The coalesce function accepts any set of argument types as long as they share a least common type.
-- The result type is the least common type of the arguments.
> SET spark.sql.ansi.enabled=true;

> SELECT typeof(coalesce(1Y, 1L, NULL));
BIGINT

> SELECT typeof(coalesce(1, DATE'2020-01-01'));
Error: Incompatible types [INT, DATE]

> SELECT typeof(coalesce(ARRAY(1Y), ARRAY(1L)));
ARRAY<BIGINT>

> SELECT typeof(coalesce(1, 1F));
DOUBLE

> SELECT typeof(coalesce(1L, 1F));
DOUBLE

> SELECT (typeof(coalesce(1BD, 1F)));
DOUBLE

-- The substring function expects arguments of type INT for the start and length parameters.
> SELECT substring('hello', 1Y, 2);
he

> SELECT substring('hello', '1', 2);
he

> SELECT substring('hello', 1L, 2);
Error: Argument 2 requires an INT type.

> SELECT substring('hello', str, 2) FROM VALUES(CAST('1' AS STRING)) AS T(str);
Error: Argument 2 requires an INT type.

SQL 函数

在 ANSI 模式 (spark.sql.ansi.enabled=true) 下,某些 SQL 函数的行为可能会有所不同。

  • size:在 ANSI 模式下,对于 NULL 输入,此函数将返回 NULL。
  • %>
    • 如果使用无效索引,则此函数会引发 ArrayIndexOutOfBoundsException
    • 如果映射中不存在键,则此函数会引发 NoSuchElementException
  • elt:如果使用无效索引,则此函数会引发 ArrayIndexOutOfBoundsException
  • make_date:如果结果日期无效,则此函数会失败并出现异常。
  • make_timestamp:如果结果时间戳无效,则此函数会失败并出现异常。
  • make_interval:如果结果间隔无效,则此函数会失败并出现异常。
  • next_day:如果输入不是有效的星期几,则此函数会引发 IllegalArgumentException
  • parse_url:如果输入字符串不是有效的 URL,则此函数会引发 IllegalArgumentException
  • to_date:如果无法分析输入字符串或模式字符串无效,则此函数会失败并出现异常。
  • to_timestamp:如果无法分析输入字符串或模式字符串无效,则此函数会失败并出现异常。
  • to_unix_timestamp:如果无法分析输入字符串或模式字符串无效,则此函数会失败并出现异常。
  • unix_timestamp:如果无法分析输入字符串或模式字符串无效,则此函数会失败并出现异常。

SQL 运算符

在 ANSI 模式 (spark.sql.ansi.enabled=true) 下,某些 SQL 运算符的行为可能会有所不同。

  • array_col[index]:如果使用无效索引,则此运算符会引发 ArrayIndexOutOfBoundsException
  • map_col[key]:如果映射中不存在键,则此运算符会引发 NoSuchElementException
  • CAST(string_col AS TIMESTAMP):如果无法分析输入字符串,则此运算符会失败并出现异常。
  • CAST(string_col AS DATE):如果无法分析输入字符串,则此运算符会失败并出现异常。

适用于 ANSI 模式的函数

ANSI 模式打开时,会针对无效操作引发异常。 可以使用以下 SQL 函数来阻止此类异常。

  • try_cast:与 CAST 相同,只不过它返回 NULL 结果而不是在出现运行时错误时引发异常。
  • try_add:与加法运算符 + 相同,只不过它返回 NULL 结果而不是在整型值溢出时引发异常。
  • try_divide:与除法运算符 / 相同,只不过它返回 NULL 结果而不是在除以 0 时引发异常。

SQL 关键字

如果 spark.sql.ansi.enabled 为 true,Spark SQL 将使用 ANSI 模式分析器。 在此模式下,Spark SQL 有两种关键字:

  • 保留关键字:保留的关键字,不能用作表、视图、列、函数、别名等的标识符。
  • 非保留关键字:仅在特定上下文中具有特殊含义的关键字,在其他上下文中可用作标识符。 例如,EXPLAIN SELECT ... 是一个命令,但 EXPLAIN 在其他位置可用作标识符。

禁用 ANSI 模式时,Spark SQL 有两种关键字:

  • 非保留关键字:与 ANSI 模式处于启用状态下的定义相同。
  • 严格非保留关键字:非保留关键字的 strict 版本,无法用作表别名。

默认情况下,spark.sql.ansi.enabled 为 false。

下面是 Spark SQL 中所有关键字的列表。

关键字 Spark SQL ANSI 模式 Spark SQL 默认模式 SQL-2016
非保留 非保留 未预订
非保留 非保留 非保留
保留 非保留 保留
修改 非保留 非保留 保留
总是 非保留 非保留 非保留
分析 非保留 非保留 非保留
保留 非保留 保留
非保留 严格非保留 非保留
任何 保留 非保留 保留
存档 非保留 非保留 非保留
数组 非保留 非保留 保留
保留 非保留 保留
ASC 非保留 非保留 非保留
非保留 非保留 保留
授权 保留 非保留 保留
之间 非保留 非保留 保留
两者 保留 非保留 保留
非保留 非保留 非保留
非保留 非保留 非保留
非保留 非保留 保留
缓存 非保留 非保留 非保留
级 联 非保留 非保留 非保留
案例 保留 非保留 保留
投掷 保留 非保留 保留
更改 非保留 非保留 非保留
检查 保留 非保留 保留
清楚 非保留 非保留 非保留
集群 非保留 非保留 非保留
集群 非保留 未预订 非保留
CODEGEN 非保留 非保留 非保留
排序 保留 非保留 保留
收藏 非保留 非保留 非保留
COLUMN 保留 非保留 保留
COLUMNS 非保留 非保留 非保留
评论 非保留 非保留 非保留
提交 非保留 非保留 保留
紧凑的 非保留 非保留 非保留
COMPACTIONS 非保留 非保留 非保留
计算 非保留 非保留 非保留
串联 非保留 非保留 非保留
CONSTRAINT 保留 非保留 保留
成本 非保留 非保留 非保留
创造 保留 非保留 保留
十字架 保留 严格非保留 保留
立方体 非保留 非保留 保留
当前 非保留 非保留 保留
CURRENT_DATE 保留 非保留 保留
当前时间 保留 非保留 保留
当前时间戳 保留 非保留 保留
当前用户 保留 非保留 保留
数据 非保留 非保留 非保留
数据库 非保留 非保留 非保留
数据库 非保留 非保留 非保留
非保留 非保留 非保留
DBPROPERTIES 非保留 非保留 非保留
定义 非保留 非保留 非保留
删除 非保留 非保留 保留
定界 非保留 非保留 非保留
DESC 非保留 非保留 非保留
描述 非保留 非保留 保留
DFS 非保留 非保留 非保留
目录 非保留 非保留 非保留
目录 非保留 非保留 非保留
独特 保留 非保留 保留
分发 非保留 非保留 非保留
DIV 非保留 非保留 非关键字
下降 非保留 非保留 保留
否则 保留 非保留 保留
End 保留 非保留 保留
逃离 保留 非保留 保留
逃跑 非保留 非保留 非保留
除了 保留 严格非保留 保留
交换 非保留 非保留 非保留
存在 非保留 非保留 保留
EXPLAIN 非保留 非保留 非保留
出口 非保留 非保留 非保留
扩展 非保留 非保留 非保留
外部 非保留 非保留 保留
提取 非保留 非保留 保留
保留 非保留 保留
检索 保留 非保留 保留
字段 非保留 非保留 非保留
过滤器 保留 非保留 保留
文件格式 非保留 非保留 非保留
第一 非保留 非保留 非保留
FN 非保留 非保留 非保留
以后 非保留 非保留 非保留
保留 非保留 保留
外国 保留 非保留 保留
格式 非保留 非保留 非保留
格式 化 非保留 非保留 非保留
保留 非保留 保留
完整的 保留 严格非保留 保留
功能 非保留 非保留 保留
功能 非保留 非保留 非保留
生成 非保留 非保留 非保留
全球 非保留 非保留 保留
GRANT 保留 非保留 保留
拨款 非保留 非保留 非保留
保留 非保留 保留
分组 非保留 非保留 保留
HAVING 保留 非保留 保留
小时 非保留 非保留 非保留
如果 非保留 非保留 非关键字
忽视 非保留 非保留 非保留
进口 非保留 非保留 非保留
保留 非保留 保留
索引 非保留 非保留 非保留
索引 非保留 非保留 非保留
保留 严格非保留 保留
INPATH 非保留 非保留 非保留
输入格式 非保留 非保留 非保留
INSERT 非保留 非保留 保留
交汇 保留 严格非保留 保留
间隔 非保留 非保留 保留
进入 保留 非保留 保留
伊斯兰国 保留 非保留 保留
非保留 非保留 非保留
JOIN 保留 严格非保留 保留
JSON(JavaScript 对象表示法) 非保留 非保留 非保留
钥匙 非保留 非保留 非保留
密钥 非保留 非保留 非保留
最后 非保留 非保留 非保留
侧面的 保留 严格非保留 保留
懒惰 非保留 非保留 非保留
主导 保留 非保留 保留
保留 严格非保留 保留
喜欢 非保留 非保留 保留
我喜欢 非保留 非保留 非保留
LIMIT 非保留 非保留 非保留
线路 非保留 非保留 非保留
LIST 非保留 非保留 非保留
负载 非保留 非保留 非保留
当地 非保留 非保留 保留
位置 非保留 非保留 非保留
非保留 非保留 非保留
非保留 非保留 非保留
合乎逻辑的 非保留 非保留 非保留
MACRO 非保留 非保留 非保留
地图 非保留 非保留 非保留
匹配 非保留 非保留 非保留
合并 非保留 非保留 非保留
分钟 非保留 非保留 非保留
减去 非保留 严格非保留 非保留
非保留 非保留 非保留
MSCK 非保留 非保留 非保留
命名空间 非保留 非保留 非保留
命名空间 非保留 非保留 非保留
自然 保留 严格非保留 保留
非保留 非保留 保留
不是 保留 非保留 保留
Null 保留 非保留 保留
空值 非保留 非保留 非保留
非保留 非保留 保留
保留 严格非保留 保留
保留 非保留 保留
选择 非保留 非保留 非保留
选项 非保留 非保留 非保留
保留 非保留 保留
次序 保留 非保留 保留
非保留 非保留 保留
外部的 保留 非保留 保留
输出格式 非保留 非保留 非保留
结束 非保留 非保留 非保留
重叠 保留 非保留 保留
叠加 非保留 非保留 非保留
改写 非保留 非保留 非保留
PARTITION 非保留 非保留 保留
分区 非保留 非保留 非保留
分区 非保留 非保留 非保留
百分比 非保留 非保留 非保留
PIVOT 非保留 非保留 非保留
放置 非保留 非保留 非保留
位置 非保留 非保留 保留
之前 非保留 非保留 非保留
主要的 保留 非保留 保留
负责人 非保留 非保留 非保留
性能 非保留 非保留 非保留
清除 非保留 非保留 非保留
QUALIFY 保留 非保留 保留
查询 非保留 非保留 非保留
范围 非保留 非保留 保留
收件人 非保留 非保留 非保留
RECIPIENTS 非保留 非保留 非保留
记录阅读器 非保留 非保留 非保留
RECORDWRITER 非保留 非保留 非保留
恢复 非保留 非保留 非保留
减少 非保留 非保留 非保留
参考文献 保留 非保留 保留
REFRESH 非保留 非保留 非保留
REGEXP 非保留 非保留 非关键字
REMOVE 非保留 非保留 非保留
重命名 非保留 非保留 非保留
修理 非保留 非保留 非保留
取代 非保留 非保留 非保留
RESET 非保留 非保留 非保留
尊重 非保留 非保留 非保留
限制 非保留 非保留 非保留
REVOKE 非保留 非保留 保留
保留 严格非保留 保留
RLIKE 非保留 非保留 非保留
角色 非保留 非保留 非保留
角色 非保留 非保留 非保留
反转 非保留 非保留 保留
汇总 非保留 非保留 保留
非保留 非保留 保留
非保留 非保留 保留
SCHEMA 非保留 非保留 非保留
架构 非保留 非保留 非关键字
第二 非保留 非保留 非保留
SELECT 保留 非保留 保留
半导体 非保留 严格非保留 非保留
分开 非保留 非保留 非保留
SERDE 非保留 非保留 非保留
SERDEPROPERTIES 非保留 非保留 非保留
会话用户 保留 非保留 保留
SET 非保留 非保留 保留
系列 非保留 非保留 非保留
共享 非保留 非保留 非保留
SHARES 非保留 非保留 非保留
显示 非保留 非保留 非保留
扭曲/偏斜 非保留 非保留 非保留
一些 保留 非保留 保留
排序 非保留 非保留 非保留
已排序 非保留 非保留 非保留
开始 非保留 非保留 保留
统计学 非保留 非保留 非保留
存储 非保留 非保留 非保留
分层 非保留 非保留 非保留
结构 非保留 非保留 非保留
SUBSTR 非保留 非保留 非保留
子串 非保留 非保留 非保留
SYNC 非保留 非保留 非保留
TABLE 保留 非保留 保留
TABLES 非保留 非保留 非保留
TABLESAMPLE 非保留 非保留 保留
TBLPROPERTIES 非保留 非保留 非保留
临时 非保留 非保留 非关键字
临时 非保留 非保留 非保留
已终止 非保留 非保留 非保留
然后 保留 非保留 保留
时间 保留 非保留 保留
保留 非保留 保留
触摸 非保留 非保留 非保留
尾随 保留 非保留 保留
交易 非保留 非保留 非保留
交易 非保留 非保留 非保留
变换 非保留 非保留 非保留
修剪 非保留 非保留 非保留
正确 非保留 非保留 保留
截断 非保留 非保留 保留
TRY_CAST 非保留 非保留 非保留
类型 非保留 非保留 非保留
取消归档 非保留 非保留 非保留
无拘无束 非保留 非保留 非保留
解除缓存 非保留 非保留 非保留
联盟 保留 严格非保留 保留
独特 保留 非保留 保留
未知 保留 非保留 保留
解锁 非保留 非保留 非保留
未设置 非保留 非保留 非保留
UPDATE 非保留 非保留 保留
使用 非保留 非保留 非保留
用户 保留 非保留 保留
使用 保留 严格非保留 保留
VALUES 非保留 非保留 保留
视图 非保留 非保留 非保留
VIEWS 非保留 非保留 非保留
何时 保留 非保留 保留
WHERE 保留 非保留 保留
WINDOW 非保留 非保留 保留
保留 非保留 保留
年份 非保留 非保留 非保留
非保留 非保留 非保留