PostgreSQL超全指南

文章目录

简介

PostgreSQL 号称世界上最先进的开源关系型数据库,念作 post-gress-Q-L,简称 Postgres 或 pg

PostgreSQL 源于 1977 年加洲大学伯克利分校的 Ingres 项目,1996 年发布第一个版本,至今已超过 35 年

PostgreSQL已连续两年超过MySQL成为最受欢迎的数据库

使用 PostgreSQL 的公司有:苹果、Instagram、Reddit、IMDb、Skype、NTT、亚马逊、NASA

每年有全球性技术大会,如 Postgres Conference,国内有 PostgreSQL 中国技术大会(至今已举办13届)

官方读法

特点

  • 最接近标准SQL且扩展了功能
  • 稳定可靠:数据零丢失
  • 开源省钱:基于BSD协议,在使用和二开上没有限制
  • 支持广泛:支持大量主流语言,如用Python写存储过程
    社区活跃:每三个月推出一个补丁版本,官网首页有BUG提交入口,各种博客
  • 强大的功能集,如地理空间数据库扩展 PostGIS
  • 支持主流操作系统,Linux、macOS、Windows、BSD、Solaris

主要功能

  • 数据类型
    • 基本数据类型:Integer, Numeric, String, Boolean
    • 结构数据类型:Date/Time, Array, Range / Multirange, UUID
    • 文档数据类型:JSON/JSONB, XML, Key-value (Hstore)
    • 几何数据类型:Point, Line, Circle, Polygon
    • 自定义数据类型:Composite, Custom Types
  • 数据完整性
    • UNIQUE, NOT NULL
    • 主键
    • 外键
    • 排除约束
    • 显式锁、咨询锁
  • 并发性、性能
    • 索引:B树、多列、表达式、部分
    • 高级索引:GiST, SP-Gist, KNN Gist, GIN, BRIN, Covering indexes, Bloom filters
    • 复杂的查询规划器/优化器,仅索引扫描,多列统计
    • 事务,嵌套事务
    • 多版本并发控制(MVCC)
    • 读取查询的并行化和B树索引的构建
    • 表分区
    • SQL标准中定义的所有事务隔离级别,包括序列化
    • 表达式即时编译(JIT)
  • 可靠性、灾难恢复
    • 预写日志(WAL)
    • 复制:异步、同步、逻辑
    • 时间点恢复 (PITR)、主动待机
    • 表空间
  • 安全性
    • 身份验证:GSSAPI、SSPI、LDAP、SCRAM-SHA-256、证书等
    • 强大的访问控制系统
    • 列级和行级安全
    • 多因素身份验证
  • 可扩展性
    • 存储函数和过程
    • 过程语言:PL/pgSQL、Perl、Python、Tcl。还有其他通过扩展可用的语言,例如 Java、JavaScript (V8)、R、Lua、Rust
    • SQL/JSON构造函数、查询函数、路径表达式和 JSON_TABLE
    • 外部数据包装器:使用标准SQL接口连接到其他数据库或流
    • 可定制的表存储接口
    • 更多插件,如 PostGIS
  • 国际化,文本搜索
    • 支持国际字符集,例如通过 ICU 排序
    • 不区分大小写和重音的排序规则
    • 全文搜索

安装

  1. 根据操作系统下载 PostgreSQL,本人使用的版本是 17(2024-09-26 发布)
  2. 选择安装的组件

pgAdmin4:PostgreSQL 的数据库管理软件,类似 Navicat

  1. 设置密码,本地开发可设 123456
  2. 端口号默认为 5432
  3. 取消勾选

Stack Builder 是一个图形化工具,用于简化在 PostgreSQL 安装环境中添加额外功能组件,如地理空间数据库扩展 PostGIS

  1. 配置环境变量 Path:C:\Program Files\PostgreSQL\17\bin

  2. 命令行 psql -d postgres -U postgres,-d 指定数据库,-U 指定用户名

  3. 打开 SQL Shell (psql)

  4. 打开 pgAmin 4 → Servers → PostgreSQL 17

初试

进入 SQL Shell (psql)

创建数据库

sql 复制代码
CREATE DATABASE shop;

连接数据库

sql 复制代码
\c shop;

创建表

sql 复制代码
CREATE TABLE product (
	product_id CHAR(4) NOT NULL,
	product_name VARCHAR(100) NOT NULL,
	product_type VARCHAR(32) NOT NULL,
	sale_price INTEGER,
	purchase_price INTEGER,
	regist_date date,
	PRIMARY KEY (product_id)
);

COMMENT ON COLUMN product.product_id IS '商品编号';
COMMENT ON COLUMN product.product_name IS '商品名称';
COMMENT ON COLUMN product.product_type IS '商品种类';
COMMENT ON COLUMN product.sale_price IS '销售单价';
COMMENT ON COLUMN product.purchase_price IS '进货单价';
COMMENT ON COLUMN product.regist_date IS '登记日期';

表定义的更新(添加列)

sql 复制代码
ALTER TABLE product ADD COLUMN product_name_pinyin VARCHAR(100);

表定义的更新(删除列)

sql 复制代码
ALTER TABLE product DROP COLUMN product_name_pinyin;

插入数据

