目录
[第一章 字符串函数](#第一章 字符串函数)
[1.1 CONCAT 与 CONCAT_WS](#1.1 CONCAT 与 CONCAT_WS)
[1.2 SUBSTRING](#1.2 SUBSTRING)
[1.3 UPPER / LOWER / INITCAP](#1.3 UPPER / LOWER / INITCAP)
[1.4 LENGTH](#1.4 LENGTH)
[1.5 TRIM(及 LTRIM / RTRIM)](#1.5 TRIM(及 LTRIM / RTRIM))
[1.6 REPLACE](#1.6 REPLACE)
[1.7 POSITION / STRPOS](#1.7 POSITION / STRPOS)
[1.8 LEFT / RIGHT](#1.8 LEFT / RIGHT)
[1.9 正则表达式函数](#1.9 正则表达式函数)
[1.9.1 REGEXP_MATCHES](#1.9.1 REGEXP_MATCHES)
[1.9.2 REGEXP_REPLACE](#1.9.2 REGEXP_REPLACE)
[第二章 数值函数](#第二章 数值函数)
[2.1 ROUND](#2.1 ROUND)
[2.2 TRUNC](#2.2 TRUNC)
[2.3 CEIL / FLOOR](#2.3 CEIL / FLOOR)
[2.4 MOD](#2.4 MOD)
[2.5 ABS](#2.5 ABS)
[2.6 POWER / SQRT](#2.6 POWER / SQRT)
[2.7 RANDOM](#2.7 RANDOM)
[第三章 日期/时间函数](#第三章 日期/时间函数)
[3.1 CURRENT_DATE / CURRENT_TIMESTAMP / NOW](#3.1 CURRENT_DATE / CURRENT_TIMESTAMP / NOW)
[3.2 EXTRACT / DATE_PART](#3.2 EXTRACT / DATE_PART)
[3.3 AGE](#3.3 AGE)
[3.4 DATE_TRUNC](#3.4 DATE_TRUNC)
[3.5 TO_CHAR(日期格式化)](#3.5 TO_CHAR(日期格式化))
[第四章 条件判断函数](#第四章 条件判断函数)
[4.1 CASE WHEN](#4.1 CASE WHEN)
[4.2 COALESCE](#4.2 COALESCE)
[4.3 NULLIF](#4.3 NULLIF)
[4.4 GREATEST / LEAST](#4.4 GREATEST / LEAST)
[第五章 聚合函数](#第五章 聚合函数)
[5.1 COUNT](#5.1 COUNT)
[5.2 SUM / AVG](#5.2 SUM / AVG)
[5.3 MAX / MIN](#5.3 MAX / MIN)
[5.4 STRING_AGG](#5.4 STRING_AGG)
[5.5 ARRAY_AGG](#5.5 ARRAY_AGG)
[第六章 类型转换函数](#第六章 类型转换函数)
[6.1 CAST 与 :: 操作符](#6.1 CAST 与 :: 操作符)
[6.2 TO_CHAR(数值转换)](#6.2 TO_CHAR(数值转换))
[6.3 TO_NUMBER](#6.3 TO_NUMBER)
[6.4 TO_DATE / TO_TIMESTAMP](#6.4 TO_DATE / TO_TIMESTAMP)
场景设定:本讲义以一家保险公司的数据平台为背景。我们将创建客户、代理人、保单和理赔四张核心表,并基于这些表中的真实业务数据,逐一演示 PostgreSQL 内置函数的使用方法。你可以将本文当做一本"案头速查手册",在编写 SQL 时随时翻阅。
第零章:环境准备:保险业务数据模型
在开始学习函数之前,请在你的 PostgreSQL 数据库中依次执行以下建表和插入数据的脚本。所有后续示例都会基于这些数据。
sql
-- 创建Schema
CREATE schema if not exists policy;
-- =====================================================
-- 0.1 代理人表 (agents)
-- =====================================================
CREATE TABLE policy.agents (
agent_id INT PRIMARY KEY,
agent_name VARCHAR(50),
phone VARCHAR(20),
hire_date DATE,
commission_rate NUMERIC(3,2) -- 佣金比例,如0.15表示15%
);
INSERT INTO policy.agents VALUES
(1, '张伟', '13800001111', '2018-03-15', 0.12),
(2, '李娜', '13900002222', '2020-07-01', 0.15),
(3, '王强', '13600003333', '2016-01-20', 0.10);
-- =====================================================
-- 0.2 客户表 (customers)
-- =====================================================
CREATE TABLE policy.customers (
customer_id INT PRIMARY KEY,
full_name VARCHAR(80),
gender CHAR(1) CHECK (gender IN ('M','F')),
birth_date DATE,
city VARCHAR(30),
agent_id INT REFERENCES policy.agents(agent_id) -- 关联的代理人
);
INSERT INTO policy.customers VALUES
(101, '赵晓明', 'M', '1990-05-20', '北京', 1),
(102, '钱丽华', 'F', '1985-12-10', '上海', 1),
(103, '孙志伟', 'M', '1978-08-15', '广州', 2),
(104, '李芳', 'F', '1993-03-22', '深圳', 2),
(105, '周国强', 'M', '2000-11-01', '北京', 3),
(106, '吴敏', 'F', '1968-07-07', '上海', NULL); -- 线上客户,无专属代理人
-- =====================================================
-- 0.3 保单表 (policies)
-- =====================================================
CREATE TABLE policy.policies (
policy_id INT PRIMARY KEY,
customer_id INT REFERENCES policy.customers(customer_id),
product_name VARCHAR(60),
premium NUMERIC(10,2), -- 年缴保费
insured_amount NUMERIC(12,2), -- 保额
start_date DATE,
end_date DATE,
status VARCHAR(20) DEFAULT '有效' -- 有效/失效/理赔中
);
INSERT INTO policy.policies VALUES
(1001, 101, '乐享一生年金险', 12000.00, 500000.00, '2022-01-01', '2042-01-01', '有效'),
(1002, 101, '百万医疗险', 450.00, 2000000.00,'2023-05-10', '2024-05-09', '有效'),
(1003, 102, '重大疾病险', 8000.00, 300000.00, '2021-09-01', NULL, '有效'),
(1004, 103, '定期寿险', 1500.00, 1000000.00,'2020-06-15', '2040-06-14', '有效'),
(1005, 104, '百万医疗险', 380.00, 2000000.00,'2024-01-01', '2025-01-01', '有效'),
(1006, 104, '意外伤害险', 200.00, 500000.00, '2023-11-01', '2024-10-31', '有效'),
(1007, 105, '乐享一生年金险', 10000.00, 400000.00, '2022-07-01', '2042-07-01', '有效'),
(1008, 106, '重大疾病险', 6500.00, 200000.00, '2019-03-15', NULL, '失效'); -- 已停效
-- =====================================================
-- 0.4 理赔表 (claims)
-- =====================================================
CREATE TABLE policy.claims (
claim_id INT PRIMARY KEY,
policy_id INT REFERENCES policy.policies(policy_id),
claim_date DATE,
claim_amount NUMERIC(10,2),
claim_status VARCHAR(20) DEFAULT '处理中' -- 处理中/已结案/拒赔
);
INSERT INTO policy.claims VALUES
(2001, 1002, '2023-12-01', 15000.00, '已结案'),
(2002, 1002, '2024-02-15', 8000.00, '已结案'),
(2003, 1003, '2022-06-10', 50000.00, '已结案'),
(2004, 1005, '2024-07-20', 12000.00, '处理中'),
(2005, 1006, '2024-03-01', 2000.00, '已结案'),
(2006, 1008, '2020-05-18', 35000.00, '拒赔');
输出如下内容:

现在,数据已就绪。让我们正式进入 PostgreSQL 内置函数的世界。
第一章 字符串函数
在保险公司,经常需要处理客户姓名、产品名称、电话号码等文本数据。下面的字符串函数将帮你轻松完成拼接、截取、清洗等工作。
1.1 CONCAT 与 CONCAT_WS
(1) CONCAT
概念 :将多个字符串或字段值拼接成一个字符串。如果参数中有 NULL,CONCAT 会将其视为空字符串,继续拼接其余部分,不会导致整个结果为 NULL。
语法 :CONCAT(str1, str2, ..., strN)
业务案例:生成客户的完整称呼(姓名 + 性别),方便在报表中展示。
sql
select customer_id
,full_name
,gender
,CONCAT(full_name, ' (', gender, ')') AS display_name
FROM policy.customers;
结果:
sql
101 赵晓明 M 赵晓明 (M)
102 钱丽华 F 钱丽华 (F)
103 孙志伟 M 孙志伟 (M)
104 李芳 F 李芳 (F)
105 周国强 M 周国强 (M)
106 吴敏 F 吴敏 (F)
(2)CONCAT_WS
概念 :CONCAT_WS 是 "Concatenate With Separator" 的缩写。它用第一个参数作为分隔符,将后续的字符串连接起来。与 CONCAT 相同,它会自动跳过 NULL 值;但如果分隔符本身是 NULL,则整个函数返回 NULL。
语法 :CONCAT_WS(separator, str1, str2, ...)
业务案例:生成客户的通讯地址,用逗号分隔城市和姓名,即使某部分缺失也不会多出无用的逗号。
sql
SELECT
customer_id,
CONCAT_WS(' - ', full_name, city) AS contact
FROM customers;
结果:
sql
101 赵晓明 北京 赵晓明 - 北京
102 钱丽华 上海 钱丽华 - 上海
103 孙志伟 广州 孙志伟 - 广州
104 李芳 深圳 李芳 - 深圳
105 周国强 北京 周国强 - 北京
106 吴敏 上海 吴敏 - 上海
1.2 SUBSTRING
概念:从源字符串中截取从指定位置开始、指定长度的子串。位置索引从1开始。如果省略长度参数,则截取从起始位置到字符串末尾的所有字符。
语法:
-
SUBSTRING(string FROM start [FOR length]) -
SUBSTRING(string, start, length)
业务案例 :从保单号中提取业务标识(假设保单号的第2位到第4位代表产品系列)。例如保单号 1001,我们可以截取 "001" 部分。
sql
select policy_id
,SUBSTRING(policy_id::text FROM 2 FOR 3) AS product_series
,SUBSTRING(policy_id::text,2,3) AS product_series
FROM policy.policies;
输出如下内容:
sql
1001 001 001
1002 002 002
1003 003 003
1004 004 004
1005 005 005
1006 006 006
1007 007 007
1008 008 008
1.3 UPPER / LOWER / INITCAP
概念:
-
UPPER:将字符串全部转换为大写。 -
LOWER:将字符串全部转换为小写。 -
INITCAP:将每个单词的首字母转换为大写,其余字母转换为小写。单词分隔符包括空格、连字符、下划线等。
语法 :UPPER(string), LOWER(string), INITCAP(string
业务案例:将客户姓名统一为首字母大写的规范格式,用于标准信函。
sql
SELECT
full_name,
INITCAP(full_name) AS standardized_name
FROM policy.customers;
如果数据库中已存有英文名,INITCAP('john doe') 会返回 'John Doe'。
sql
-- 将产品名称转换为全大写,用于报表标题
SELECT
product_name,
UPPER(product_name) AS product_label
FROM policy.policies;
1.4 LENGTH
概念 :返回字符串中的字符个数(而不是字节数)。如果字符串为 NULL 则返回 NULL,空字符串返回 0。
语法 :LENGTH(string)
业务案例:检查客户手机号的长度是否正确(应为11位)。
sql
SELECT
full_name,
phone,
LENGTH(phone) AS phone_length
FROM policy.agents;
结果:
sql
full_name | phone | phone_length
----------+-------------+-------------
张伟 | 13800001111 | 11
李娜 | 13900002222 | 11
王强 | 13600003333 | 11
1.5 TRIM(及 LTRIM / RTRIM)
概念:移除字符串首尾(或指定侧)的指定字符,默认移除空格。这是清洗输入数据时最常用的函数。
语法:
-
TRIM([LEADING | TRAILING | BOTH] [char] FROM string) -
LTRIM(string [, char]):仅去除左侧字符 -
RTRIM(string [, char]):仅去除右侧字符
业务案例:某次数据导入后,客户姓名前后意外包含了空格和制表符,需要清洗。
sql
SELECT TRIM(BOTH ' ' FROM ' 赵晓明 ') AS cleaned_name; -- 结果为 '赵晓明'
若要清除字段前后所有空白符(包括空格和制表符),可以直接使用 TRIM(string)。
sql
-- 假设导入的保单状态字段含有前导空格
UPDATE policies SET status = TRIM(status);
1.6 REPLACE
概念:将字符串中出现的所有指定子串替换成另一个子串。如果未找到匹配的子串,则返回原字符串。
语法 :REPLACE(string, old_substring, new_substring)
业务案例:将产品名称中的"险"字统一替换为"(保险)",以适应某些报表系统的命名要求。
sql
SELECT
product_name,
REPLACE(product_name, '险', '(保险)') AS formatted_name
FROM policy.policies;
1.7 POSITION / STRPOS
概念 :返回子串在字符串中第一次出现的位置(从1开始计数)。如果未找到则返回 0。
语法:
-
POSITION(substring IN string) -
STRPOS(string, substring)
业务案例 :快速定位保单号中连字符 - 的位置,以便后续拆分。
sql
SELECT
policy_id::TEXT,
POSITION('1' IN policy_id::TEXT) AS pos
FROM policy.policies;
1.8 LEFT / RIGHT
概念:分别从字符串左端或右端截取指定数量的字符。
语法 :LEFT(string, n), RIGHT(string, n)
业务案例:提取代理人手机号的前三位(运营商号段)和后四位。
sql
SELECT
agent_name,
LEFT(phone, 3) AS prefix,
RIGHT(phone, 4) AS suffix
FROM policy.agents;
1.9 正则表达式函数
1.9.1 REGEXP_MATCHES
概念:返回字符串中第一个匹配正则表达式的子串。结果以集合(set)的形式返回(通常为单行单列),可用于校验数据格式。
语法 :REGEXP_MATCHES(string, pattern [, flags])
业务案例 :校验客户手机号是否为11位数字。注意在正则表达式中 \d 需要写成 \\d。
sql
SELECT
phone,
(REGEXP_MATCHES(phone, '^\\d{11}$'))[1] IS NOT NULL AS is_valid
FROM policy.agents;
1.9.2 REGEXP_REPLACE
概念:用正则表达式匹配并替换字符串中的内容。
语法 :REGEXP_REPLACE(string, pattern, replacement [, flags])
业务案例 :将保单号中的数字替换为星号,实现脱敏展示(如 1001 → ****)。
sql
SELECT
policy_id::TEXT,
REGEXP_REPLACE(policy_id::TEXT, '[0-9]', '*', 'g') AS masked_id
FROM policy.policies;
第二章 数值函数
保费计算、佣金比例、保额统计......在保险行业,数值运算无处不在。PostgreSQL 提供了精确的数值函数,避免因浮点数误差导致财务偏差。
2.1 ROUND
概念:对数值进行四舍五入,保留指定小数位数。当第二个参数为负数时,会在整数位上进行四舍五入。
语法 :ROUND(numeric_value, decimal_places)
业务案例:将代理人的佣金比例四舍五入保留一位小数,或将保额向上舍入到千元。
sql
SELECT
agent_name,
commission_rate,
ROUND(commission_rate, 1) AS rounded_rate
FROM policy.agents;
-- 保额四舍五入到千元
SELECT
policy_id,
insured_amount,
ROUND(insured_amount, -3) AS rounded_amount
FROM policy.policies;
2.2 TRUNC
概念:截断数值到指定位数,直接舍弃多余的小数位,不进行四舍五入。
语法 :TRUNC(numeric_value, decimal_places)
业务案例:计算代理人年终奖时,按整百元向下取整发放。
sql
SELECT
agent_name,
commission_rate * 100000 AS bonus_base,
TRUNC(commission_rate * 100000, -2) AS bonus_rounded_down
FROM policy.agents;
2.3 CEIL / FLOOR
概念 :CEIL 返回不小于参数的最小整数(向上取整),FLOOR 返回不大于参数的最大整数(向下取整)。
语法 :CEIL(numeric_value), FLOOR(numeric_value)
业务案例:计算一个客户的所有保单总保费后,向上取整作为年度预算参考。
sql
SELECT
customer_id,
SUM(premium) AS total_premium,
CEIL(SUM(premium)) AS budget_ceiling,
FLOOR(SUM(premium)) AS budget_floor
FROM policy.policies
WHERE status = '有效'
GROUP BY customer_id;
2.4 MOD
概念:返回除法运算的余数。可用于判断奇偶、分组等场景。
语法 :MOD(dividend, divisor)
业务案例:将客户按 ID 的奇偶性分组,用于 AB 测试或批量任务分割。
sql
SELECT
customer_id,
full_name,
CASE WHEN MOD(customer_id, 2) = 0 THEN '偶数' ELSE '奇数' END AS group_id
FROM policy.customers;
2.5 ABS
概念:返回数值的绝对值。
语法 :ABS(numeric_value)
业务案例:计算实际理赔金额与预估金额的偏差绝对值。
sql
SELECT
claim_id,
claim_amount,
ABS(claim_amount - 10000) AS deviation_from_10k
FROM policy.claims;
2.6 POWER / SQRT
概念 :POWER(m, n) 返回 m 的 n 次方;SQRT(x) 返回 x 的平方根。
语法 :POWER(base, exponent), SQRT(numeric_value)
业务案例 :在保费厘定精算模型中,经常需要计算平方和、标准差等,SQRT 就是基础工具。
sql
SELECT
SQRT(AVG(premium * premium)) AS rms_premium
FROM policies;
2.7 RANDOM
概念:返回介于 0.0(包含)和 1.0(不包含)之间的随机浮点数。
语法 :RANDOM()
业务案例:从客户库中随机抽取 5% 的客户进行满意度调查。
sql
SELECT *
FROM policy.customers
WHERE RANDOM() < 0.05;
第三章 日期/时间函数
保险合同的关键就是时间------起保日期、理赔日期、续保提醒......日期函数是保险数据工程师的"基本功"。
3.1 CURRENT_DATE / CURRENT_TIMESTAMP / NOW
概念 :CURRENT_DATE 返回当前日期;CURRENT_TIMESTAMP 返回当前时间戳(含时区);NOW() 与 CURRENT_TIMESTAMP 等价。
语法:无需参数。
业务案例:查询所有已过终止日期的保单(失效)。
sql
SELECT *
FROM policy.policies
WHERE end_date IS NOT NULL
AND end_date < CURRENT_DATE;
3.2 EXTRACT / DATE_PART
概念 :从日期/时间中提取特定的部分,如年、月、日、小时等。EXTRACT 和 DATE_PART 功能几乎相同,只是书写风格略有差异。
语法:
-
EXTRACT(field FROM source) -
DATE_PART('field', source)
业务案例:分析保单生效日期的月份分布,了解哪个月份是销售高峰期。
sql
SELECT
EXTRACT(MONTH FROM start_date) AS month_start,
COUNT(*) AS policy_count
FROM policy.policies
GROUP BY EXTRACT(MONTH FROM start_date)
ORDER BY month_start;
3.3 AGE
概念 :计算两个日期之间的间隔,返回 interval 类型(人类可读的"X年X月X天")。若只给一个参数,则计算该日期到当前日期之间的间隔。
语法:
-
AGE(timestamp1, timestamp2) -
AGE(timestamp)
业务案例:计算每个客户的年龄(基于出生日期)。
sql
SELECT
full_name,
birth_date,
AGE(CURRENT_DATE, birth_date) AS current_age
FROM policy.customers;
3.4 DATE_TRUNC
概念:将日期/时间截断到指定的精度,例如截断到月份,则返回该月第一天。
语法 :DATE_TRUNC(precision, source)
业务案例:按月份统计理赔总额,需要将所有理赔日期归一化到所在月份。
sql
SELECT
DATE_TRUNC('month', claim_date) AS claim_month,
SUM(claim_amount) AS total_claim
FROM policy.claims
WHERE claim_status = '已结案'
GROUP BY DATE_TRUNC('month', claim_date)
ORDER BY claim_month;
3.5 TO_CHAR(日期格式化)
概念:将日期/时间按照指定的格式模式转换为字符串,是生成报表、导出数据时的利器。
语法 :TO_CHAR(timestamp, format_pattern)
常见格式符:
-
YYYY:四位年份 -
MM:月份(01-12) -
DD:日(01-31) -
HH24:小时(24小时制) -
MI:分钟 -
SS:秒 -
Day:星期名称 -
Q:季度
业务案例:将保单起保日期格式化为"2022年01月01日"的形式。
sql
SELECT
policy_id,
start_date,
TO_CHAR(start_date, 'YYYY"年"MM"月"DD"日"') AS formatted_start
FROM policy.policies;
第四章 条件判断函数
在数据处理中,经常需要根据不同的条件返回不同的值,比如给保单打标签、处理缺失值、避免除零错误等。
4.1 CASE WHEN
概念 :SQL 中的条件表达式,可以实现复杂的逻辑分支。有两种形式:搜索式 CASE(最常用)和简单 CASE。
语法:
sql
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
[ELSE default_result]
END
业务案例:根据年缴保费将保单划分为"高端""中端""经济型"三类。
sql
SELECT
policy_id,
premium,
CASE
WHEN premium >= 8000 THEN '高端'
WHEN premium >= 1000 THEN '中端'
ELSE '经济型'
END AS tier
FROM policy.policies;
4.2 COALESCE
概念 :返回参数列表中第一个非 NULL 的值。是处理缺失数据的首选工具。
语法 :COALESCE(value1, value2, ..., valueN)
业务案例 :customers 表中 agent_id 可能为 NULL(线上客户),我们希望未绑定代理人的客户在报表中显示为"无专属代理人"。
sql
SELECT
full_name,
COALESCE(agent_id::TEXT, '无专属代理人') AS agent_display
FROM customers;
4.3 NULLIF
概念 :如果两个参数相等,则返回 NULL;否则返回第一个参数的值。通常用于规避除零错误。
语法 :NULLIF(value1, value2)
业务案例 :计算理赔通过率时,分母(申请理赔次数)可能为零,使用 NULLIF 避免除以零报错。
sql
SELECT
COUNT(*) AS total_claims,
SUM(CASE WHEN claim_status = '已结案' THEN 1 ELSE 0 END) AS settled,
SUM(CASE WHEN claim_status = '已结案' THEN 1 ELSE 0 END) * 1.0
/ NULLIF(COUNT(*), 0) AS settlement_rate
FROM claims;
4.4 GREATEST / LEAST
概念 :分别返回列表中的最大值或最小值。注意:参数中的 NULL 会导致整个函数返回 NULL,除非所有参数都是 NULL。
语法 :GREATEST(value1, value2, ...), LEAST(value1, value2, ...)
业务案例:计算客户所有有效保单中最高的保额和最低的年缴保费。
sql
SELECT
customer_id,
MAX(insured_amount) AS max_cover,
MIN(premium) AS min_premium
FROM policy.policies
WHERE status = '有效'
GROUP BY customer_id;
虽然 MAX/MIN 是聚合函数,但 GREATEST 可用于单行多列的比较,例如比较多个年度保费调整方案,选择最高者:
sql
SELECT
policy_id,
GREATEST(premium * 1.05, premium * 1.10, premium * 1.15) AS max_next_year_premium
FROM policy.policies;
第五章 聚合函数
聚合函数将多行数据汇总为单个值,通常与 GROUP BY 结合使用,是数据分析的基石。
5.1 COUNT
概念 :计数函数。COUNT(*) 计算所有行的数量(包括 NULL 行);COUNT(column) 计算该列非 NULL 值的数量;COUNT(DISTINCT column) 计算去重后的非 NULL 值数量。
语法 :COUNT( [DISTINCT] expression )
业务案例:
-
统计客户总数
-
统计有多少个客户购买了"百万医疗险"
sql
-- 全部客户数(包括所有行)
SELECT COUNT(*) AS total_customers FROM policy.customers;
-- 购买了百万医疗险的客户数(去重)
SELECT COUNT(DISTINCT customer_id) AS med_cust_count
FROM policy.policies
WHERE product_name = '百万医疗险';
5.2 SUM / AVG
概念 :对数值列求和或计算平均值。它们自动忽略 NULL 值。
语法 :SUM(column), AVG(column)
业务案例:计算公司总保费收入,以及每单平均保费。
sql
SELECT
SUM(premium) AS total_premium,
AVG(premium) AS avg_premium
FROM policy.policies
WHERE status = '有效';
5.3 MAX / MIN
概念:返回列中的最大值或最小值。可用于日期、字符串和数值类型。
语法 :MAX(column), MIN(column)
业务案例:找出最老的保单(最早起保日期)和最近生效的保单。
sql
SELECT
MIN(start_date) AS oldest_policy_start,
MAX(start_date) AS newest_policy_start
FROM policy.policies;
5.4 STRING_AGG
概念 :将组内多行的字符串拼接成一个字符串,可指定分隔符并可选择排序顺序。类似 MySQL 的 GROUP_CONCAT。
语法 :STRING_AGG(expression, delimiter [ORDER BY ...])
业务案例:为每位代理人列出其名下的所有客户姓名,用顿号分隔。
sql
SELECT
a.agent_name,
STRING_AGG(c.full_name, '、' ORDER BY c.customer_id) AS client_list
FROM policy.agents a
INNER JOIN policy.customers c
ON a.agent_id = c.agent_id
GROUP BY a.agent_name;
5.5 ARRAY_AGG
概念:将组内多行的值聚合成一个 PostgreSQL 数组。非常适合传递给应用程序或进行二次分析。
语法 :ARRAY_AGG(expression [ORDER BY ...])
业务案例:将每个客户持有的所有保单 ID 收集到一个数组中。
sql
SELECT
customer_id,
ARRAY_AGG(policy_id ORDER BY start_date) AS policy_list
FROM policy.policies
GROUP BY customer_id;
第六章 类型转换函数
数据类型不匹配是 SQL 开发中的常见问题。PostgreSQL 提供了灵活的类型转换工具,确保数据能被正确解释和计算。
6.1 CAST 与 :: 操作符
概念 :将一种数据类型显式转换为另一种。:: 是 PostgreSQL 特有的快捷写法。
语法:
-
CAST(expression AS target_type) -
expression::target_type
业务案例 :将理赔金额转换为整数进行财务入账,或将字符串形式的日期转换为真正的 DATE 类型。
sql
-- 理赔金额转为整数
SELECT claim_id, CAST(claim_amount AS INTEGER) FROM claims;
-- 使用 :: 快捷方式
SELECT '2025-06-01'::DATE + INTERVAL '1 month';
6.2 TO_CHAR(数值转换)
概念:将数值按照指定的格式掩码转换为字符串,常用于财务报表的输出。
语法 :TO_CHAR(numeric_value, format_pattern)
常用格式符:
-
9:数字位(不强制显示) -
0:数字位(强制显示,不足补零) -
,:千分位分隔符 -
L:本地货币符号 -
FM:去除前导空格或尾部零
业务案例:将保额格式化为带有千分位和货币符号的字符串。
sql
SELECT
policy_id,
insured_amount,
TO_CHAR(insured_amount, 'FM¥999,999,999.00') AS formatted_amount
FROM policy.policies;
6.3 TO_NUMBER
概念:将格式化的字符串解析成数值。需要格式掩码与字符串严格匹配。
语法 :TO_NUMBER(string, format_pattern)
业务案例 :导入外部数据时,保费可能以"¥12,345.67"的形式存储,需要转换成可计算的 NUMERIC 类型。
sql
SELECT TO_NUMBER('¥12,345.67', 'L99,999.99');
6.4 TO_DATE / TO_TIMESTAMP
概念:将字符串按指定的日期/时间格式转换为对应的日期或时间戳类型。
语法:
-
TO_DATE(string, format_pattern) -
TO_TIMESTAMP(string, format_pattern)
业务案例 :将非标准格式的出生日期字符串(例如 '1990-05-20' 或 '20/05/1990')转换为标准日期类型,以便参与日期计算。
sql
SELECT TO_DATE('20/05/1990', 'DD/MM/YYYY') AS birth_date;
在我们的 customers 表中,birth_date 已经是 DATE 类型,但在 ETL 过程中经常会用到这两个函数。
结语:以上六大类函数覆盖了保险数据开发中最常用的场景。建议将本文收藏,在实际编写 SQL 时参照示例快速上手。随着 PostgreSQL 版本更新,还会有更多强大的内置函数涌现,但掌握这些基础,你已经可以应对 90% 的数据处理需求。