/home/zmy/postgresql/home/zmy/software/postgresql/install/home/zmy/software/postgresql/postgresql-18.3cd /home/zmy/software/postgresql; curl -O https://ftp.postgresql.org/pub/source/v18.3/postgresql-18.3.tar.gz; tar zxf postgresql-18.3.tar.gzcd /home/zmy/software/postgresql/postgresql-18.3
apt install -y gcc pkg-config libicu-dev bison flex libreadline-dev zlib1g-dev libssl-dev libxml2-dev libxslt1-dev libsystemd-dev make docbook-xml docbook-xsl libxml2-utils xsltproc fop
# 编译
./configure \
--prefix=/home/zmy/software/postgresql/install \
--with-pgport=15432 \
--with-ssl=openssl \
--with-systemd \
--with-libxml \
--with-libxslt \
--with-liburing \ # 启用io_uring
--with-lz4 \
--with-zstd
# 构建:构建所有可构建的内容,包括文档(HTML 和手册页)以及附加模块 (contrib)
make world -j$(nproc)
# 安装
make install-world
# 共享库
sudo ldconfig /home/zmy/software/postgresql/install/lib/
# 设置环境变量 ~/.bashrc
export PGHOME=/home/zmy/software/postgresql/install
export PGDATA=$PGHOME/data
export PATH=$PGHOME/bin:$PATH
export MANPATH=$PGHOME/share/man:$MANPATH
. ~/.bashrc
# 初始化数据库
pg_ctl initdb # 或者 initdb (-D /usr/local/pgsql/data 或者设置PGDATA环境变量 -W 交互式设置密码)
# 启动数据库
pg_ctl start -l logfile # 或者 postgres -D /usr/local/pgsql/data > logfile 2>&1 &
# 停止数据库
pg_ctl stop -m smart
# 配置为服务
sudo sh -c 'cat > /etc/systemd/system/postgresql.service << EOF
[Unit]
Description=PostgreSQL database server
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=zmy
ExecStart=/home/zmy/software/postgresql/install/bin/postgres -D /home/zmy/software/postgresql/install/data
ExecReload=/bin/kill -HUP $MAINPID
KillMode=mixed
KillSignal=SIGINT
TimeoutSec=infinity
[Install]
WantedBy=multi-user.target
EOF'
sudo systemctl daemon-reload
sudo systemctl enable
# 安装官方扩展(可选)
cd /home/zmy/software/postgresql/postgresql-18.3/contrib
make && make install
# 设置密码
ALTER USER zmy WITH PASSWORD '123456';
# 配置免密
cat > ~/.pgpass <<EOF
# hostname:port:database:username:password
# 前四项可以使用通配符 *
localhost:5432:postgres:zmy:123456
127.0.0.1:5432:postgres:zmy:123456
EOF
chmod 0600 ~/.pgpass
# 准备环境
curl -O https://www.pgbouncer.org/downloads/files/1.25.2/pgbouncer-1.25.2.tar.gz
tar zxf pgbouncer-1.25.2.tar.gz && cd pgbouncer-1.25.2
sudo apt install libevent-dev python3 pandoc -y
# 构建
./configure \
--prefix=/home/zmy/software/postgresql/other_ext/pgbouncer/install \
--with-systemd
# 编译
make -j$(nproc)
# 安装
make install
ln -sf /home/zmy/software/postgresql/other_ext/pgbouncer/install/bin/pgbouncer /usr/local/bin/pgbouncer
# 配置
mkdir /home/zmy/software/postgresql/other_ext/pgbouncer/install{etc,log,run,data}
# 连接pg,查看所有用户和密码哈希
SELECT
usename AS username,
passwd AS password_hash
FROM pg_shadow
WHERE usename NOT LIKE 'pg_%'
AND passwd IS NOT NULL
ORDER BY usename;
# 认证文件
cat > etc/userlist.txt <<EOF
;;;
;;; PgBouncer 用户认证文件
;;; 格式: "用户名" "密码哈希"
;;;
;;; 密码哈希可以是:
;;; - MD5: md5 + md5(password + username)
;;; - SCRAM-SHA-256: SCRAM-SHA-256$...
;;; - 明文: password(不推荐)
;;;
;; [修改] 根据上一步查询结果填写
"postgres" "md5abcdef1234567890abcdef1234567890"
"myuser" "md5d8578edf8458ce06fbc5bb76a58c5ca4"
;; 添加 PgBouncer 管理员用户(用于管理控制台)
;; 如果 postgres 用户已添加,可以用它作为管理员
EOF
cat > etc/pgbouncer.ini <<EOF
[databases]
postgres = host=localhost port=5432 user=postgres password=123456
uni_dev_platform = host=localhost port=5432 user=unicorn password=123456
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /home/zmy/software/postgresql/other_ext/pgbouncer/install/etc/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 50
max_db_connections = 150
logfile = /home/zmy/software/postgresql/other_ext/pgbouncer/install/log/pgbouncer.log
pidfile = /home/zmy/software/postgresql/other_ext/pgbouncer/install/run/pgbouncer.pid
# admin_users 可以连接虚拟的pgbouncer数据库,执行一些特殊的 SHOW 和 RELOAD 命令
admin_users = zmy
EOF
# 配置服务 share/doc/pgbouncer/pgbouncer.service
sudo sh -c 'cat > /etc/systemd/system/pgbouncer.service <<EOF
[Unit]
Description=connection pooler for PostgreSQL
Documentation=https://www.pgbouncer.org/
After=network.target postgresql.service
Wants=network-online.target
#Requires=pgbouncer.socket
[Service]
Type=forking
User=zmy
Group=zmy
ExecStart=/usr/local/bin/pgbouncer -d /home/zmy/software/postgresql/other_ext/pgbouncer/install/etc/pgbouncer.ini
ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/home/zmy/software/postgresql/other_ext/pgbouncer/install/run/pgbouncer.pid
KillSignal=SIGINT
Restart=on-failure
# 安全增强 (Hardening)
ProtectSystem=strict
ReadWritePaths=/home/zmy/software/postgresql/other_ext/pgbouncer/install/run /home/zmy/software/postgresql/other_ext/pgbouncer/install/log /home/zmy/software/postgresql/other_ext/pgbouncer/install/etc
NoNewPrivileges=true
PrivateTmp=true
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF'
# 下载源码
git clone --branch v0.8.2 https://github.com/pgvector/pgvector.git
cd pgvector
# 编译安装
make PG_CONFIG=/home/zmy/software/postgresql/install/bin/pg_config
make PG_CONFIG=/home/zmy/software/postgresql/install/bin/pg_config install
# 重启
sudo systemctl restart postgresql
# 安装扩展
CREATE EXTENSION vector;
# 安装scws
curl -LO http://www.xunsearch.com/scws/down/scws-1.2.3.tar.bz2
tar xf scws-1.2.3.tar.bz2
cd scws-1.2.3
./configure
make install
# 编译安装zhparser
git clone https://github.com/amutu/zhparser.git
PG_CONFIG=/home/zmy/software/postgresql/install/bin/pg_con make && make install
CREATE EXTENSION zhparser;
CREATE TEXT SEARCH CONFIGURATION chinese_zh (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION chinese_zh ADD MAPPING FOR n,v,a,i,e,l WITH simple;
--创建bm25索引
--K1 - 项频率饱和参数(默认为 1.2)值越大,词频对得分的影响持续越长
--b - 长度归一化参数(默认为 0.75)
CREATE INDEX docs_idx ON documents USING bm25(content) WITH (text_config='chinese_zh', k1=1.2, b=0.75);
git clone https://github.com/jaiminpan/pg_jieba
cd pg_jieba
# initilized sub-project
git submodule update --init --recursive
cd pg_jieba
mkdir build && cd build
cmake -DCMAKE_PREFIX_PATH=/home/zmy/software/postgresql/install ..
make
make install
监控类角色:允许读取监控信息,但不能修改数据。
pg_monitor:核心监控角色,可以读取各种统计信息和日志,用于监控数据库性能。是 pg_read_all_settings, pg_read_all_stats, pg_stat_scan_tables 的集合。pg_read_all_settings:读取所有配置参数,可以查看数据库的所有设置(包括一些敏感设置)。pg_read_all_stats:读取所有统计信息,可以查看诸如表大小、索引使用情况等统计信息。pg_stat_scan_tables:执行统计信息扫描,允许执行需要扫描表的统计信息查询。数据访问类角色:允许读取或写入数据库内的所有数据。
pg_read_all_data:读取所有数据,可以读取数据库中任何表中的数据(类似只读权限),但不能修改。pg_write_all_data:写入所有数据,可以向数据库中的任何表插入、更新、删除数据。文件系统访问类角色:允许在数据库服务器本地读写文件。(高危权限!)
pg_read_server_files:读取服务器文件,可以使用如 COPY FROM 或 pg_read_file() 等命令读取数据库服务器磁盘上的文件。pg_write_server_files:写入服务器文件,可以使用如 COPY TO 等命令向数据库服务器磁盘写入文件。pg_execute_server_program:在服务器上执行程序,可以在执行 COPY 命令时调用外部程序。极其危险!管理操作类角色:允许执行特定的管理任务。
pg_signal_backend:向其他进程发送信号,可以终止其他后端的查询或连接(如 pg_terminate_backend)。pg_checkpoint:执行检查点操作,可以手动触发一个数据库检查点(通常由系统自动完成)。pg_create_subscription:创建逻辑复制订阅,用于设置逻辑复制。pg_use_reserved_connections:使用保留连接,可以使用为超级用户保留的连接槽(当普通连接已满时)。pg_signal_autovacuum_worker:向自动清理进程发信号,可以终止正在运行的自动清理(autovacuum)工作进程。特殊角色:
pg_database_owner:数据库所有者角色的集合,不是一个具体用户,任何数据库的拥有者都自动拥有该角色的权限。pg_maintain维护角色,允许执行一系列常见的维护操作,如 VACUUM, ANALYZE, REINDEX 等,但不需要超级用户权限。postgres:超级用户角色,这是安装后创建的默认超级用户(SA),拥有最高权限,可以对数据库做任何操作。授权语句:GRANT pg_monitor TO my_monitoring_user;
pg_catalog 是 PostgreSQL 数据库的“心脏”和“大脑”,它是一组核心的系统表、视图、函数和数据类型的集合。这些对象共同记录了当前数据库的所有元数据。
information_schema:是 ANSI SQL 标准定义的一套用于查询元数据的视图。优点是标准、稳定,但查询可能比 pg_catalog 慢
指定密码登录:psql "dbname=postgres host=127.0.0.1 user=postgres password=123456 port=5432"
使用psql登录时,-d 可以省(默认数据库名 = 用户名),-p 可以省(默认 5432),-h 省略默认 localhost,-U 省略默认找 PGUSER,-h 和 -U 最好写明确,除非配置了环境变量 PGHOST、PGUSER。
创建用户也就是创建一个能登录的角色,pg 中只有角色,没有用户,create user 'zmy' password '123456' 实际就是创建角色:create role 'zmy' with LOGIN,如果单纯创建角色就是 create role 'zmy' with NOLOGIN
修改搜索路径:
-- 查看当前的搜索路径
SHOW search_path;
-- 临时设置
SET search_path TO myschema, public;
RESET search_path;
-- 为当前用户设置
ALTER ROLE myuser SET search_path TO "$user", myschema, public;
-- 为整个数据库设置
ALTER DATABASE mydb SET search_path TO "$user", myschema, public;
-- 为整个示例设置
search_path = 'myschema, public'
# JDBC中指定搜索路径
String url = "jdbc:postgresql://127.0.0.1:5432/test_db?options=-c%20search_path%3Dtest_schema,public,$user";
pg_hba.conf 配置了pg的访问权限,限制了哪些机器/IP段上的哪些用户对 pg 有什么样的访问权限,默认配置中只有本地用户可以访问 pg。对该文件的修改可以使用 pg_ctl reload 命令或 SQ L命令 SELECT pg_reload_conf() 来重新加载配置,无需重启数据库服务。pg_ident.conf 定义了操作系统用户名和 pg 中用户名的映射关系。postgresql.conf 中包含了一个 pg 实例的其他所有配置。postmaster.pid 文件是一个锁文件,生命周期和 postmaster(pg最主要的一个进程,操作系统中其进程名也是postgres)进程一样,其中记录了 postmaster 的pid 和共享内存段 id。postmaster.opts 中记录了 postmaster 上一次启动时的命令行参数。CREATE TRIGGER trigger_name
{ BEFORE | AFTER | INSTEAD OF }
{ INSERT | UPDATE [OF column_name [, ...]] | DELETE | TRUNCATE }
ON table_name
[ FOR [ EACH ] { ROW | STATEMENT } ]
EXECUTE FUNCTION function_name();
BEFORE 在操作真正执行前触发 数据校验、自动赋值(如 updated_at)、修改 NEW 值AFTER 在操作成功完成后触发 写审计日志、发通知、更新其他表(如统计计数)INSTEAD OF 不执行原操作,只执行触发器函数 用于视图(View) 上的 DML 操作(因为视图不能直接改), INSTEAD OF 只能用于视图,不能用于普通表。INSERT 插入新行UPDATE 更新现有行(可指定具体列:UPDATE OF name, email)UPDATE OF col1, col2 表示:只有当 col1 或 col2 被更新时才触发,避免无谓调用。DELETE 删除行TRUNCATE 清空整张表(PostgreSQL 特有,MySQL 不支持)FOR EACH ROW vs FOR EACH STATEMENTFOR EACH ROW(默认) 每行一次 NEW, OLD 处理单行数据(90% 场景)FOR EACH STATEMENT 整个语句一次 不能用 NEW/OLD 全局操作(如“本次更新共影响多少行”)NEW 是一个特殊变量,代表 即将写入数据库的新行数据CREATE FUNCTION dept(text) RETURNS dept AS $$ SELECT * FROM dept WHERE name = $1 $$ LANGUAGE SQL; 这里 $1 引用函数被调用时第一个函数参数的值(rowfunction(a,b)).col3 从复合类型的表列中抽取字段 (mytable.compositecol).somefield 获取所有字段 (compositecol).*SELECT ROW(1,2.5,'this is a test'); 默认情况下,ROW 表达式创建的值具有匿名记录类型。如有需要,可以把它转换为具名复合类型,也就是某个表的行类型. 默认情况下,ROW 表达式创建的值具有匿名记录类型。如有需要,可以把它转换为具名复合类型,也就是某个表的行类型SELECT concat_lower_or_upper('Hello', 'World', true);SELECT concat_lower_or_upper(a => 'Hello', b => 'World'); 兼容旧语法:SELECT concat_lower_or_upper(a := 'Hello', uppercase := true, b := 'World');SELECT concat_lower_or_upper('Hello', 'World', uppercase => true);CREATE TABLE中使用GENERATED ... AS IDENTITY子句. 列定义中的ALWAYS和BY DEFAULT子句决定了在INSERT和UPDATE命令中如何处理用户显式指定的值。在INSERT命令中,如果选择了ALWAYS,只有当INSERT语句指定了OVERRIDING SYSTEM VALUE时,才接受用户指定的值。如果选择了BY DEFAULT,则用户指定的值优先生效。因此,使用BY DEFAULT的行为更接近默认值,即默认值可以被显式值覆盖;而ALWAYS则能更好地防止意外插入显式值。标识列的数据类型必须是序列支持的数据类型之一VIRTUAL或STORED可以显式指定类型price numeric CHECK (price > 0) 带约束名 price numeric CONSTRAINT positive_price CHECK (price > 0) 表约束 CHECK (price > discounted_price). 列约束也可以写成表约束,但反过来不行ALTER TABLE products ADD COLUMN description text; ALTER TABLE products ADD COLUMN description text CHECK (description <> ''); 凡是在CREATE TABLE中可用于列描述的选项,在这里都可以使用ALTER TABLE products DROP COLUMN description; 级联删除ALTER TABLE products DROP COLUMN description CASCADE;ALTER TABLE products ADD CHECK (name <> ''); ALTER TABLE products ADD CONSTRAINT some_name UNIQUE (product_no); ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups; 非空约束: ALTER TABLE products ALTER COLUMN product_no SET NOT NULL;ALTER TABLE products DROP CONSTRAINT some_name; 删除非空约束:ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77; 移除任何默认值 ALTER TABLE products ALTER COLUMN price DROP DEFAULT;ALTER TABLE products RENAME COLUMN product_no TO product_number;ALTER TABLE products RENAME TO items;ALTER TABLE table_name OWNER TO new_owner;GRANT UPDATE ON accounts_tab TO joe;REVOKE ALL ON accounts_tab FROM PUBLIC;CREATE SCHEMA schema_name AUTHORIZATION user_name;SHOW search_path; 把新模式放在搜索路径中 SET search_path TO myschema,public;INSERT INTO users (firstname, lastname) VALUES ('Joe', 'Cool') RETURNING id;UPDATE products SET price = price * 1.10 WHERE price <= 99.99 RETURNING name, price AS new_price;DELETE FROM products WHERE obsoletion_date = 'today' RETURNING *;MERGE INTO products p USING new_products n ON p.product_no = n.product_no
WHEN NOT MATCHED THEN INSERT VALUES (n.product_no, n.name, n.price)
WHEN MATCHED THEN UPDATE SET name = n.name, price = n.price
RETURNING p.*;
ROWS FROM语法把多个表函数组合起来,并以并行列的形式返回结果;这种情况下,结果行数等于返回行数最多的那个函数的结果行数,较小的结果会用空值填充到相同长度。function_call [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]; ROWS FROM( function_call [, ... ] ) [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]UNNEST( array_expression [, ... ] ) [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$
SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);
function_call [AS] alias (column_definition [, ... ])
function_call AS [alias] (column_definition [, ... ])
ROWS FROM( ... function_call AS (column_definition [, ... ]) [, ... ] )
SELECT o.order_id, o.customer, items.item_name, items.price
FROM orders o
CROSS JOIN LATERAL (
SELECT item_name, price
FROM order_items
WHERE order_id = o.order_id -- 这里引用了外层的 o.order_id
ORDER BY price DESC
LIMIT 2 -- 只取前 2 个
) items;
SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter);CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); enum 类型中各值的顺序,就是创建该类型时列出的顺序, 枚举标签是大小写敏感的,因此'happy'与'HAPPY'是不同的。标签中的空格也是有意义的。SELECT 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12'::tsvector; 一个位置通常表示源词在文档中的位置。位置信息可用于邻近度排序。位置值可以位于 1 到 16383 之间;更大的数字会被静默设为 16383。同一词位的重复位置会被丢弃。SELECT 'a:1A fat:2B,4C cat:5D'::tsvector; 权重通常用于反映文档结构,例如把标题中的词和正文中的词区分开来。 文本搜索排序函数可以为不同的权重标记分配不同优先级。to_tsvector,以按搜索需要对词语进行规范化SELECT to_tsvector('english', 'The Fat Rats');<->(FOLLOWED BY)。此外, FOLLOWED BY 还有一种变体 <N>,其中 N 是整数常量,用于指定被搜索的两个词位 之间的距离。<-> 等效于 <1>。SELECT 'fat & rat & ! cat'::tsquery;SELECT 'fat:ab & cat'::tsquery;* 标签来指定前缀匹配:SELECT 'super:*'::tsquery; 这个查询将匹配 tsvector 中任何以 super 开头的词位。SELECT to_tsquery('Fat:ab & Cats');json1 @> json2:如果 json1 包含 json2 返回 true:SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb;。一般原则是,被包含对象 json2 在结构和数据内容上都必须与包含对象 json1 匹配,特殊例外:数组可以包含一个基本值:SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb;json ? key,只查找最顶层的 key 或数组中的元素:SELECT '["foo", "bar", "baz"]'::jsonb ? 'bar';jsonb_ops(默认)和 jsonb_path_ops: CREATE INDEX idxgin ON api USING GIN (jdoc) 和 CREATE INDEX idxginp ON api USING GIN (jdoc jsonb_path_ops); jsonb_ops 支持键存在操作符:?, ?|, ?& 存在操作符 @> 和 jsonpath 匹配操作符 @?, @@,jsonb_path_ops 支持 @>, @?, @@。jsonb_path_ops 在 @>, @?, @@ 上的操作比 jsonb_ops 更快(jsonb_ops 与 jsonb_path_ops GIN 索引之间的技术差异在于,前者会为数据中的每个键和值分别创建独立的索引项,而后者只会为数据中的每个值创建索引项。)SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c']; SELECT ('[1, "2", null]'::jsonb)[1];'{ val1, val2, ... }' 每个 val 要么是数组元素类型的常量,要么是一个子数组。也可以使用 ARRAY 构造器 ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']])array[1] 开始,到 array[n] 结束lower-bound:upper-bound.如果任何一个维度写成切片形式,也就是包含冒号,那么所有维度都会被当作切片处理。任何只有单个数字(没有冒号)的维度都会被视为从 1 到该数字指定的范围。例如,[2] 会被当作 [1:2]。为了避免与非切片情况混淆,最好对所有维度都使用切片语法,例如写成 [1:2][1:1],而不是 [2][1:1]。切片说明符中的 lower-bound 或 upper-bound 可以省略;缺失的边界会分别由数组下标的下界或上界代替。如果数组本身或任一下标表达式为 NULL,则数组下标表达式将返回空值。如果下标超出数组边界,也会返回空值SELECT * FROM sal_emp WHERE 10000 = ANY (pay_by_quarter); SELECT * FROM sal_emp WHERE 10000 = ALL (pay_by_quarter); && 操作符: 它会检查左操作数是否与右操作数有重叠 SELECT * FROM sal_emp WHERE pay_by_quarter && ARRAY[10000]; array_position 和 array_positions 函数在数组中搜索特定值。前者返回某个值在数组中首次出现位置的下标;后者返回一个数组,其中包含该值在数组中所有出现位置的下标 SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon'); 返回 2,SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon'); 返回 {1,4,8}--定义复合类型
CREATE TYPE complex AS (
r double precision,
i double precision
);
CREATE TYPE inventory_item AS (
name text,
supplier_id integer,
price numeric
);
--使用复合类型
CREATE TABLE on_hand (
item inventory_item,
count integer
);
INSERT INTO on_hand VALUES (ROW('fuzzy dice', 42, 1.99), 1000);
'("fuzzy dice",42,1.99)',要让某个字段为 NULL,就在列表中对应的位置什么也不写 '("fuzzy dice",42,)',如果想写空字符串而不是 NULL,请写双引号:'("",42,)'ROW('', 42, NULL),只要表达式中有多个字段,ROW关键字实际上是可选的 ('', 42, NULL)SELECT (item).name FROM on_hand WHERE (item).price > 9.99;,需要使用表名 SELECT (on_hand.item).name FROM on_hand WHERE (on_hand.item).price > 9.99;--插入或更新整个列值
INSERT INTO mytab (complex_col) VALUES((1.1,2.2)); --省略了ROW
UPDATE mytab SET complex_col = ROW(1.1,2.2) WHERE ...;
-- 更新组合列中的单个子字段,这里不需要(事实上也不能)给紧跟在SET后面的列名加圆括号,但在等号右侧的表达式中引用同一列时,则需要加圆括号。
UPDATE mytab SET complex_col.r = (complex_col).r + 1 WHERE ...;
--把子字段指定为INSERT的目标,如果没有为该列的所有子字段提供值,其余子字段就会填充为 NULL 值。
INSERT INTO mytab (complex_col.r, complex_col.i) VALUES(1.1, 2.2);
SELECT c FROM inventory_item c; 产生一个单独的组合值列 ("fuzzy dice",42,1.99)(简单名称会先与列名匹配,再与表名匹配).* 表达式应用展开行为,只要 .* 所作用的值不是简单表名,就需要给该值加圆括号 SELECT (myfunc(x)).* FROM some_table;。当 composite_value.* 出现在 SELECT输出列表、INSERT/UPDATE/DELETE/MERGE中的RETURNING列表、VALUES子句或行构造器的顶层时,就会产生这种列展开行为。在所有其他上下文中(包括嵌套在上述结构之内时),给复合值附加 .* 不会改变其值,因为它表示“所有列”,因此结果仍然是同一个复合值。当确定需要复合值时,优先使用 c.* 而非 c 是个好习惯,否则会优先将 c 作为列名解析field(table) 和 table.field 可以互换。oid 表示一个对象标识符。 此外还有若干 oid 的别名类型,统称为 regsomething。别名类型简化了对象 OID 值的查找 SELECT * FROM pg_attribute WHERE attrelid = 'mytable'::regclass;string LIKE pattern [ESCAPE escape-character] 在pattern里的下划线 (_)代表(匹配)任何单个字符; 而一个百分号(%)匹配任何零或更多个字符的序列。要匹配文本的 _ 或者 %,而不是匹配其它字符, 在pattern里相应的字符必须前导转义字符。缺省的转义字符是反斜线\,但是可以用ESCAPE子句指定一个不同的转义字符。要匹配转义字符本身,写两个转义字符 \\。~~ 等效于 LIKE, 而 ~~* 对应 ILIKE。 还有 !~~ 和 !~~* 操作符分别代表 NOT LIKE 和 NOT ILIKE\, *, +, ?, {m,n}, [...] ( . 不是元字符)select to_char(current_timestamp, 'YYYY-FMMM-dd HH24:MI:SS') FMMM 抑制前导 0CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_TIME(precision), CURRENT_TIMESTAMP(precision), LOCALTIME, LOCALTIMESTAMP, LOCALTIME(precision), LOCALTIMESTAMP(precision)SELECT TIMESTAMP 'now'; 例如在表列的DEFAULT子句中。系统将在分析这个常量的时候把 now 转换为一个 timestamp,这样需要默认值时就会得到创建表的时间,而不是插入数据时的时间,now() 和 CURRENT_TIMESTAMP 因为它们是函数调用。因此它们可以给出每次插入行的时刻transaction_timestamp(), statement_timestamp(), clock_timestamp(), timeofday(), now()transaction_timestamp() 等价于 CURRENT_TIMESTAMPstatement_timestamp() 返回当前语句的开始时刻(更准确的说是收到 客户端最后一条命令的时间)clock_timestamp() 返回真正的当前时间,因此它的值甚至在同一条 SQL 命令中都会变化。timeofday() 是一个有历史原因的PostgreSQL函数。和 clock_timestamp() 相似,timeofday() 也返回真实的当前时间,但是它的结果是一个格式化的text串(Sat Apr 25 18:15:52.487224 2026 CST),而不是 timestamp with time zone 值。now() 是PostgreSQL的一个传统,等效于 transaction_timestamp()。unnest (anyarray) → setof anyelement 将数组展开到一组行。 数组的元素按存储顺序读出。unnest (anyarray, anyarray [, ... ]) → setof anyelement, anyelement [, ... ] 将多个数组(可能是不同的数据类型)展开到一组行中。如果数组的长度不完全相同,那么较短的数组将用NULL填充current_setting(setting_name text [, missing_ok boolean]) → text 查看配置参数。错误的参数名报错,除非设置missing_ok为true,此函数相当于 show setting_name 命令set_config(setting_name text, new_value text, is_local boolean ) → text 将参数setting_name设置为new_value,并返回该值。 如果is_local为true,新值将仅在当前事务期间应用。如果希望新值应用于当前会话的其余部分,请使用false代替。这个函数对应于SQL命令SET。CREATE INDEX name ON table [USING HASH (column)];< <= = >= >=<< &< &> >> <<| &<| |&> |>> @> <@ ~= &&<< >> ~= <@ <<| |>><@ @> = &&< <= = >= >SELECT y FROM tab WHERE x = 'key'; 创建索引 CREATE INDEX tab_x_y ON tab(x) INCLUDE (y); 查询就可以以仅索引扫描的方式完成,因为y可以直接从索引中取得,而不必访问堆@@:如果一个 tsvector(文档)匹配一个 tsquery(查询),它就返回 true。哪一种数据类型写在前面并不重要。text @@ tsquery 等价于 to_tsvector(x) @@ y。text @@ text 等价于 to_tsvector(x) @@ plainto_tsquery(y)&(AND)操作符指定它的两个参数都必须出现在文档中才表示匹配。类似地,|(OR)操作符指定至少一个参数必须出现,而 !(NOT)操作符指定它的参数不出现才能匹配。<->(FOLLOWED BY)tsquery操作符,也可以搜索短语。只有当它的参数在文档中有相邻且顺序符合要求的匹配时,查询才算匹配。FOLLOWED BY 操作符还有一个更一般的形式 <N>,其中 N 是表示匹配词位位置差的整数CREATE INDEX pgweb_idx ON pgweb USING GIN(to_tsvector('english', body)); 由于上面的索引使用了 to_tsvector 的双参数版本,因此只有同样使用相同配置名的双参数版 to_tsvector 查询,才能使用该索引。也就是说,WHERE to_tsvector('english', body) @@ 'a & b' 可以使用该索引,而 WHERE to_tsvector(body) @@ 'a & b' 则不能plainto_tsquery 把未格式化的文本 querytext 转换成一个 tsquery 值。该文本会像to_tsvector那样被解析并正规化,然后在保留下来的词之间插入&(AND)tsquery操作符。注意,plainto_tsquery 不会识别输入中的 tsquery 操作符、权重标签或前缀匹配标签 SELECT plainto_tsquery('english', 'The Fat Rats'); 返回 'fat' & 'rat'phraseto_tsquery 的行为很像 plainto_tsquery,不过它会在保留下来的词之间插入 <->(FOLLOWED BY)操作符,而不是 &(AND)操作符。phraseto_tsquery 函数也不会识别输入中的tsquery操作符、权重标签或前缀匹配标签。SELECT phraseto_tsquery('english', 'The Fat Rats'); 返回 'fat' <-> 'rat'websearch_to_tsquery 使用一种替代语法从 querytext 创建 tsquery 值,在这种语法中,简单的未格式化文本本身就是合法查询。与 plainto_tsquery 和 phraseto_tsquery 不同,它还能识别某些操作符。此外,这个函数永远不会报告语法错误,因此可以直接把用户提供的原始输入用于搜索。支持的语法如下:&操作符分隔的词,就像经过plainto_tsquery处理一样。<->操作符分隔的词,就像经过phraseto_tsquery处理一样。OR:“or”将转换为|操作符。-:短横线会被转换成 ! 操作符。SELECT websearch_to_tsquery('english', '"supernovae stars" -crab'); 返回 'supernova' <-> 'star' & !'crab'tsvector_update_trigger(tsvector_column_name, config_name, text_column_name [, ... ]):config_name 必须带模式限定tsvector_update_trigger_column(tsvector_column_name, config_column_name, text_column_name [, ... ]):config_column_name 是另一个表列的名称,该列必须是 regconfig 类型。这样就可以按行选择配置pg_ctl reload或调用 SQL 函数pg_reload_conf()来发送这个信号)后都会重新读取postgresql.conf配置文件。postgresql.auto.conf 它与 postgresql.conf 采用相同的格式,但设计为自动编辑而非手工编辑。这个文件保存了通过ALTER SYSTEM命令提供的设置。 每当读取 postgresql.conf 时,也会读取该文件,并以同样的方式使其中设置生效。 postgresql.auto.conf 中的设置会覆盖 postgresql.conf 中的设置。ALTER SYSTEM命令提供了一种改变全局默认值的从SQL可访问的方法;它在功效上等效于编辑postgresql.confALTER DATABASE命令允许针对一个数据库覆盖其全局设置。ALTER ROLE命令允许用用户指定的值来覆盖全局设置和数据库设置。ALTER DATABASE和 ALTER ROLE设置的值才会被应用current_setting(setting_name text)set_config(setting_name, new_value, is_local) pg_settings可以被用来查看和改变会话本地的值, 在这个视图上使用UPDATE并且指定更新setting 列,其效果等同于发出SET命令postgres -c log_connections=all --log-destination='syslog'postgresql.conf中使用include命令导入配置文件,还有include_if_exists, include_dir 非绝对目录名会被解释为相对于引用配置文件所在目录的路径。在指定目录中, 只有名称以 .conf 结尾的非目录文件才会被包含pg_dump dbname -n schema|-t table > dumpfile,-h host, -p port,-U user_name,pg_dump连接同样受常规客户端认证机制约束psql -X dbname < dumpfile,默认情况下,psql脚本在遇到 SQL 错误后仍会继续执行。ON_ERROR_STOP可以改变这一行为,并让psql在发生 SQL 错误时以退出状态码 3 退出 psql -X --set ON_ERROR_STOP=on dbname < dumpfile-1或--single-transaction命令行选项传给psql来启用这种模式。pg_dump -h host1 dbname | psql -X -h host2 dbnametemplate0为基准的。这意味着通过template1增加的任何语言、过程等也都会被pg_dump转储。因此,恢复时如果你使用的是定制过的template1,就必须像上面的例子那样,从template0创建空数据库。pg_dumpall > dumpfile, 生成的转储可以用psql恢复:psql -X -f dumpfile postgres (实际上,可以指定任意一个现有数据库名作为起点,但如果要装载到一个空集簇中,通常应使用postgres。)恢复pg_dumpall转储时始终需要数据库超级用户权限,因为恢复角色和表空间信息必须使用该权限。如果使用了表空间,请确保转储中的表空间路径适合新的安装。pg_dump dbname | gzip > filename.gz 恢复时:gunzip -c filename.gz | psql dbname 或 cat filename.gz | gunzip | psql dbnamepg_dump dbname | split -b 2G - filename 恢复时:cat filename* | psql dbnamepg_dump dbname | split -b 2G --filter='gzip > $FILE.gz' 恢复时可使用 zcat。pg_dump -Fc dbname > filename 自定义格式的转储不是供psql执行的脚本,而必须通过pg_restore恢复,例如:pg_restore -d dbname filenamepg_dump -j num -F d -f out.dir dbname 可以使用pg_restore -j并行恢复转储。tar -cf backup.tar /usr/local/pgsql/data,这种方法有两个限制:wal_level设置为replica或更高,将archive_mode设置为on,并在archive_command配置参数中指定要使用的 shell 命令,或者在archive_library配置参数中指定要使用的库。archive_command中,%p会被替换为待归档文件的路径名,而%f只会被替换为文件名。(该路径名相对于当前工作目录,也就是集簇的数据目录。)如果需要在命令中嵌入实际的%字符,请使用%%。例如:archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f' # Unix; archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"' # WindowsSELECT pg_backup_start(label => 'label', fast => false);SELECT * FROM pg_backup_stop(wait_for_archive => true);这会终止备份模式。restore_command = 'cp /mnt/server/archivedir/%f %p' 重要的是,该命令在失败时必须返回非零退出状态。restore_command。在恢复开始时,你也可能看到一条针对类似00000001.history文件的错误消息。这同样是正常的,在简单恢复场景中并不表示有问题;相关讨论见Section 25.3.6。将 WAL 记录直接从一台数据库服务器移动到另一台数据库服务器,通常称为日志传送。PostgreSQL 通过一次传送一个文件(WAL 段)中的 WAL 记录来实现基于文件的日志传送。
26.2.1. 规划
26.2.2. 备库操作: 如果服务器启动时其数据目录中存在 standby.signal 文件,服务器就会进入备库模式。在备库模式中,服务器会持续应用从主库接收到的 WAL。备库可以从 WAL 归档中读取 WAL(见 restore_command),也可以通过 TCP 连接直接从主库读取 WAL(流复制)。当执行 pg_ctl promote 或调用 pg_promote() 时,将退出备库模式,服务器切换到正常运行。
26.2.3. 为备库准备主库
26.2.4. 设置备库: 要设置备库,先恢复从主库获取的基础备份(见 Section 25.3.5)。然后在备库的集簇数据目录中创建一个 standby.signal 文件。如果想使用流复制,就在 primary_conninfo 中填入一个 libpq 连接字符串,其中包含主机名(或 IP 地址)以及连接主库所需的其他细节。如果主库认证需要密码,也应在 primary_conninfo 中指定该密码。如果使用 WAL 归档,可以借助archive_cleanup_command 参数删除备库不再需要的文件,以尽量缩小归档大小。pg_archivecleanup 工具专门设计用于在典型的单备库配置中与 archive_cleanup_command 配合使用,见 pg_archivecleanup。备库的数量可以任意多,但如果使用流复制,请确保主库上的 max_wal_senders 设置得足够高,以允许它们同时连接
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass options=''-c wal_sender_timeout=5000'''
restore_command = 'cp /path/to/archive/%f %p'
archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'
host replication foo 192.168.1.100/32 scram-sha-256 监控: 可以通过 pg_stat_replication 视图取得 WAL 发送进程列表。在热备上,WAL 接收进程的状态可以通过 pg_stat_wal_receiver视图取得。CREATE PUBLICATION 命令创建发布,之后可使用对应命令修改或删除。单个表可以使用 ALTER PUBLICATION 动态添加和移除。ADD TABLE 与 DROP TABLE 都是事务性的,因此事务提交后,表会在正确的快照点开始或停止复制。CREATE SUBSCRIPTION 添加订阅;可随时用 ALTER SUBSCRIPTION 停止/恢复;并可用 DROP SUBSCRIPTION 删除。--在发布端创建一些测试表。
/* pub # */ CREATE TABLE t1(a int, b text, PRIMARY KEY(a));
/* pub # */ CREATE TABLE t2(c int, d text, PRIMARY KEY(c));
/* pub # */ CREATE TABLE t3(e int, f text, PRIMARY KEY(e));
--在订阅端创建同样的表。
/* sub # */ CREATE TABLE t1(a int, b text, PRIMARY KEY(a));
/* sub # */ CREATE TABLE t2(c int, d text, PRIMARY KEY(c));
/* sub # */ CREATE TABLE t3(e int, f text, PRIMARY KEY(e));
--在发布端向表中插入数据。
/* pub # */ INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three');
/* pub # */ INSERT INTO t2 VALUES (1, 'A'), (2, 'B'), (3, 'C');
/* pub # */ INSERT INTO t3 VALUES (1, 'i'), (2, 'ii'), (3, 'iii');
--为这些表创建发布。发布 pub2 和 pub3a 禁用了部分 publish 操作。发布 pub3b 使用了行过滤器(见 Section 29.4)。
/* pub # */ CREATE PUBLICATION pub1 FOR TABLE t1;
/* pub # */ CREATE PUBLICATION pub2 FOR TABLE t2 WITH (publish = 'truncate');
/* pub # */ CREATE PUBLICATION pub3a FOR TABLE t3 WITH (publish = 'truncate');
/* pub # */ CREATE PUBLICATION pub3b FOR TABLE t3 WHERE (e > 5);
--为这些发布创建订阅。订阅 sub3 同时订阅 pub3a 和 pub3b。默认情况下所有订阅都会复制 初始数据。
/* sub # */ CREATE SUBSCRIPTION sub1
/* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=sub1'
/* sub - */ PUBLICATION pub1;
/* sub # */ CREATE SUBSCRIPTION sub2
/* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=sub2'
/* sub - */ PUBLICATION pub2;
/* sub # */ CREATE SUBSCRIPTION sub3
/* sub - */ CONNECTION 'host=localhost dbname=test_pub application_name=sub3'
/* sub - */ PUBLICATION pub3a, pub3b;
--注意,无论发布的 publish 操作如何,初始表数据都会被复制。(不受行过滤器影响)
--在常规复制阶段会使用相应的 publish 操作。这意味着发布 pub2 与 pub3a 不会复制 INSERT。
ALTER ROUTINE 和 DROP ROUTINE 这样的命令可以操作函数和过程而不需要知道它们是哪一种。不过,要注意没有CREATE ROUTINE 命令。SETOF sometype被声明为返回一个集合(也就是多个行),或者等效地声明它为RETURNS TABLE(columns)。在这种情况下,最后一个查询的结果的所有行会被返回。CREATE TABLE emp (
name text,
salary numeric,
age integer,
cubicle point
);
-- 返回复合类型emp(列表顺序必须与列在复合类型中出现的顺序完全相同;类型必须匹配)
CREATE FUNCTION new_emp() RETURNS emp AS $$
SELECT text 'None' AS name,
1000.0 AS salary,
25 AS age,
point '(2,2)' AS cubicle;
$$ LANGUAGE SQL;
-- 返回多列(本质上该函数的结果创建了一个匿名复合类型)
-- 在从 SQL 调用这样一个函数时,输出参数不会被包括在调用参数列表中。
CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int)
AS 'SELECT x + y, x * y'
LANGUAGE SQL;
-- 等价效果
CREATE TYPE sum_prod AS (sum int, product int);
CREATE FUNCTION sum_n_product (int, int) RETURNS sum_prod
AS 'SELECT $1 + $2, $1 * $2'
LANGUAGE SQL;
-- 可变参数:只要所有 “可选”参数都属于同一种数据类型。可选参数会以数组形式传递给函数。定义这类函数时,需要把最后一个参数标记为 VARIADIC;该参数必须被声明为数组类型
CREATE FUNCTION mleast(VARIADIC arr numeric[]) RETURNS numeric AS $$
SELECT min($1[i]) FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;
-- 调用方式
SELECT mleast(10, -1, 5, 4.4);
SELECT mleast(ARRAY[10, -1, 5, 4.4]); -- doesn't work
SELECT mleast(VARIADIC ARRAY[10, -1, 5, 4.4]);
SELECT mleast(VARIADIC ARRAY[]::numeric[]); --在调用中指定VARIADIC也是向 variadic 函数传递空数组的唯一方式
SELECT mleast(); -- 不会匹配mleast(VARIADIC arr numeric[]),除非定义一个无参的同名函数
-- 带默认值
CREATE FUNCTION foo(a int, b int DEFAULT 2, c int = 3)
RETURNS int
LANGUAGE SQL
AS $$
SELECT $1 + $2 + $3;
$$;
-- 返回集合的SQL函数
CREATE TABLE foo (fooid int, foosubid int, fooname text);
INSERT INTO foo VALUES (1, 1, 'Joe');
INSERT INTO foo VALUES (1, 2, 'Ed');
INSERT INTO foo VALUES (2, 1, 'Mary');
CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$
SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
-- 通过输出参数定义的列来返回多行
-- 必须写成 RETURNS SETOF record,以表明该函数返回的是多行而不是单行。
-- 如果只有一个输出参数,则写该参数的类型,而不是 record
CREATE TABLE tab (y int, z int);
INSERT INTO tab VALUES (1, 2), (3, 4), (5, 6), (7, 8);
CREATE FUNCTION sum_n_product_with_tab (x int, OUT sum int, OUT product int)
RETURNS SETOF record
AS $$
SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;
--通过多次调用集合返回函数来构造查询结果通常很有用,其中每次调用的参数都来自表或子查询的连续行。
SELECT * FROM nodes;
name | parent
-----------+--------
Top |
Child1 | Top
Child2 | Top
Child3 | Top
SubChild1 | Child1
SubChild2 | Child1
-- 获取text的子节点
CREATE FUNCTION listchildren(text) RETURNS SETOF text AS $$
SELECT name FROM nodes WHERE parent = $1
$$ LANGUAGE SQL STABLE;
-- 对于nodes中的每个name,调用listchildren,并把结果join在一起
SELECT name, child FROM nodes, LATERAL listchildren(name) AS child;
-- 等价写法
SELECT name, listchildren(name) FROM nodes;
name | child
--------+-----------
Top | Child1
Top | Child2
Top | Child3
Child1 | SubChild1
Child1 | SubChild2
-- 返回TABLE的SQL函数:不允许把显式的OUT或者INOUT参数用于 RETURNS TABLE 记法 — 必须把所有输出列放在 TABLE 列表中
CREATE FUNCTION sum_n_product_with_tab (x int)
RETURNS TABLE(sum int, product int) AS $$
SELECT $1 + tab.y, $1 * tab.y FROM tab;
$$ LANGUAGE SQL;
-- 多态SQL函数
CREATE FUNCTION make_array(anyelement, anyelement) RETURNS anyarray AS $$
SELECT ARRAY[$1, $2];
$$ LANGUAGE SQL;
-- 指定比较规则
CREATE FUNCTION anyleast (VARIADIC anyarray) RETURNS anyelement AS $$
SELECT min($1[i] COLLATE "en_US") FROM generate_subscripts($1, 1) g(i);
$$ LANGUAGE SQL;
VOLATILE、STABLE或者IMMUTABLE。 如果 CREATE FUNCTION 命令没有指定一个分类,则默认是VOLATILE。易变性分类是给优化器的关于该函数行为的一种承诺:VOLATILE函数可以做任何事情,包括修改数据库。在使用相同的参数连续调用时,它能返回不同的结果。优化器不会对这类函数的行为做任何假定。对于在每一行都需要 volatile 函数值的查询,函数都会被重新求值。STABLE函数不能修改数据库并且被确保对一个语句中的所有行用给定的相同参数返回相同的结果。这种分类允许优化器把该函数的多个调用优化成一个调用。特别是,在一个索引扫描条件中使用包含这样一个函数的表达式是安全的(因为一次索引扫描只会计算一次比较值,而不是为每一行都计算一次,在一个索引扫描条件中不能使用 VOLATILE 函数)。IMMUTABLE函数不能修改数据库并且被确保用相同的参数永远返回相同的结果。这种分类允许优化器在一个查询用常量参数调用该函数时提前计算该函数。例如,一个 SELECT ... WHERE x = 2 + 2 这样的查询可以被简化为 SELECT ... WHERE x = 4,因为整数加法操作符底层的函数被标记为IMMUTABLE。VOLATILE, 这样对它的调用就不能被优化掉。甚至如果一个函数的值在一个查询中会变化,即使它没有副作用也需要被标记为VOLATILE。这样的示例有random()、currval()、 timeofday()等。current_timestamp家族的函数有资格 被标记为STABLE,因为它们的值在一个事务中不会改变CREATE EXTENSION 命令依赖于每个扩展都有一个控制文件,该文件的名称必须与扩展同名并带有 .control 后缀,且必须放在安装目录的 SHAREDIR/extension 目录中。此外还必须至少有一个 SQL 脚本文件,其命名模式为 extension--version.sql (例如,扩展 foo 的 1.0 版脚本文件为 foo--1.0.sql)。默认情况下,脚本文件也放在 SHAREDIR/extension 目录中;但控制文件可以为脚本文件指定不同的目录。extension_control_path 配置。extension--version.control。\echo 开头的行,扩展机制会忽略这些行(将其视为注释)。这一约定通常用于在脚本文件被直接交给 psql 而不是通过 CREATE EXTENSION 装载时抛出错误@extowner@,该字符串会被 替换为调用 CREATE EXTENSION 或 ALTER EXTENSION 的用户名称TRUNCATE 的触发器只能定义为语句级,不能定义为每行只要与事件触发器关联的事件在其定义所在数据库中发生,事件触发器就会被触发。 目前支持的事件有 login、 ddl_command_start、 ddl_command_end、 table_rewrite 和 sql_drop。
ddl_command_start 事件发生在 DDL 命令即将执行之前。 此处的 DDL 命令包括:CREATE, ALTER, DROP, COMMENT, GRANT, IMPORT FOREIGN SCHEMA, REINDEX, REFRESH MATERIALIZED VIEW, REVOKE, SECURITY LABEL。ddl_command_start 也会在 SELECT INTO 命令即将执行之前发生,因为它等价于 CREATE TABLE ASddl_command_end 事件发生在与 ddl_command_start 相同的一组命令执行之后。要获取这些 DDL 操作的更多细节,可在 ddl_command_end 事件触发器代码中使用集合返回函数 pg_event_trigger_ddl_commands()(见 Section 9.30)。注意,触发器是在这些动作已发生之后(但在事务提交之前)触发的,因此读取系统目录时,看到的已是变更后的状态。sql_drop 事件都发生在 ddl_command_end 事件触发器之前。请注意,除了显而易见的 DROP 命令外,某些 ALTER 命令也会触发 sql_drop 事件。要列出已删除的对象,可在 sql_drop 事件触发器代码中使用集合返回函数 pg_event_trigger_dropped_objects()(见 Section 9.30)。注意,触发器是在这些对象已经从系统目录中删除之后执行的,因此已无法再查找它们。table_rewrite 事件发生在表即将因 ALTER TABLE 和 ALTER TYPE 命令中的某些操作而被重写之前。虽然还有其他控制语句也可以重写表,例如 CLUSTER 和 VACUUM,但它们不会触发 table_rewrite 事件。要找出被重写表的 OID,请使用函数 pg_event_trigger_table_rewrite_oid();要找出重写的一个或多个原因, 可使用函数 pg_event_trigger_table_rewrite_reason()(见 Section 9.30)。事件触发器(与其他函数一样)不能在已中止的事务中执行。因此,如果 DDL 命令因错误而失败,任何关联的 ddl_command_end 触发器都不会执行。反之,如果 ddl_command_start 触发器因错误而失败,后续事件触发器都不会触发,也不会尝试执行该命令本身。类似地,如果 ddl_command_end 触发器因错误而失败,DDL 语句的效果将被回滚,就像包含该语句的事务在任何其他情况下中止时那样。
事件触发器通过命令 CREATE EVENT TRIGGER 创建。为了创建事件触发器,必须先创建一个返回类型为 event_trigger 的特殊函数。该函数不需要(也不能)返回值;这个返回类型仅用于表明该函数要作为事件触发器被调用。触发器定义也可以指定一个 WHEN 条件,这样,例如, ddl_command_start 触发器就可以只针对用户希望拦截的特定命令触发。
针对事件触发器本身的 DDL 命令不会受事件触发器影响。
CREATE FUNCTION somefunc(integer, text) RETURNS integer
AS 'function body text'
LANGUAGE plpgsql;
-- function body text
[ <<label>> ]
[ DECLARE
declarations ]
BEGIN
statements
END [ label ];
name [ CONSTANT ] type [ COLLATE collation_name ] [ NOT NULL ] [ { DEFAULT | := | = } expression ];name table.column%TYPE; name variable%TYPE; user_ids users.user_id%TYPE[];name table_name%ROWTYPE; name composite_type_name; 复合类型的变量称为行变量(或行类型变量)name RECORD; 记录变量与行类型变量类似,但没有预定义结构。它会在 SELECT 或 FOR 命令为其赋值时采用相应行的实际结构。记录变量的内部结构在每次被赋值时都可能变化。其后果是:在记录变量第一次被赋值之前,它没有任何子结构,任何试图访问其中字段的行为都会引发运行时错误。注意,RECORD 并不是真正的数据类型,它只是一个占位符。IF x < y THEN ... PL/pgSQL会通过向主 SQL 引擎送入如下查询 SELECT x < y 来计算该表达式。如Section 41.11.1中详细讨论的那样,在构造该SELECT命令时,PL/pgSQL变量名的每一次出现都会被替换成查询参数。这使得该SELECT的查询计划只需准备一次,然后就能在后续以不同变量值求值时重用。因此,表达式第一次被使用时,实际发生的事情本质上相当于执行了一条PREPARE命令 PREPARE statement_name(integer, integer) AS SELECT $1 < $2; 然后,在每次执行 IF 语句时,这条预备语句都会以当前PL/pgSQL变量值作为参数值被EXECUTE。IF count(*) > 0 FROM my_table THEN ... 因为 IF 和 THEN 之间的expression会被解析为 SELECT count(*) > 0 FROM my_table。该SELECT必须产生单个列值,而且不能返回多于一行。(如果没有返回任何行,则结果被视为 NULL。)variable { := | = } expression; 该表达式必须得到一个单一值SELECT、INSERT、UPDATE、DELETE、MERGE 以及某些包含其中之一的实用程序命令,比如EXPLAIN和CREATE TABLE ... AS SELECT。在这些命令中,命令文本中出现的任何PL/pgSQL变量名都会被查询参数替换,然后变量的当前值会在运行时作为参数值提供。EXECUTE它PERFORM query; 这会执行query并且丢弃掉结果。如果该查询产生至少一行,特殊变量FOUND会被设置为真,而如果它不产生行则设置为假BEGIN
SELECT * INTO STRICT myrec FROM emp WHERE empname = myname;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'employee % not found', myname;
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'employee % not unique', myname;
END;
CREATE FUNCTION get_userid(username text) RETURNS int
AS $$
#print_strict_params on
DECLARE
userid int;
BEGIN
SELECT users.userid INTO STRICT userid
FROM users WHERE users.username = get_userid.username;
RETURN userid;
END;
$$ LANGUAGE plpgsql;
ERROR: query returned no rows
DETAIL: parameters: username = 'nosuchuser'
CONTEXT: PL/pgSQL function get_userid(text) line 6 at SQL statement
EXECUTE command-string [ INTO [STRICT] target ] [ USING expression [, ... ] ]; USING子句中提供的值。EXECUTE 'SELECT count(*) FROM mytable WHERE inserted_by = $1 AND inserted <= $2' INTO c USING checked_user, checked_date;EXECUTE 'SELECT count(*) FROM ' || quote_ident(tabname) || ' WHERE inserted_by = $1 AND inserted <= $2' INTO c USING checked_user, checked_date;, 一种更干净的方法是使用format() 的 %I 规范,插入自带引号的表名或者列名:EXECUTE format('SELECT count(*) FROM %I WHERE inserted_by = $1 AND inserted <= $2', tabname) INTO c USING checked_user, checked_date;%L 相当于 quote_nullable(自动加引号,处理 NULL)。%I 相当于 quote_ident(用于处理表名或列名)。%s 简单的字符串替换。SELECT, INSERT, UPDATE, DELETE, MERGE以及包含其中一个的某些命令)。 在其他语句类型(通称为实用程序语句)中,即使它们只是数据值,您也必须以文本方式插入值。有好几种方法可以判断一条命令的效果。第一种方法是使用GET DIAGNOSTICS命令,其形式如下:GET [ CURRENT ] DIAGNOSTICS variable { = | := } item [ , ... ]; 这条命令允许检索系统状态指示符。CURRENT是一个噪声词(另见Section 41.6.8.1中的GET STACKED DIAGNOSTICS)。每个item是一个关键字, 它标识一个要被赋予给指定变量的状态值(变量应具有正确的数据类型来接收状态值)。
第二种确定命令效果的方法是检查名为FOUND的特殊变量,类型为boolean。在每次PL/pgSQL函数调用中,FOUND都是以 false 开头。 它由以下类型的语句设置:
SELECT INTO语句在分配行时将FOUND设置为true,如果没有返回行则设置为false。PERFORM语句在生成(和丢弃)一个或多个行时将FOUND设置为true, 如果没有生成行则设置为false。UPDATE、INSERT、DELETE和MERGE 语句在至少影响一行时将FOUND设置为true,如果没有影响行则设置为false。FETCH语句在返回行时将FOUND设置为true, 如果没有返回行则设置为false。MOVE语句在成功重新定位游标时将FOUND设置为true, 否则设置为false。FOR或FOREACH语句在迭代一次或多次时将 FOUND 设置为true,否则设置为false。当循环退出时,FOUND被设置为这种方式;在循环执行过程中,FOUND不会被循环语句修改,尽管它可能会被循环体内的其他语句执行修改。RETURN QUERY和RETURN QUERY EXECUTE语句在查询返回至少一行时将FOUND设置为true, 如果没有返回行则设置为false。FOUND的状态。 特别注意,EXECUTE会改变GET DIAGNOSTICS的输出,但不会改变FOUND。FOUND是每个PL/pgSQL函数的局部变量;任何对它的修改只影响当前的函数。
RETURN和RETURN NEXT。RETURN expression; 带有一个表达式的RETURN用于终止函数并把expression的值返回给调用者。这种形式被用于不返回集合的PL/pgSQL函数。RETURN NEXT 或 RETURN QUERY 命令指定,最后再用一个不带参数的 RETURN 命令表明函数已经执行完毕。在同一个集合返回函数中,RETURN NEXT 和 RETURN QUERY 可以自由混用,此时它们的结果会被串接起来。RETURN NEXT expression; -- 可用于标量和组合数据类型;对于组合结果类型,会返回完整的结果“表”
RETURN QUERY query; -- 会把查询执行结果追加到函数的结果集中
RETURN QUERY EXECUTE command-string [ USING expression [, ... ] ];
RETURN NEXT和RETURN QUERY实际上不会从函数中返回,它们简单地向函数的结果集中追加零或多行。然后会继续执行PL/pgSQL函数中的下一条语句。随着后继的RETURN NEXT和RETURN QUERY命令的执行,结果集就建立起来了。最后一个RETURN(应该没有参数)会导致控制退出该函数(或者你可以让控制到达函数的结尾)。RETURN NEXT。在每一次执行时,输出参数变量的当前值将被保存下来用于最终返回为结果的一行。注意为了创建一个带有输出参数的集合返回函数,在有多个输出参数时,你必须声明函数为返回SETOF record;或者如果只有一个类型为sometype的输出参数时,声明函数为SETOF sometype。IF ... THEN ... END IF
IF ... THEN ... ELSE ... END IF
IF ... THEN ... ELSIF ... THEN ... ELSE ... END IF --关键词ELSIF也可以被拼写成ELSEIF。
-- 简单式 CASE, search-expression被计算一次,找匹配的结果,如果没有找到匹配,则执行 ELSE statements;但如果没有 ELSE,则会抛出 CASE_NOT_FOUND 异常。
CASE search-expression
WHEN expression [, expression [ ... ]] THEN
statements
[ WHEN expression [, expression [ ... ]] THEN
statements
... ]
[ ELSE
statements ]
END CASE;
-- 搜索式 CASE,每个布尔表达式结算一遍,如果没有找到为真的结果,就执行ELSE statements;但如果不存在ELSE,则会抛出CASE_NOT_FOUND异常。
CASE
WHEN boolean-expression THEN
statements
[ WHEN boolean-expression THEN
statements
... ]
[ ELSE
statements ]
END CASE;
[ <<label>> ]
LOOP
statements
END LOOP [ label ];
EXIT [ label ] [ WHEN boolean-expression ];<<ablock>>
BEGIN
-- 一些计算
IF stocks > 100000 THEN
EXIT ablock; -- 导致从 BEGIN 块中退出
END IF;
-- 当stocks > 100000时,这里的计算将被跳过
END;
CONTINUE [ label ] [ WHEN boolean-expression ];[ <<label>> ]
WHILE boolean-expression LOOP
statements
END LOOP [ label ];
[ <<label>> ]
FOR name IN [ REVERSE ] expression .. expression [ BY expression ] LOOP
statements
END LOOP [ label ];
[ <<label>> ]
FOR target IN query LOOP
statements
END LOOP [ label ];
target 可以是记录变量、行变量,或者由标量变量组成的逗号分隔列表。query 产生的每一行都会依次赋给 target,并为每一行执行一次循环体。
FOR-IN-EXECUTE语句是在行上迭代的另一种方式:
[ <<label>> ]
FOR target IN EXECUTE text_expression [ USING expression [, ... ] ] LOOP
statements
END LOOP [ label ];
[ <<label>> ]
FOREACH target [ SLICE number ] IN ARRAY expression LOOP
statements
END LOOP [ label ];
CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
DECLARE
x int[];
BEGIN
FOREACH x SLICE 1 IN ARRAY $1
LOOP
RAISE NOTICE 'row = %', x;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);
NOTICE: row = {1,2,3}
NOTICE: row = {4,5,6}
NOTICE: row = {7,8,9}
NOTICE: row = {10,11,12}
[ <<label>> ]
[ DECLARE
declarations ]
BEGIN
statements
EXCEPTION
WHEN condition [ OR condition ... ] THEN
handler_statements
[ WHEN condition [ OR condition ... ] THEN
handler_statements
... ]
END;
WHEN division_by_zero THEN ...
WHEN SQLSTATE '22012' THEN ...
进入和退出一个包含EXCEPTION子句的块要比不包含EXCEPTION的块开销大的多。因此,只在必要的时候使用EXCEPTION。
GET STACKED DIAGNOSTICS命令。SQLSTATE包含了对应于被抛出异常的错误代码(可能的错误代码列表见Table A.1)。特殊变量SQLERRM包含与该异常相关的错误消息。这些变量在异常处理器外是未定义的。GET STACKED DIAGNOSTICS命令检索有关当前异常的信息,该命令的形式为:GET STACKED DIAGNOSTICS variable { = | := } item [ , ... ]; 每个item是一个关键词,它标识一个被赋予给指定变量(应该具有接收该值的正确数据类型)的状态值。| 错误诊断项名称 | 类型 | 描述 |
|---|---|---|
| RETURNED_SQLSTATE | text | 该异常的 SQLSTATE 错误代码 |
| COLUMN_NAME | text | 与异常相关的列名 |
| CONSTRAINT_NAME | text | 与异常相关的约束名 |
| PG_DATATYPE_NAME | text | 与异常相关的数据类型名 |
| MESSAGE_TEXT | text | 该异常的主要消息的文本 |
| TABLE_NAME | text | 与异常相关的表名 |
| SCHEMA_NAME | text | 与异常相关的模式名 |
| PG_EXCEPTION_DETAIL | text | 该异常的详细消息文本(如果有) |
| PG_EXCEPTION_HINT | text | 该异常的提示消息文本(如果有) |
| PG_EXCEPTION_CONTEXT | text | 描述产生异常时调用栈的文本行 |
GET DIAGNOSTICS(之前在Section 41.5.5中描述)命令检索有关当前执行状态的信息(反之上文讨论的GET STACKED DIAGNOSTICS命令会把有关执行状态的信息报告成一个以前的错误)。它的PG_CONTEXT状态项可用于标识当前执行位置。状态项PG_CONTEXT将返回一个文本字符串,其中有描述该调用栈的多行文本。第一行会指向当前函数以及当前正在执行GET DIAGNOSTICS的命令。第二行及其后的行表示调用栈中更上层的调用函数。refcursor。创建游标变量的一种方法是把它声明为一个类型为refcursor的变量。另外一种方法是使用游标声明语法,通常是:name [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR query; 为兼容 Oracle,可以用 IS 代替 FOR。如果指定了 SCROLL,游标就支持向后滚动;如果指定了 NO SCROLL,向后提取会被拒绝;如果两者都未指定,是否允许向后提取则取决于查询本身。如果指定了 arguments,它就是一个由 name datatype 对组成的逗号分隔列表,这些名字会在给定查询中被参数值替换。实际替换这些名字的值会在打开游标时提供。DECLARE
curs1 refcursor;
curs2 CURSOR FOR SELECT * FROM tenk1;
curs3 CURSOR (key integer) FOR SELECT * FROM tenk1 WHERE unique1 = key;
OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR query; OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;OPEN unbound_cursorvar [ [ NO ] SCROLL ] FOR EXECUTE query_string [ USING expression [, ... ] ];OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;OPEN bound_cursorvar [ ( [ argument_name { := | => } ] argument_value [, ...] ) ];OPEN curs2;
OPEN curs3(42);
OPEN curs3(key := 42);
OPEN curs3(key => 42);
DECLARE
key integer;
curs4 CURSOR FOR SELECT * FROM tenk1 WHERE unique1 = key;
BEGIN
key := 42;
OPEN curs4;
FETCH [ direction { FROM | IN } ] cursor INTO target; FETCH从游标中按指定方向检索下一行到目标中,目标可以是一个行变量、记录变量或者逗号分隔的简单变量列表,就像SELECT INTO一样。如果没有合适的行,目标会被设置为 NULL。与SELECT INTO一样,可以检查特殊变量FOUND来看是否获得了一行。若未获得行,则游标会根据移动方向定位到最后一行之后或第一行之前。direction子句可以是 SQL FETCH命令中允许的任何变体,除了那些能够取得多于一行的。即它可以是 NEXT、 PRIOR、 FIRST、 LAST、 ABSOLUTE count、 RELATIVE count、 FORWARD或者 BACKWARD。 省略direction和指定NEXT是一样的。在使用count的形式中,count可以是任意的整数值表达式(与SQL命令FETCH不一样,FETCH仅允许整数常量)。除非游标被使用SCROLL选项声明或打开,否则要求反向移动的direction值很可能会失败。cursor必须是一个引用已打开游标 portal 的refcursor变量名。FETCH curs1 INTO rowvar;
FETCH curs2 INTO foo, bar, baz;
FETCH LAST FROM curs3 INTO x, y;
FETCH RELATIVE -2 FROM curs4 INTO x;
MOVE [ direction { FROM | IN } ] cursor;MOVE curs1;
MOVE LAST FROM curs3;
MOVE RELATIVE -2 FROM curs4;
MOVE FORWARD 2 FROM curs4;
UPDATE table SET ... WHERE CURRENT OF cursor; DELETE FROM table WHERE CURRENT OF cursor;UPDATE foo SET dataval = myval WHERE CURRENT OF curs1;CLOSE cursor;-- 调用者提供游标名字的方法
CREATE TABLE test (col text);
INSERT INTO test VALUES ('123');
CREATE FUNCTION reffunc(refcursor) RETURNS refcursor AS '
BEGIN
OPEN $1 FOR SELECT col FROM test;
RETURN $1;
END;
' LANGUAGE plpgsql;
BEGIN;
SELECT reffunc('funccursor');
FETCH ALL IN funccursor;
COMMIT;
[ <<label>> ]
FOR recordvar IN bound_cursorvar [ ( [ argument_name { := | => } ] argument_value [, ...] ) ] LOOP
statements
END LOOP [ label ];
recordvar会被自动定义为record类型,并且只存在于循环内部(循环中该变量名任何已有定义都会被忽略)。每一个由游标返回的行都会被陆续地赋值给这个记录变量并且执行循环体。COMMIT和ROLLBACK结束事务。事务通过这些命令结束后,会自动开始一个新事务,因此不存在单独的START TRANSACTION命令(注意BEGIN和END在 PL/pgSQL 中含义不同)。CREATE PROCEDURE transaction_test1()
LANGUAGE plpgsql
AS $$
BEGIN
FOR i IN 0..9 LOOP
INSERT INTO test1 (a) VALUES (i);
IF i % 2 = 0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
END LOOP;
END;
$$;
CALL transaction_test1();
新事务开始时具有默认事务特征,如事务隔离级别。在循环中提交事务的情况下,可能需要以与前一个事务相同的特征来自动启动新事务。 命令COMMIT AND CHAIN和ROLLBACK AND CHAIN可以完成此操作。
只有在从顶层调用的CALL或DO中才能进行事务控制,在没有任何其他中间命令的嵌套CALL或DO调用中也能进行事务控制。例如,如果调用栈是CALL proc1() → CALL proc2() → CALL proc3(),那么第二个和第三个过程可以执行事务控制动作。但是如果调用栈是CALL proc1() → SELECT func2() → CALL proc3(),则最后一个过程不能做事务控制,因为中间有SELECT。
PL/pgSQL 不支持保存点(SAVEPOINT/ROLLBACK TO SAVEPOINT/RELEASE SAVEPOINT)。保存点的典型用法可以用带异常处理器的代码块替代(见Section 41.6.8)。在内部,实现为带异常处理器的代码块会形成一个子事务,这意味着在这类代码块内部不能结束事务。
对于游标循环,还有一些特殊注意事项。请看下面这个示例:
CREATE PROCEDURE transaction_test2()
LANGUAGE plpgsql
AS $$
DECLARE
r RECORD;
BEGIN
FOR r IN SELECT * FROM test2 ORDER BY x LOOP
INSERT INTO test1 (a) VALUES (r.x);
COMMIT;
END LOOP;
END;
$$;
CALL transaction_test2();
UPDATE ... RETURNING)驱动的游标循环中,不允许使用事务命令。RAISE语句报告消息以及抛出错误。RAISE [ level ] 'format' [, expression [, ... ]] [ USING option { = | := } expression [, ... ] ];
RAISE [ level ] condition_name [ USING option { = | := } expression [, ... ] ];
RAISE [ level ] SQLSTATE 'sqlstate' [ USING option { = | := } expression [, ... ] ];
RAISE [ level ] USING option { = | := } expression [, ... ];
RAISE ;
level选项指定了错误的严重性。允许的级别有DEBUG、LOG、INFO、NOTICE, WARNING以及EXCEPTION,默认级别是EXCEPTION。EXCEPTION会抛出一个错误(通常会中止当前事务)。其他级别仅仅是产生不同优先级的消息。不管一个特定优先级的消息是被报告给客户端、还是写到服务器日志、亦或是二者同时都做,这都由log_min_messages和client_min_messages配置变量控制。详见Chapter 19。RAISE NOTICE 'Calling cs_create_job(%)', v_job_id; -- format
RAISE division_by_zero; -- condition_name
RAISE WARNING SQLSTATE '22012'; -- SQLSTATE
option = expression项的USING,为错误报告附加额外信息。每一个expression可以是任意字符串值的表达式。允许的option关键词是:-- 用给定的错误消息和提示中止事务:
RAISE EXCEPTION 'Nonexistent ID --> %', user_id USING HINT = 'Please check your user ID';
-- 这两个示例展示了设置 SQLSTATE 的两种等价的方法:
RAISE 'Duplicate user ID: %', user_id USING ERRCODE = 'unique_violation';
RAISE 'Duplicate user ID: %', user_id USING ERRCODE = '23505';
-- 另一种得到前面示例相同结果的方式是:
RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
ASSERT condition [ , message ];ASSERT_FAILURE异常(如果在计算 condition时发生错误, 它会被报告为一个普通错误)。plpgsql.check_asserts可以启用或者禁用断言测试, 这个参数接受布尔值且默认为on。如果这个参数为off, 则ASSERT语句什么也不做。CREATE FUNCTION命令创建,它被声明为一个没有参数并且返回类型为trigger(对于数据更改触发器)或者event_trigger(对于数据库事件触发器)的函数。名为PG_something的特殊局部变量将被自动创建用以描述触发该调用的条件。一个数据更改触发器被声明为一个没有参数并且返回类型为trigger的函数。注意,如下所述,即便该函数准备接收一些在CREATE TRIGGER中指定的参数, 这类参数通过TG_ARGV传递,也必须把它声明为没有参数。
当一个PL/pgSQL函数当做触发器调用时,在顶层块会自动创建一些特殊变量。它们是:
NEW record 行级触发器中用于 INSERT/UPDATE 操作的新数据行。在语句级触发器和 DELETE 操作中,该变量为 null。OLD record 行级触发器中用于 UPDATE/DELETE 操作的旧数据行。在语句级触发器和 INSERT 操作中,该变量为 null。TG_NAME name 被触发的触发器名称。TG_WHEN text 根据触发器定义,其值为 BEFORE、AFTER 或 INSTEAD OF。TG_LEVEL text 根据触发器定义,其值为 ROW 或 STATEMENT。TG_OP text 触发器对应的操作:INSERT、UPDATE、DELETE 或 TRUNCATE。TG_RELID oid(引用 pg_class.oid) 导致触发器调用的表的对象 ID。TG_RELNAME name 导致触发器调用的表名。该变量已弃用,未来版本可能移除;请改用 TG_TABLE_NAME。TG_TABLE_NAME name 导致触发器调用的表名。TG_TABLE_SCHEMA name 导致触发器调用的表所在模式名。TG_NARGS integer CREATE TRIGGER 语句中传给触发器函数的参数个数。TG_ARGV text[] 来自 CREATE TRIGGER 语句的参数。索引从 0 开始;非法索引(小于 0 或大于等于 tg_nargs)返回 null。一个触发器函数必须返回NULL或者是一个与触发器为之引发的表结构完全相同的记录/行值。
BEFORE 行级触发器可以返回 null,以通知触发器管理器跳过该行后续的操作(也就是说,不再触发后续触发器,并且不会对该行执行INSERT/UPDATE/DELETE)。如果返回非 null 值,则操作会继续进行,并使用该行值。返回一个不同于原始NEW的行值会改变即将插入或更新的行。因此,如果触发器函数希望触发动作正常成功而不修改行值,就必须返回NEW(或与之相等的值)。若要修改将被存储的行,可以直接替换NEW中的单个值并返回修改后的NEW,或者构造一个完整的新记录/行来返回。
用于DELETE的 before 触发器,返回值本身没有直接效果,但必须为非 null 才能让触发器动作继续。注意,在DELETE触发器中NEW为 null,因此通常没有理由返回它。在DELETE触发器中,常见写法是返回OLD。
INSTEAD OF触发器(总是行级触发器,并且可能只被用于视图)能够返回空来表示它们没有执行任何更新,并且对该行剩余的操作可以被跳过(即后续的触发器不会被引发,并且该行不会被计入外围INSERT/UPDATE/DELETE的行影响状态中)。否则一个非空值应该被返回用以表示该触发器执行了所请求的操作。对于INSERT 和UPDATE操作,返回值应该是NEW,触发器函数可能对它进行了修改来支持INSERT RETURNING和UPDATE RETURNING(这也将影响被传递给任何后续触发器的行值,或者被传递给带有ON CONFLICT DO UPDATE的INSERT语句中一个特殊的EXCLUDED别名引用)。对于DELETE操作,返回值应该是OLD。
一个AFTER行级触发器或一个BEFORE或AFTER语句级触发器的返回值总是会被忽略,它可能也是空。不过,任何这些类型的触发器可能仍会通过抛出一个错误来中止整个操作。
event_trigger。TG_EVENT text 触发器被触发时对应的事件。TG_TAG text 触发该触发器的命令标签。#variable_conflict error
#variable_conflict use_variable
#variable_conflict use_column
--这些命令只影响它们所属的函数,并且会覆盖plpgsql.variable_conflict的设置。一个示例是:
CREATE FUNCTION stamp_user(id int, comment text) RETURNS void AS $$
#variable_conflict use_variable
DECLARE
curtime timestamp := now();
BEGIN
UPDATE users SET last_modified = curtime, comment = comment
WHERE users.id = id;
END;
$$ LANGUAGE plpgsql;