PostgreSql CAST

一、概述

​   CAST 是 PostgreSQL 中的 "类型翻译官" ,它负责将一种数据类型的值转换为另一种数据类型。如果没有合适的 CAST,PostgreSQL 就无法自动完成类型转换,查询就会失败。PostgreSQL的内置转换是优化器信任体系的一部分,其安全、高效经过了大量实践验证。而人工添加的隐式转换,是数据库引入的一个"变量",这个变量充满了不确定性,可能在任何时候破坏优化器的精确判断,导致难以诊断的性能问题。PostgreSQL 默认内置的转换已经覆盖得非常完善,可支持常用的绝大多数场景了。除非你对PostgreSQL的内核机制有深入理解,并能100%确保你的自定义转换不会干扰优化器的任何判断,否则在生产环境中,应尽量避免手动创建转换

sql 复制代码
-- CAST 就像一个翻译官,把小数3.14"翻译"成整数
highgo=# SELECT 3.1415;
 ?column?
----------
   3.1415
(1 row)

highgo=# SELECT CAST(3.1415 as int);
 int4
------
    3
(1 row)

highgo=# SELECT 3.1415::int;
 int4
------
    3
(1 row)

二、类型转换等级

PostgreSQL 根据转换的上下文 将 CAST 分为三个等级,对应系统表 pg_cast 中的 castcontext 字段:

等级 代码 含义 触发场景
Explicit(显式转换) e 必须显式调用 CAST(...)...::
Assignment(赋值转换) a 赋值时自动转换 INSERTUPDATE 赋值
Implicit(隐式转换) i 任何上下文中自动转换 表达式运算、比较等

这三种等级的控制非常重要。Implicit(隐式转换) 最为方便,但风险也最大;Assignment(赋值转换) 是折衷方案;而 Explicit(显式转换) 则是最安全的方式。

2.1 Explicit(显式转换)

显式转换是默认的转换方式 ,也是最安全 的方式。必须使用 CAST:: 语法才能触发转换:

sql 复制代码
-- 使用 CAST 函数
highgo=# SELECT CAST('123' AS INTEGER);
 int4
------
  123
(1 row)


-- 使用 :: 简写
highgo=# SELECT '2026-04-22'::DATE;
    date
------------
 2026-04-22
(1 row)

优点:意图明确,不会产生歧义或意外的类型转换,建议在非赋值场景下优先使用。

2.2 Assignment(赋值转换)

赋值转换允许在将值赋给目标列时 自动进行类型转换。例如,向 text 类型的列插入 integer 值:

sql 复制代码
CREATE TABLE test (f1 text);

-- 如果存在 integer → text 的赋值转换,以下语句会自动生效
INSERT INTO test (f1) VALUES (42);   -- integer 值 42 被自动转换为 '42'

这种机制的好处是方便,同时不会影响表达式求值,降低了误转换的风险。这是推荐的默认隐式转换方式 ,建议优先使用 AS ASSIGNMENT 而非 AS IMPLICIT

2.3 Implicit(隐式转换)

隐式转换可以在任何上下文中自动调用 ,无论是赋值还是表达式内部。SELECT 2 + 4.0 之所以能成功,就是因为存在 integer → numeric 的隐式转换:

sql 复制代码
SELECT 2 + 4.0;
-- 解析器隐式地将其转换为: SELECT CAST(2 AS numeric) + 4.0;

虽然方便,但隐式转换需要谨慎使用。过多的隐式转换可能导致查询歧义,PostgreSQL 可能无法解析命令。最佳实践是:只在同一类型类别中、且能完整保留信息的情况下使用隐式转换

注意:官方文档明确警告,过多隐式转换可能导致"查询歧义"的错误。

三、语法

PostgreSQL 提供了三种方式来创建 CAST,分别应对不同的转换场景。

3.1 基于函数的转换

这是最常见的方式:指定一个自定义函数来执行转换。

sql 复制代码
CREATE CAST (source_type AS target_type)
    WITH FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

函数必须接受一个源类型的参数 ,并返回目标类型

3.2 I/O 转换(便捷方式)

利用数据类型的输入/输出函数自动完成转换:

sql 复制代码
CREATE CAST (source_type AS target_type)
    WITH INOUT
    [ AS ASSIGNMENT | AS IMPLICIT ]

