real
在 PostgreSQL 中对一个操作数和一个操作数执行数学运算numeric
会得到一个double precision
值,但我觉得这很令人惊讶。为什么结果不是real
, 呢?
我使用 PostgreSQL 13 和 16 确认了这种行为。
SELECT
pg_typeof(0::numeric + 0::numeric), -- numeric
pg_typeof(0::real + 0::real), -- real
pg_typeof(0::numeric + 0::real), -- double precision??
pg_typeof(0::real + 0::numeric); -- double precision??
我已阅读了 PostgreSQL 文档的大部分内容,其中最相关的是:10.2。类型转换:运算符
我已经查询过pg_catalog.pg_cast
(或psql
)\dC
来了解相关的隐式转换是什么:
> SET search_path = 'pg_catalog';
> SELECT format_type(castsource, NULL) AS source,
format_type(casttarget, NULL) AS target
FROM pg_cast
WHERE castcontext = 'i' -- i.e. casts allowed implicitly
AND ARRAY[format_type(castsource, NULL), format_type(casttarget, NULL)]
<@ '{numeric,real,double precision}'::text[];
结果是:
source | target
---------+------------------
real | double precision
numeric | real
numeric | double precision
numeric | numeric
(4 rows)
据我所知,对于0::numeric + 0::real
,PostgreSQL 应该(摘录全部来自10.2. 类型转换:运算符):
- 从系统目录中选择要考虑的运算符
pg_operator
。如果使用了非模式限定的运算符名称(通常情况),则考虑的运算符是那些在当前搜索路径中可见的具有匹配名称和参数计数的运算符。
我希望它以所有运算符开始+
- 每个运算符接受一对相同的数字类型(real + real
,,integer + integer
等等)
- 检查运算符是否准确接受输入参数类型。
real
在步骤 2 中,没有运算符与和完全匹配numeric
,因此我预计这里不会发生任何事情。
3a. 丢弃输入类型不匹配且无法转换(使用隐式转换)以匹配的候选运算符。
我预计这应该会丢弃大多数候选者。它应该只保留两个:一个是real
,另一个是double precision
它显然最终使用的 。(请注意,real
不能隐式转换为numeric
,因此它不应该保留numeric
。)
3b. 如果任何输入参数属于域类型,则在所有后续步骤中将其视为域的基类型。
我认为这不会产生任何作用,因为这里没有域类型。
3c. 遍历所有候选,并保留那些与输入类型最精确匹配的候选。如果没有精确匹配的候选,则保留所有候选。如果只剩下一个候选,则使用它;否则继续下一步。
在这里,我期望它应该丢弃 的运算符double precision
,因为 的运算符real
有一个精确匹配,而 的运算符double precision
没有精确匹配。然后,由于这里只剩下一个候选,它应该使用 的运算符real
,我期望结果是real
。但结果却是一个double precision
值。
因此,我的问题是,为什么 PostgreSQLdouble precision
在这种情况下选择运算符,它需要隐式转换两个操作数而不是其中一个操作数?文档有误,还是我的理解有误?
缺少的两部分:
混合类型数学运算结果类型的文档:
所讨论的列表是“ ,,,,,
smallint
和” 。这是一般规则,但integer
它bigint
numeric
real
double precision
是内置运算符的结果,下面将讨论。类型偏好。您可以在 中查看
pg_type.ispreferred
。在那里,在数字类型组double precision
中,显示为real
和的首选类型:numeric
(双精度)
(实数)
(小整数)
(整数)
(大整数)
您在10.2. 类型转换:运算符中停了下来,就在到达它使用的步骤之前:
它确实转到 3d,因为在 3c 中它发现没有
real+numeric
操作符(没有完全匹配,有三个半匹配):并且在三个最接近的候选者中,
real+real
(向右投)real+double precision
(向右投)和numeric+numeric
(向左投),中间的那个获胜,因为它的目的地类型是首选。第一个原因只是为了让这个答案更通用,并适用于其他组合。第二个原因正是您看到
real+numeric
结果为real+numeric::double precision
的原因,结果为double precision
,而不是real+numeric::real
,结果为real
。它只转换一个,即
numeric
。您的测试使用了常量折叠的常量,只留下预先评估的结果并隐藏了底层操作。如果您使用explain verbose
具有不同值的表,您可以更清楚地看到这一点:db<>fiddle 上的演示
, pg_typeof((r + r))
, pg_typeof(((n)::double precision + r))
, pg_typeof((r + (n)::double precision))