sql 复制代码
BEGIN TRANSACTION;
INSERT INTO product VALUES ('0001', 'T恤' ,'衣服', 1000, 500, '2009-09-20');
INSERT INTO product VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11');
INSERT INTO product VALUES ('0003', '运动T恤', '衣服', 4000, 2800, NULL);
INSERT INTO product VALUES ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20');
INSERT INTO product VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
INSERT INTO product VALUES ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20');
INSERT INTO product VALUES ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28');
INSERT INTO product VALUES ('0008', '圆珠笔', '办公用品', 100, NULL, '2009-11-11');
COMMIT;

查询全部列

sql 复制代码
SELECT * FROM product;

查询某些列,设别名

sql 复制代码
SELECT product_id AS id, product_name AS name, purchase_price AS price FROM product;

查询商品种类(删除重复行)

sql 复制代码
SELECT DISTINCT product_type FROM product;

查询商品种类为衣服的商品名称

sql 复制代码
SELECT product_name, product_type FROM product WHERE product_type='衣服';

大小写

  • 建库时自动转成小写,除非用双引号
  • 建表时自动转成小写,除非用双引号
  • 建字段时自动转成小写,除非用双引号

注意:执行SQL要加分号!!!

注释

sql 复制代码
-- 单行注释

/*多行注释
多行注释
多行注释
多行注释
多行注释
多行注释*/

列出

数据库

sql 复制代码
\l

SELECT datname FROM pg_database;

数据表

sql 复制代码
\dt

删除库或表 DROP

数据库

sql 复制代码
DROP DATABASE test;

数据表

sql 复制代码
DROP TABLE product;

变更表名 ALTER

sql 复制代码
ALTER TABLE poduct RENAME TO product;

算法运算符

所有包含 NULL 的计算,结果都是 NULL

sql 复制代码
SELECT NULL+5;

比较运算符

等于 =,不等于 !=

大于 >,小于 <

大于等于 >=,小于等于 <=

2 和 '2' 不一样,字符串类型的比较按照字典顺序

不能对 NULL 使用比较运算符,要用 IS NULLIS NOT NULL

sql 复制代码
-- 查不出数据
SELECT * FROM product WHERE purchase_price=NULL;
-- 能查到数据
SELECT * FROM product WHERE purchase_price IS NULL;

逻辑运算符

与:AND

或:OR

非:NOT

AND 优先于 OR,可使用括号提高优先级

聚合函数

统计:COUNT

求和:SUM

平均值:AVG

最大值:MAX

最小值:MIN

sql 复制代码
-- 全部行数
SELECT COUNT(*) FROM product;
-- 进货单价的非空行数
SELECT COUNT(purchase_price) FROM product;
-- 产品种类数
SELECT COUNT(DISTINCT product_type) FROM product;
-- 销售单价的求和
SELECT SUM(sale_price) FROM product;
-- 销售单价的平均值
SELECT AVG(sale_price) FROM product;
-- 销售单价的最大值
SELECT MAX(sale_price) FROM product;
-- 销售单价的最小值
SELECT MIN(sale_price) FROM product;

分组 GROUP BY

GROUP BY,要写在 FROMWHERE 之后

对分组后的结果指定条件用 HAVING,要写在 GROUP BY 之后

sql 复制代码
-- 不同产品种类的数量
SELECT product_type, COUNT(*)
FROM product
GROUP BY product_type;

-- 产品数为2的产品种类
SELECT product_type, COUNT(*)
FROM product
GROUP BY product_type
HAVING COUNT(*)=2;

GROUP BY 常见错误

  1. 在 SELECT 中写了非分组列的列
  2. 使用了列的别名
  3. 在 WHERE 子句中使用聚合函数,应该在 HAVING 中使用

WHERE 的执行速度比 HAVING 快

排序 ORDER BY

ORDER BY

升序:ASC(默认)

降序:DESC

排序键中包含 NULL 时,会在末尾汇总

sql 复制代码
-- 按销售单价从低到高排(升序)
SELECT * FROM product ORDER BY sale_price;

-- 按销售单价从高到低排(降序)
SELECT * FROM product ORDER BY sale_price DESC;

-- 按销售单价和商品编号从低到高排(升序)
SELECT * FROM product ORDER BY sale_price, product_id;

-- NULL会在末尾汇总
SELECT * FROM product ORDER BY purchase_price;

书写顺序

查询:SELECTFROMWHEREGROUP BYHAVINGORDER BY

插入数据 INSERT