这相当于调用源类型的输出函数 生成字符串,再交给目标类型的输入函数。常用于字符串类型与其他类型之间的转换,无需编写额外的转换函数。

3.3 二进制可转换

当两种类型使用完全相同的内部表示 时,可以使用 WITHOUT FUNCTION 标记它们为二进制兼容。

sql 复制代码
CREATE CAST (source_type AS target_type)
    WITHOUT FUNCTION
    [ AS ASSIGNMENT | AS IMPLICIT ]

例如,textvarchar 是双向二进制兼容的。需要注意的是,二进制可转换不一定是对称关系 ------xmltext 可以免费转换,但反过来就需要语法检查。

扩展知识 :当 castmethod 字段的值为 b 时,表示该转换是二进制可转换的。pg_cast 中的 castfunc 字段会存储转换函数的 OID;如果转换不需要函数,则存储 0。

四、常见场景与操作示例

​   以下示例仅用于展示 CREATE CAST 的语法写法。PostgreSQL 对大多数常用类型之间的转换已经内置了默认行为,因此即使不手动执行这些 CREATE CAST 语句,后续的 SQL 示例通常也能正常运行。实际开发中,只有在处理自定义类型或特殊格式数据时,才需要手动创建 CAST。

4.1 显式转换:日常查询的最佳实践

场景:字符串存储的数字需要参与运算。

sql 复制代码
-- 创建测试表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price VARCHAR(50)  -- 不推荐,仅为演示
);

-- 查询时需要转换
SELECT CAST(price AS NUMERIC) * 0.8 AS discount_price FROM products;

-- 或使用 :: 语法
SELECT price::NUMERIC * 0.8 FROM products;

4.2 自定义转换:处理特殊数据格式

场景 :将 text 类型转换为 date,但需要指定特定的日期格式。

sql 复制代码
-- 创建转换函数
CREATE OR REPLACE FUNCTION text_to_date(text) RETURNS date AS $$
    SELECT to_date($1, 'YYYY-MM-DD');
$$ LANGUAGE sql STRICT;

-- 创建赋值转换
CREATE CAST (text AS date)
    WITH FUNCTION text_to_date(text)
    AS ASSIGNMENT;

创建后,可以直接进行隐式赋值转换:

sql 复制代码
CREATE TABLE events (event_date date);
INSERT INTO events (event_date) VALUES ('2024-03-15');  -- 隐式生效

4.3 I/O 转换:利用内置函数快速实现

当源类型和目标类型可以通过字符串作为中介进行转换时,WITH INOUT 是最便捷的方案:

sql 复制代码
-- 利用 I/O 函数将 varchar 转换为 numeric
CREATE CAST (varchar AS numeric) WITH INOUT AS ASSIGNMENT;

-- 现在可以直接插入字符串形式的数字
CREATE TABLE accounts (balance numeric);
INSERT INTO accounts (balance) VALUES ('123.45');  -- 自动转换

4.4 删除不需要的 CAST

如果某个 CAST 不再需要,可以使用 DROP CAST 将其删除:

sql 复制代码
DROP CAST IF EXISTS (text AS date);
DROP CAST IF EXISTS (varchar AS numeric);

4.5 查询 CAST 定义

sql 复制代码
WITH cast_info AS (
    SELECT
        c.oid AS cast_oid,
        c.castsource::regtype::text AS source_type,
        c.casttarget::regtype::text AS target_type,
        c.castfunc,
        c.castmethod,
        c.castcontext,
        CASE c.castmethod
            WHEN 'f' THEN 'FUNCTION'
            WHEN 'i' THEN 'INOUT'
            WHEN 'b' THEN 'WITHOUT FUNCTION'
        END AS method_type,
        CASE c.castcontext
            WHEN 'a' THEN 'AS ASSIGNMENT'
            WHEN 'i' THEN 'AS IMPLICIT'
            ELSE ''
        END AS context_text,
        -- 预先获取函数信息
        p.proname AS func_name,
        p.pronamespace,
        n.nspname AS func_schema,
        pg_get_functiondef(p.oid) AS func_def
    FROM pg_cast c
    LEFT JOIN pg_proc p ON c.castfunc = p.oid
    LEFT JOIN pg_namespace n ON p.pronamespace = n.oid
)
SELECT
    source_type AS 源类型,
    target_type AS 目标类型,
    method_type AS 转换方法,
    context_text AS 转换上下文,
    -- 显示函数名(如果存在)
    CASE WHEN castfunc != 0 THEN func_name::text ELSE '-' END AS 转换函数名,
    func_def AS 转换函数定义,
    func_schema AS 函数所在模式,
    CASE 
        WHEN castfunc != 0 AND func_schema = 'pg_catalog' THEN '是'
        WHEN castfunc != 0 AND func_schema != 'pg_catalog' THEN '否'
        ELSE '无关联函数'
    END AS 转换函数是否系统自带,
    -- 生成创建语句
    CASE castmethod
        WHEN 'f' THEN 
            format('CREATE CAST (%s AS %s) WITH FUNCTION %s(%s) %s;',
                source_type, target_type,
                func_name, source_type,
                context_text
            )
        WHEN 'i' THEN 
            format('CREATE CAST (%s AS %s) WITH INOUT %s;',
                source_type, target_type,
                context_text
            )
        WHEN 'b' THEN 
            format('CREATE CAST (%s AS %s) WITHOUT FUNCTION %s;',
                source_type, target_type,
                context_text
            )
    END AS 创建语句
FROM cast_info
-- 可选:只显示初始化数据库后用户自己创建的(OID >= 16384)
-- WHERE cast_oid >= 16384
ORDER BY source_type, target_type;

五、最佳实践与注意事项

5.1 推荐做法

  1. 优先使用显式转换 :在查询中直接使用 CAST(...)::,意图最清晰。
  2. 谨慎使用隐式转换 :只在同一类型类别中、且能完整保留信息时才使用 AS IMPLICIT
  3. 自定义转换优先使用 AS ASSIGNMENT :仅在赋值时生效,比 AS IMPLICIT 更安全。
  4. 避免双向隐式转换 :如果同时定义 A→BB→AIMPLICIT,会导致转换路径冲突和解析歧义。

5.2 常见陷阱

  1. 滥用 AS IMPLICIT:可能导致查询歧义,PostgreSQL 无法决定使用哪个转换路径。
  2. 转换函数签名错误:函数必须接受一个源类型参数并返回目标类型。
  3. 创建循环转换路径 :例如 type_a → type_btype_b → type_a 都是 IMPLICIT,可能引发无限递归错误。

六、总结

PostgreSQL 的 CAST 机制是类型系统的核心组成部分。理解 eai 三个等级的区别,掌握 WITH FUNCTIONWITH INOUTWITHOUT FUNCTION 三种创建方式,就能在开发中灵活应对各种类型转换需求。

等级 关键字 使用建议
Explicit 默认 日常查询首选,意图最清晰
Assignment AS ASSIGNMENT 赋值场景推荐,平衡便利与安全
Implicit AS IMPLICIT 谨慎使用,仅在同一类型类别中且能完整保留信息时考虑

记住一个黄金原则:默认使用显式转换,必要时使用 AS ASSIGNMENT,万不得已才用 AS IMPLICIT。这样可以最大程度地保证查询的可读性和可预测性。

相关推荐
思麟呀2 小时前
Epoll的学习,在select和poll的基础上
网络·数据库·sql·学习·tcp/ip
zhangchaoxies2 小时前
c++怎么在Linux下获取文件被最后一次访问的精确纳秒时间【进阶】
jvm·数据库·python
m0_747854522 小时前
c++怎么在Linux下获取文件被最后一次访问的精确纳秒时间【进阶】
jvm·数据库·python
2301_816660212 小时前
如何用HTML函数工具检测当前设备性能_内置诊断操作【操作】
jvm·数据库·python
zhangchaoxies2 小时前
CSS如何实现移动端视口适配_利用rem与vw单位构建响应式布局
jvm·数据库·python
m0_747854522 小时前
如何创建CDB公共用户_C##前缀强制规则与CONTAINER=ALL
jvm·数据库·python
Absurd5872 小时前
SQL如何高效提取每组首条记录 ROW_NUMBER优化策略
jvm·数据库·python
2501_914245932 小时前
CSS如何更改鼠标悬停时的指针样式_设置cursor属性为pointer或not-allowed
jvm·数据库·python
四维迁跃2 小时前
Go语言如何做SSE推送_Go语言Server-Sent Events教程【技巧】
jvm·数据库·python