sql 复制代码
-- 建表
CREATE TABLE productins (
    product_id CHAR(4) NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    product_type VARCHAR(32) NOT NULL,
    sale_price INTEGER DEFAULT 0,
    purchase_price INTEGER,
    regist_date date,
    PRIMARY KEY (product_id)
);
sql 复制代码
-- 使用列清单
INSERT INTO productins (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');

-- 使用值清单,需要保证列数一致
INSERT INTO productins VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');

-- 插入多行
INSERT INTO productins VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11'),
                              ('0003', '运动T恤', '衣服', 4000, 2800, NULL),
                              ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20');

-- 显式插入默认值DEFAULT
INSERT INTO productins (product_id, product_name, product_type, sale_price, purchase_price, regist_date) VALUES ('0007', '擦菜板', '厨房用具', DEFAULT, 790, '2009-04-28');

-- 隐式插入默认值
INSERT INTO productins (product_id, product_name, product_type, purchase_price, regist_date) VALUES ('0007', '擦菜板', '厨房用具', 790, '2009-04-28');

从其他表中复制数据

sql 复制代码
CREATE TABLE productcopy (
    product_id CHAR(4) NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    product_type VARCHAR(32) NOT NULL,
    sale_price INTEGER,
    purchase_price INTEGER,
    regist_date date,
    PRIMARY KEY (product_id)
);

-- 将商品表中的数据复制到复制商品表中
INSERT INTO productcopy (product_id, product_name, product_type, sale_price, purchase_price, regist_date)
SELECT product_id, product_name, product_type, sale_price, purchase_price, regist_date
  FROM product;
  
-- 确认复制行
SELECT * FROM ProductCopy;

删除数据 DELETE

sql 复制代码
-- 清空表(表还在)
DELETE FROM product;

-- 删除销售单价大于4000的数据
DELETE FROM product
WHERE sale_price>4000;

TRUNCATE 可以快速清空表中所有数据,但无法做过滤

更新数据 UPDATE

sql 复制代码
-- 将登记日期全部更新为2019-10-10
UPDATE product
SET regist_date='2009-10-10';

-- 将厨房用具的销售单价更新为原来的10倍
UPDATE product
SET sale_price=sale_price*10
WHERE product_type='厨房用具';

-- 多列更新
UPDATE product
SET sale_price=sale_price*10,
    purchase_price=purchase_price/2
WHERE product_type='厨房用具';

事务

  • 事务(transaction)是需要在同一个处理单元中执行的一系列更新处理的集合。
  • 通过事务,可以对数据库中的数据更新处理的提交和取消进行管理。
  • 事务处理的终止命令有提交 COMMIT 和回滚 ROLLBACK
  • DBMS 的事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),统称 ACID。
sql 复制代码
BEGIN transaction;

-- 运动T恤的销售单价下调1000
UPDATE product
SET sale_price=sale_price-1000
WHERE product_name='运动T恤';

-- T恤的销售单价上浮1000日元
UPDATE product
SET sale_price=sale_price+1000
WHERE product_name='T恤';

-- 查看更新后的结果
SELECT * FROM product WHERE product_name='运动T恤';
SELECT * FROM product WHERE product_name='T恤';

-- 失败则回滚事务,撤销更新操作
ROLLBACK;

-- 成功则提交事务,使更新永久生效
COMMIT;

ACID 特性

  • 原子性(Atomicity)
    要么全部执行,要么全部不执行。如,事务会保证运动T恤价格下降,T恤的价格也下降。
  • 一致性(Consistency)
    处理满足数据库约束。如,设置了NOT NULL的列不会被更新为NULL。
  • 隔离性(Isolation)
    不同事务之间互不干扰。
  • 持久性(Durability)
    事务结束后,数据状态会被保存。

视图

  • 表保存的是实际的数据,视图保存的是 SELECT 语句(视图本身不保存数据)
  • 可以将常用的 SELECT 语句做成视图使用
  • 尽量避免在视图的基础上创建视图,因为多重视图会降低 SQL 性能,最好使用单一视图
  • 视图不能使用 ORDER BY
sql 复制代码
-- 创建视图
CREATE VIEW productsum (product_type, cnt_product) AS
SELECT product_type, COUNT(*)
FROM product
GROUP BY product_type;
sql 复制代码
-- 使用视图
SELECT product_type, cnt_product FROM productsum;
sql 复制代码
-- 删除视图
DROP VIEW productsum;
-- 删除关联视图
DROP VIEW productsum CASCADE;

子查询

  • 子查询可以理解为一次性视图
  • 子查询需要命名
  • 尽量避免使用多层嵌套的子查询
sql 复制代码
CREATE VIEW productsum (product_type, cnt_product) AS
SELECT product_type, COUNT(*)
FROM product
GROUP BY product_type;
SELECT product_type, cnt_product FROM productsum;

-- 两种方法效果相同
SELECT product_type, cnt_product
FROM (SELECT product_type, COUNT(*) AS cnt_product
      FROM product
      GROUP BY product_type) AS productsum

标量子查询

标量子查询只返回某个值

sql 复制代码
-- 报错
SELECT * FROM product
WHERE sale_price > AVG(sale_price);
-- 查询销售单价高于均值的产品
SELECT * FROM product
WHERE sale_price > (SELECT AVG(sale_price) FROM product);

关联子查询

  • 关联子查询在细分的组内进行比较时使用
  • 关联子查询和 GROUP BY 子句一样,可以对表中的数据进行切分
  • 关联子查询的结合条件如果未出现在子查询之中会发生错误
  • 结合条件一定要写在子查询中

按商品种类分组,并查询大于其种类平均销售单价的商品

sql 复制代码
SELECT * FROM product AS p1
WHERE
    sale_price>(
        SELECT AVG(sale_price)
        FROM product AS p2
        WHERE p1.product_type=p2.product_type
        GROUP BY product_type
    );

函数

算术函数 功能
ABS 绝对值
MOD 求余
ROUND 四舍五入
sql 复制代码
SELECT ABS(-100);
-- 100
SELECT MOD(7, 3);
-- 1
SELECT ROUND(5.555, 2);
-- 5.56
字符串函数 功能
`
LENGTH 字符串长度
LOWER 小写转换
UPPER 大写转换
REPLACE 字符串替换
SUBSTRING 字符串截取
sql 复制代码
SELECT 'Hello '||'World'||'!';
-- Hello World!
SELECT LENGTH('Hello');
-- 5
SELECT LOWER('Hello'), UPPER('Hello');
-- hello HELLO
SELECT REPLACE('Hello', 'ell', 'ooo');
-- Hoooo
SELECT SUBSTRING('Hello' FROM 3 FOR 2);
-- ll
日期函数 功能
CURRENT_DATE 当前日期
CURRENT_TIME 当前时间
CURRENT_TIMESTAMP 当前时间和日期
EXTRACT 截取日期元素
sql 复制代码
SELECT CURRENT_DATE;
-- 2025-01-09
SELECT CURRENT_TIME;
-- 17:41:19.841152+08:00
SELECT CURRENT_TIMESTAMP;
-- 2025-01-09 17:41:20.750016+08
SELECT EXTRACT(YEAR FROM CURRENT_TIMESTAMP), 
       EXTRACT(MONTH FROM CURRENT_TIMESTAMP),
       EXTRACT(DAY FROM CURRENT_TIMESTAMP),
       EXTRACT(HOUR FROM CURRENT_TIMESTAMP),
       EXTRACT(MINUTE FROM CURRENT_TIMESTAMP),
       EXTRACT(SECOND FROM CURRENT_TIMESTAMP);
-- 2025 1 9 17 41 20.750016
转换函数 功能
CAST 类型转换
COALESCE 将 NULL 转换为其他值
sql 复制代码
SELECT CAST('0001' AS INTEGER);
-- 1
SELECT CAST('2025-01-09' AS DATE);
-- 2025-01-09
SELECT COALESCE(NULL, 1);
-- 1

谓词

  • LIKE:字符串部分一致
    • 前方一致
    • 中间一致
    • 后方一致
    • %:长度大于等于0的任意字符串
    • _:任意1个字符

以下结果均为 true

sql 复制代码
-- 前方一致
SELECT 'dddabc' LIKE 'ddd%';

-- 中间一致
SELECT 'abcddd' LIKE '%ddd%',
       'dddabc' LIKE '%ddd%',
       'abdddc' LIKE '%ddd%';

-- 后方一致
SELECT 'abcddd' LIKE 'ddd%';

-- abc+任意3个字符
SELECT 'abcddd' LIKE 'abc___';
  • BETWEEN:范围查询
sql 复制代码
-- 销售单价100~1000的商品(包含)
SELECT *
FROM product
WHERE sale_price BETWEEN 100 AND 1000;

-- 销售单价100~1000的商品(不包含)
SELECT *
FROM product
WHERE sale_price>100 AND sale_price<1000;
  • IS NULLIS NOT NULL:判断是否为 NULL(不能用 =)
sql 复制代码
SELECT *
FROM product
WHERE purchase_price IS NULL;
  • INNOT IN:使用子查询作为其参数
sql 复制代码
CREATE TABLE shopproduct (
    shop_id CHAR(4) NOT NULL,
    shop_name VARCHAR(200) NOT NULL,
    product_id CHAR(4) NOT NULL,
    quantity INTEGER NOT NULL,
    PRIMARY KEY (shop_id, product_id)
);

BEGIN TRANSACTION;
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000A',	'东京', '0001', 30);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000A',	'东京', '0002', 50);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000A',	'东京', '0003', 15);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000B',	'名古屋', '0002', 30);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000B',	'名古屋', '0003', 120);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000B',	'名古屋', '0004', 20);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000B',	'名古屋', '0006', 10);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000B',	'名古屋', '0007', 40);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000C',	'大阪', '0003', 20);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000C',	'大阪', '0004', 50);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000C',	'大阪', '0006', 90);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000C',	'大阪', '0007', 70);
INSERT INTO ShopProduct (shop_id, shop_name, product_id, quantity) VALUES ('000D',	'福冈', '0001', 100);
COMMIT;
sql 复制代码
SELECT * 
FROM product 
WHERE product_id IN ('0003', '0004', '0006', '0007');

SELECT *
FROM product
WHERE product_id IN (
        SELECT product_id
        FROM shopproduct
        WHERE shop_id='000C'
    );
  • EXISTS:较难理解,实际上可以用 INNOT IN 替代
sql 复制代码
SELECT *
FROM product AS p
WHERE EXISTS (
        SELECT *
        FROM shopproduct AS sp
        WHERE sp.shop_id='000C' 
        AND sp.product_id=p.product_id
    );

CASE表达式

  • 相当于编程中的条件分支
  • CASE 表达式中的 ELSE 可以省略,但为了可读性最好别省略
  • CASE 表达式中的 END 不能省略
sql 复制代码
SELECT product_name,
    CASE
        WHEN product_type='衣服' THEN 'A:'||product_type
        WHEN product_type='办公用品' THEN 'B:'||product_type
        WHEN product_type='厨房用具' THEN 'C:'||product_type
        ELSE NULL
    END AS abc_product_type
FROM product;

行、列转换

sql 复制代码
-- 行的形式
SELECT product_type, SUM(sale_price)
FROM product
GROUP BY product_type;
sql 复制代码
--列的形式
SELECT
    SUM(CASE WHEN product_type='衣服'
        THEN sale_price
        ELSE 0
        END
    ) AS sum_price_clothes,
    SUM(CASE WHEN product_type='办公用品'
        THEN sale_price
        ELSE 0
        END
    ) AS sum_price_office,
    SUM(CASE WHEN product_type='厨房用具' 
        THEN sale_price
        ELSE 0
        END
    ) AS sum_price_kitchen
FROM product;

集合运算

  • 集合运算以行为单位进行操作
  • 集合运算符有:并集 UNION、交集 INTERSECT、差集 EXCEPT
  • 集合运算符可以去除重复行
  • 保留重复行使用 ALL

数据准备

sql 复制代码
CREATE TABLE product2 (
    product_id CHAR(4) NOT NULL,
    product_name VARCHAR(100) NOT NULL,
    product_type VARCHAR(32) NOT NULL,
    sale_price INTEGER,
    purchase_price INTEGER,
    regist_date date,
    PRIMARY KEY (product_id)
);

BEGIN TRANSACTION;
INSERT INTO product2 VALUES ('0001', 'T恤', '衣服', 1000, 500, '2009-09-20');
INSERT INTO product2 VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11');
INSERT INTO product2 VALUES ('0003', '运动T恤', '衣服', 4000, 2800, NULL);
INSERT INTO product2 VALUES ('0009', '手套', '衣服', 800, 500, NULL);
INSERT INTO product2 VALUES ('0010', '水壶', '厨房用具', 2000, 1700, '2009-09-20');
COMMIT;

并集 UNION

加法运算(并集)

  • 列数必须相同
  • 类型必须一致(可用 CAST 转换类型)
  • ORDER BY 只能在最后使用一次
sql 复制代码
-- 两表相加(去重)
SELECT product_id, product_name
FROM product
UNION
SELECT product_id, product_name
FROM product2
ORDER BY product_id;

-- 两表相加(不去重)
SELECT product_id, product_name
FROM product
UNION ALL
SELECT product_id, product_name
FROM product2
ORDER BY product_id;

交集 INTERSECT

选取共有的部分

sql 复制代码
SELECT product_id, product_name
FROM product
INTERSECT
SELECT product_id, product_name
FROM product2
ORDER BY product_id;

差集EXCEPT

sql 复制代码
SELECT product_id, product_name
FROM product
EXCEPT
SELECT product_id, product_name
FROM product2
ORDER BY product_id;

联结

  • 以列为单位进行操作
  • 内联结 INNER JOIN ,最常用,INNER 可以省略
  • 外联结 OUTER JOIN
  • 别名不是必须的
  • ON 必须写在 FROMWHERE 之间

两张表包含的列

product shopproduct
商品编号
商品名称
商品种类
销售单价
进货单价
登记日期
商店编号
商店名称
商品数量

内联结 INNER JOIN

sql 复制代码
-- 各商店的商品信息
SELECT sp.shop_id, sp.shop_name, sp.product_id,
       p.product_name, p.sale_price
FROM shopproduct AS sp
INNER JOIN product AS p 
ON sp.product_id=p.product_id;

外联结 OUTER JOIN

  • 相对于内联结来说,只要数据存在于某一张表中,就能够取出来
  • 指定主表的关键字是 LEFTRIGHT,决定会包含哪一张的全部信息
  • 一般用 LEFT 比较多
sql 复制代码
-- 各商店的商品信息(含没商店归属的商品)
SELECT sp.shop_id, sp.shop_name, sp.product_id,
       p.product_name, p.sale_price
FROM shopproduct AS sp
RIGHT OUTER JOIN product AS p 
ON sp.product_id=p.product_id;

窗口函数

窗口函数也叫 OLAP 函数,对数据库数据进行实时分析处理。

  • PARTITION BY:分组依据
sql 复制代码
-- 对销售单价从低到高排序
SELECT product_name, product_type, sale_price,
       RANK() OVER (ORDER BY sale_price) AS ranking
FROM product;

-- 根据产品种类分组,对销售单价从低到高排序
SELECT product_name, product_type, sale_price,
       RANK() OVER (PARTITION BY product_type
                    ORDER BY sale_price) AS ranking
FROM product;

专用窗口函数

  • RANK:跳过之后的次位,如有3条记录在第1位时,1位、1位、1位、4位......
  • DENSE_RANK:不跳过之后的次位,如有3条记录在第1位时,1位、1位、1位、2位......
  • ROW_NUMBER:唯一的连续次位,如1位、2位、3位、4位
sql 复制代码
SELECT product_name, product_type, sale_price,
       RANK() OVER (ORDER BY sale_price) AS ranking,
       DENSE_RANK() OVER (ORDER BY sale_price) AS dense_ranking,
       ROW_NUMBER() OVER (ORDER BY sale_price) AS row_num
FROM product;

累计

sql 复制代码
-- 商品编号小于等于自己的销售单价合计值
SELECT product_id, product_name, product_type, sale_price,
       SUM(sale_price) OVER (ORDER BY product_id) AS current_sum
FROM product;

-- 商品编号小于等于自己的销售单价平均值
SELECT product_id, product_name, product_type, sale_price,
       AVG(sale_price) OVER (ORDER BY product_id) AS current_sum
FROM product;

移动平均

  • ROWS:行
  • PRECEDING:之前
  • FOLLOWING:之后
  • CURRENT ROW:当前行
  • UNBOUNDED FOLLOWING:最后一行
sql 复制代码
-- 商品编号小于等于自己的最近3个的销售单价平均值
SELECT product_id, product_name, product_type, sale_price,
       AVG(sale_price) OVER (ORDER BY product_id
                             ROWS 2 PRECEDING) AS current_sum
FROM product;

-- 商品编号包含自己最近3个的销售单价平均值
SELECT product_id, product_name, product_type, sale_price,
       AVG(sale_price) OVER (ORDER BY product_id
                             ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS current_sum
FROM product;

分组运算符

  • GROUP BY:按列分组,然后对每个组应用聚合函数
  • GROUP BY ROLLUP():同时得出合计和小计
  • GROUPING:分辨出是分组产生的 NULL 还是数据本身的 NULL
  • CUBE:比 ROLLUP 更全面的分组,生成所有可能的分组汇总
  • GROUPING SETS:精确指定想要的分组
sql 复制代码
-- 分别算出合计和各类汇总
SELECT '合计' AS product_type, SUM(sale_price)
FROM product
UNION ALL
SELECT product_type, SUM(sale_price)
FROM product
GROUP BY product_type;

更简便的方法

sql 复制代码
SELECT product_type, SUM(sale_price)
FROM product
GROUP BY rollup (product_type);

分别算出合计、商品种类、登记日期的汇总

sql 复制代码
SELECT product_type, regist_date, SUM(sale_price)
FROM product
GROUP BY rollup (product_type, regist_date)
ORDER BY product_type DESC;

会发现衣服的汇总很奇怪,有两条 regist_date 为 NULL 的汇总信息。因为其中一条衣服的 regist_date 是 NULL,无法区分出是汇总后产生的 NULL 还是数据本身是 NULL。

sql 复制代码
-- 使用GROUPING来区分
SELECT GROUPING(product_type), GROUPING(regist_date), SUM(sale_price)
FROM product
GROUP BY rollup (product_type, regist_date)
ORDER BY product_type DESC;

-- 插入恰当的字符串
SELECT CASE WHEN GROUPING(product_type)=1 
            THEN '商品种类 合计'
            ELSE product_type END AS product_type,
       CASE WHEN GROUPING(regist_date)=1 
            THEN '登记日期 合计'
            ELSE CAST(regist_date AS VARCHAR(16)) END AS regist_date,
       SUM(sale_price)
FROM product
GROUP BY rollup (product_type, regist_date);

更全面的分组汇总

sql 复制代码
SELECT CASE WHEN GROUPING(product_type)=1 
            THEN '商品种类 合计'
            ELSE product_type END AS product_type,
       CASE WHEN GROUPING(regist_date)=1 
            THEN '登记日期 合计'
            ELSE CAST(regist_date AS VARCHAR(16)) END AS regist_date,
       SUM(sale_price)
FROM product
GROUP BY rollup (product_type, regist_date)
ORDER BY GROUPING(product_type), GROUPING(regist_date);

更精确的分组汇总

sql 复制代码
-- 按商品种类和登记日期分组+总计
SELECT product_type, regist_date, SUM(sale_price)
FROM product
GROUP BY GROUPING SETS ((product_type, regist_date), ());

Python 驱动

安装

shell 复制代码
pip install psycopg2

代码

python 复制代码
import psycopg2

conn = psycopg2.connect(database='postgres', user='postgres', password='123456', host='127.0.0.1', port=5432)
cur = conn.cursor()
cur.execute('SELECT * FROM bookings;')
records = cur.fetchall()
print(records)

if 10 not in {i[0] for i in records}:
    cur.execute('INSERT INTO bookings (bookid, facid, memid, starttime, slots) VALUES (%s, %s, %s, %s, %s);',
                (10, 4, 0, '2012-07-04 16:00:00', 3))
    conn.commit()

pgAdmin 4 设置

File → Preferences

  • Query Tool
    • SQL formatting
      • Identifier case:Lower case
      • 取消勾选 Spaces around operators
      • 勾选 Use spaces

与 MySQL 的区别

WSL 连接 Windows 的 PostgreSQL

Windows 配置

  1. 打开【高级安全 Windows Defender 防火墙】

  2. 入站规则 → 新建规则 → 端口 → 下一页

  3. TCP → 填【5432】

  4. 全部下一步 → 名称填 PostgreSQL

  5. 修改 C:\Program Files\PostgreSQL\17\data\pg_hba.conf,添加

    host all all 172.0.0.0/8 scram-sha-256
    host all all 192.0.0.0/8 scram-sha-256

  6. 重启服务 postgresql-x64-17

  7. 查看主机 IP:ipconfig

WSL 连接

shell 复制代码
sudo apt-get update
sudo apt-get install postgresql-client
psql -h 172.26.224.1 -p 5432 -U postgres -d postgres

从MySQL迁移到PostgreSQL

mysqldump 导出数据

shell 复制代码
mysqldump --host host --port port -u user -p databasename tablename > table.sql

mysqldump -u <mysql_username> -p <mysql_password> --no-create-info <mysql_database_name> > mysql_data.sql

目前 pgloader 版本只到 2022 发布的 3.6.9,不足以支持 PostgreSQL 17

安装 pgloader

shell 复制代码
sudo apt-get update
sudo apt-get install pgloader
shell 复制代码
docker pull dimitri/pgloader
docker run -it dimitri/pgloader
pgloader --version

迁移表结构

shell 复制代码
pgloader -v 'mysql://user:pass@host:port/dbname' 'postgresql://user:pass@host:port/dbname'

# 批量
 --with 'batch rows=1000'

如果报错Heap exhausted during allocation,表示内存不足,增加内存来启动

docker run -it --memory=8g dimitri/pgloader

使用 Docker 启动 pgloader 非常容易报错,建议自行编译

shell 复制代码
# 拉代码
git clone https://github.com/dimitri/pgloader.git
cd pgloader

# 安装编译工具
sudo apt-get update
sudo apt-get install sbcl
sudo apt-get install make

# 编译软件并安装到系统中
make
sudo make install
find . -type f -executable -iname "pgloader"

为什么MySQL会丢数据,PostgreSQL不会丢数据?

项目 MySQL PostgreSQL
事务的持久性 innodb_flush_log_at_trx_commit 默认为 1 ,每次事务提交都强制写日志到磁盘 0 或 2,异步刷盘,性能更高但可能丢 1 秒的事务 fsync 默认为 on,确保每个事务提交后 WAL 日志立即写入磁盘
存储引擎 已淘汰的 MyISAM 不支持事务,留下坏印象 统一存储,数据变更先写 WAL 日志,再异步刷数据页
复制机制 默认异步复制,主库提交后立即返回,备库存在延迟,主库宕机会丢最新数据 默认同步复制,确保事务在一个备库持久化后才视为成功
故障策略 依赖 Binlog,没开启的话无法恢复 WAL 比数据页优先写入,若使用时间点恢复,可恢复到任意时间点
不丢数据配置 innodb_flush_log_at_trx_commit=1 + sync_binlog=1 + 双写缓冲 默认配置即强调数据安全

结论:正确配置是关键

Q:如果备库断电,主库会一直等待吗?

A:当备库断电,主库在等待一段时间后(synchronous_commitsynchronous_standby_names ),仍然没有收到备库的确认信息,主库会根据配置进行相应处理。默认情况下,主库会将复制机制降级为异步复制,继续处理新的事务,避免长时间阻塞。当备库恢复后,会重新建立同步复制关系并追赶数据。

PostGIS

安装PostGIS

启用扩展

sql 复制代码
CREATE EXTENSION postgis;

查看版本

sql 复制代码
SELECT PostGIS_Full_Version();
SELECT * FROM pg_available_extensions WHERE name = 'postgis';

创建空间数据表

sql 复制代码
CREATE TABLE city (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    location GEOMETRY(Point, 4326)
);

插入数据

sql 复制代码
INSERT INTO city (name, location)
VALUES 
    ('深圳', ST_SetSRID(ST_MakePoint(114.0596, 22.5429), 4326)),
    ('广州', ST_SetSRID(ST_MakePoint(113.2644, 23.1291), 4326)),
    ('武汉', ST_SetSRID(ST_MakePoint(114.3052, 30.5928), 4326)),
    ('青岛', ST_SetSRID(ST_MakePoint(120.3830, 36.0662), 4326)),
    ('北京', ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326));

查询两个城市之间的距离

sql 复制代码
# EPSG:4527:基于国家大地坐标系的投影坐标系
SELECT ST_Distance(
        ST_Transform((SELECT location FROM city WHERE name = '深圳'), 4527),
        ST_Transform((SELECT location FROM city WHERE name = '北京'), 4527));

查询200公里内的城市

sql 复制代码
SELECT name 
FROM city 
WHERE ST_DWithin(
        ST_Transform(location, 4527),
        ST_Transform((SELECT location FROM city WHERE name = '深圳'), 4527), 
        200000);

EPSG:4326:WGS84 坐标系,全球通用

EPSG:3857:伪墨卡托投影(Web Mercator),为网络地图应用设计的投影坐标系

EPSG:4490:CGCS2000 坐标系(China Geodetic Coordinate System 2000),国家大地坐标系

EPSG:4527:基于CGCS2000的投影坐标系,便于地理数据测量、分析和制图

EPSG:32650:通用横轴墨卡托投影(Universal Transverse Mercator,UTM)坐标系的一部分。UTM 投影将地球划分为 60 个投影带,每个投影带的经度跨度为 6 度,EPSG:32650 指的是第 50 个投影带(Zone 50),适用于北半球,能在局部地区提供高精度的坐标基准,确保测量结果的准确性和可靠性。

插件

术语

  • DBMS(Database Management System):数据库管理系统
  • RDBMS(Relational Database Management System):关系型数据库管理系统
  • DML(Data Manipulation Language):数据操纵语言
  • DDL(Data Definition Language):数据定义语言
  • OLAP(OnLine Analytical Processing):联机分析处理
  • ODBC(Open DataBase Connectivity):一种标准的应用程序编程接口(API),允许应用程序访问不同的数据库管理系统(DBMS),而无需关心所使用的数据库的具体细节
  • JDBC(Java DataBase Connectivity):Java 版本的 ODBC
  • RBAC(Role-Based Access Control):基于角色的访问控制
  • RLS(Row-Level Security):行级安全
  • CTE(Common Table Expressions):公共表表达式,允许在SQL查询中创建临时结果集
  • RDS(Relational Database Service):关系型数据库服务,是一种基于云计算技术的数据库服务模式,为用户提供了便捷、高效、可扩展的关系型数据库管理解决方案
  • WAL(Write-Ahead Logging):预写式日志,对数据库中的数据进行修改前,先将操作记录到日志文件中,用于保证数据的持久性和事务的原子性

遇到的坑

  1. Navicat 12 for PostgreSQL 表为空,但可以通过 SELECT 语句检索出来
    更新版本 12 为 15 。
  2. 启动 postgresql-x64-17 服务报错:本地计算机上的 postgreql-x64-17 服务启动后停止。某些服务在未有其他服务或程序使用时将自动停止。
    删除 C:\Program Files\PostgreSQL\17\data\postmaster.pid,重启服务。
    不行的话,在计算机管理 → 系统工具 → 事件查看器 → Windows 日志 → 应用程序中,查看对应的日志。
    可尝试重装。
  3. Navicat 15 for PostgreSQL 打开连接时报错:字段 "datlaastsysoid" 不存在

参考文献

  1. PostgreSQL 官网
  2. PostgreSQL | 菜鸟教程
  3. PostgreSQL 与 MySQL 相比,优势何在?
  4. postgreSQL psql工具使用详解
  5. Psycopg Documentation
  6. PostgreSQL Tutorials
  7. SQL基础教程(第2版)
  8. SQL基础教程(第2版) (豆瓣)
  9. SQL进阶教程
  10. SQL进阶教程 (豆瓣)
  11. postgres_books GitHub
  12. 全方位对比 Postgres 和 MySQL (2023 版)
  13. Postgres vs. MySQL: a Complete Comparison in 2024
  14. MySQL vs Postgres in 2024.
  15. 阿里云数据库RDS PostgreSQL
  16. PostgreSQL 在国内公司应用的多吗? - 知乎
  17. PostgreSQL中文社区
  18. Planet PostgreSQL
  19. PostgreSQL新手入门 - 阮一峰的网络日志
  20. 国产数据库清单(2020年第1季度) - 墨天轮
  21. PostgreSQL与MySQL优劣势比较浅谈
  22. MySQL vs PostgreSQL:数据库巨头之争,哪个更适合你的业务?
  23. 为什么PostgreSQL是最成功的数据库? - 知乎
  24. PostgreSQL vs MySQL performance benchmarking
  25. The Part of PostgreSQL We Hate the Most
  26. MySQL与PostgreSQL比较,哪个更好、我们该选用哪个?
  27. PostgreSQL 规约(2024版)
  28. Postgres与MySQL比较 - 极道
  29. PgSQL · 最佳实践 · EXPLAIN 使用浅析(优化器,查询计划)
  30. 弱水三千,只取一瓢,当图像搜索遇见PostgreSQL
  31. awesome-postgres: A curated list of awesome PostgreSQL software, libraries, tools and resources
  32. Managed Databases Connection Pools and PostgreSQL Benchmarking Using pgbench
  33. 都说MySQL快,为什么我的测试是PostgreSQL要快呢? - 知乎
  34. pgtpc: TPC B/C/H Benchmark for PostgreSQL
  35. dimitri/pgloader - Docker Image | Docker Hub
  36. pgloader Documentation
  37. Mysql迁移至pgsql分享
  38. Gis坐标系4326与3857及高德百度坐标系转换
  39. 超详细讲解GIS中常用的坐标系及在软件中的判别与操作
  40. PostGIS 距离计算建议
  41. 地图坐标系间的坐标转换及坐标距离计算
  42. <>
  43. <>
  44. <>
  45. <>
相关推荐
数据知道2 小时前
万字详解模式(Schema):如何利用 Schema 实现PostgreSQL中开发/测试/生产环境隔离
数据库·postgresql
WilliamHu.2 小时前
智能体项目实战
数据库·oracle
数据知道2 小时前
PostgreSQL实战:详细讲述UUID主键,以及如何生成无热点的分布式主键
数据库·分布式·postgresql
数据知道2 小时前
PostgreSQL实战:如何选择合适的数据类型?
数据库·postgresql
nvd112 小时前
Pytest 异步数据库测试实战:基于 AsyncMock 的无副作用打桩方案
数据库·pytest
os_lee2 小时前
Milvus 实战教程(Go 版本 + Ollama bge-m3 向量模型)
数据库·golang·milvus
laplace01232 小时前
向量库 Qdrant + 图数据库Neo4j+Embedding阿里百炼text-embedding-v3
数据库·embedding·agent·neo4j
云边有个稻草人2 小时前
从痛点到落地:金仓时序数据库核心能力拆解
数据库·时序数据库·kingbasees·金仓数据库·数据库安全防护
霍格沃兹测试学院-小舟畅学2 小时前
Playwright数据库断言:测试前后数据验证
数据库·oracle