安装 PostgreSQL
PostgreSQL 是一个功能强大的开源对象关系数据库系统。它经过数十年的积极开发和经过验证的架构,使其在可靠性、数据完整性和正确性方面赢得了良好的声誉。而其天生支持异步操作的特性,在高并发场景中倍受欢迎。同时,它的扩展性极强,只要你有能力,你可以使用任何你熟悉的其它开发语言来编写 PostgreSQL 脚本,比如:Rust、Python 等。PostgreSQL 对象
本章将介绍 PostgreSQL 常见的服务器和数据库对象。示例数据库
本章将介绍示例数据库,以便后续的演示。同时,你将学习到如何将已存在的数据导入到 PostgreSQL 中。基础 SELECT
本章将讨论如何使用简单的 `SELECT` 语句从 PostgreSQL 查询数据。PostgreSQL 的 `SELECT` 语句有诸多独特的特性,让我们一起感受一下。WHERE 子句
本章我们将学习 `WHERE` 子句,它不但能用于 `SELECT` 过滤查询结果,还能用于其它语句。LIMIT、OFFSET 和 FETCH 子句
本章我们学习 `LIMIT` 、`OFFSET` 和 `FETCH` 子句。和 `LIMIT` 一样,`FETCH` 也是为了限定返回的行数,但你不知道的是,`FETCH` 才是 SQL 标准,而 `LIMIT` 不是。LIKE 和 ILIKE
前面章节提过,在 PostgreSQL 中,`LIKE` 是区分大小写的。如果要像其它数据库那样不区分大小写,需要使用 `ILIKE`。PostgreSQL 还为它们提供了等价的运算符。连接
本章将讨论 PostgreSQL 的各种连接:内连接、左外连接、右外连接、交叉连接、自然连接、自连接和完全外连接。值得一提的是,哪怕到了 MySQL 8,MySQL 依然不支持完全外连接。分组
本章将讨论 PostgreSQL 的 `GROUP BY` 子句:将`SELECT` 语句返回的结果进行分组;对于每个分组,可以使用聚合函数。同时讨论与之相关的 `HAVING`、`CUBE`、`ROLLUP` 等。联合查询(并集)、交集查询及差集查询
本章将讨论 `UNION`:联合(并集)查询、`INTERSECT `:交集查询和`EXCEPT`:差集查询。子查询
本章我们将讨论如何使用 PostgreSQL 子查询来构建复杂的查询。同时会学习 `IN`、`EXISTS`、`ANY`、`SOME`、`ALL` 等操作。插入数据
本章将讨论如何使用 `INSERT` 向 PostgreSQL 中插入新行。同时介绍如何通过`REGURNING`子句返回最新插入行的ID以及批量插入数据的方法。修改数据
本章将讨论如何使用 `UPDATE` 语句修改 PostgreSQL 数据,以及配合 `RETURNING` 子句在修改数据的同时,返回修改后的内容。最后,还将介绍 `UPDATE JOIN`:根据另一张表中的数据进行修改。删除数据
本章将讨论如何使用 `DELETE` 语句删除 PostgreSQL 数据,以及配合 `RETURNING` 子句在删除数据的同时,返回已删除的内容。最后,还将介绍 `DELETE JOIN`:根据另一张表中的数据进行删除。插入或更新数据
在关系型数据库中,术语`upsert`称为合并:当插入数据时,如果数据已存在则进行更新,否则插入新行。PostgreSQL 使用 `INSERT ON CONFLICT` 实现这一功能。基础数据类型
本章我们讨论 PostgreSQL 的基本数据类型:布尔型、字符型、数值型和日期时间型。这些类型与其它数据库有着很多不同,让我们一起深入细节进行了解。表
本章将讨论和表相关的知识,包括:创建表、修改表、清空(截断)表和删除表。约束
本章将讨论 PostgreSQL 的约束,包括:主键约束、外键约束、唯一约束、非空约束、 CHECK 约束。条件表达式和运算符
本章将讨论 PostgreSQL 条件表达式和运算符,包括:使用 `CASE` 构造条件查询、使用 `COALESCE` 过滤非空参数、使用 `NULLIF` 处理 `NULL` 值以及使用 `CAST` 进行数据类型转换。视图
本章我们将讨论视图:创建、修改、删除视图。为高级篇的物化视图、递归视图等打好基础。性能分析
PostgreSQL 提供了 `EXPLAIN` 语句,它可以用来分析 SQL 的执行情况。本章将对其进行讨论。索引
PostgreSQL 索引是增强数据库查询性能的有效工具。然而,索引增加了数据库系统的写入和存储开销。因此,正确使用它们非常重要。本章我们将讨论如何使用索引。角色与权限
本章介绍角色与权限。PostgreSQL 使用角色来表示用户账号,而不是其它数据库那样使用用户概念。客户端鉴权
本章将讨论 PostgreSQL 客户端鉴权。回到之前安装 PostgreSQL 时的一个问题,为什么在本地登录 PostgreSQL 时,不需要输入密码?本章将回答这个问题。事务
本章将讨论如何使用 `BEGIN` 、 `COMMIT` 和 `ROLLBACK` 语句处理 PostgreSQL 事务。备份与还原
本章将介绍备份和还原 PostgreSQL 数据库。常用函数
本章对 PostgreSQL 常用函数进行汇总,包括:聚合函数、日期时间函数、字符串函数和数学函数。对于窗口函数,我们将在高级篇进行介绍。
基础数据类型
- 283
- 2023-07-31 03:29:37
本章我们讨论 PostgreSQL 的基本数据类型:布尔型、字符型、数值型和日期时间型。这些类型与其它数据库有着很多不同,让我们一起深入细节进行了解。
布尔类型
和某些数据库使用 TINYINT
模拟布尔类型不同,PostgreSQL 原生支持布尔类型:BOOLEAN
。它可以有三个值:TRUE
、FALSE
和 NULL
。它使用一个字节存储布尔值,同时 BOOLEAN
可以简写为 BOOL
。
在标准 SQL中,布尔值可以是 TRUE
、FALSE
和 NULL
。然而,PostgreSQL 在处理 FALSE
值时非常灵活。下表显示了 PostgreSQL 中 TRUE
和 FALSE
的有效常量值。
TRUE | FALSE |
---|---|
true | false |
't' | 'f' |
'true' | 'false' |
'y' | 'n' |
'yes' | 'no' |
'1' | '0' |
- 除了
TRUE
和FALSE
,其它常量值都要用单引号包裹- 这些常量值并不区分大小写
- 除了
TRUE
和FALSE
,其它常量值都要用单引号包裹 - 这些常量值并不区分大小写
示例
示例数据:
CREATE TABLE stock_availability (
product_id INT PRIMARY KEY,
available BOOLEAN NOT NULL
);
INSERT INTO stock_availability (product_id, available)
VALUES
(100, TRUE),
(200, FALSE),
(300, 't'),
(400, '1'),
(500, 'y'),
(600, 'yes'),
(700, 'no'),
(800, '0');
检查产品的可用性:
SELECT *
FROM stock_availability
WHERE available = 'yes';
/*结果
product_id | available
------------+-----------
100 | t
300 | t
400 | t
500 | t
600 | t
(5 rows)
*/
可以直接使用布尔类型的字段,而不用任何运算符来暗示真实值:
SELECT *
FROM stock_availability
WHERE available;
如果要查找 FALSE
值,则可以将布尔类型的字段与任何有效的布尔常量值进行比较。
以下查询返回不可用的产品:
或者使用 NOT
运算符来检查布尔类型字段的值是否为 FALSE
:
SELECT *
FROM stock_availability
WHERE NOT available;
字符类型
PostgreSQL 主要有三种字符类型:CHARACTER(n)
、CHARACTER VARYING(n)
和 TEXT
,n
是正整数。
其中,CHARACTER(n)
通常简写为 CHAR(n)
,CHARACTER VARYING(n)
通常简写为 VARCHAR(n)
。
字符类型 | 说明 |
---|---|
VARCHAR(n) | 有长度限制的可变字符 |
CHAR(n) | 固定长度、空白填充的不可变字符 |
TEXT 、VARCHAR | 无限长度可变字符 |
CHAR(n)
和VARCHAR(n)
:最多存储n
个字段,如果尝试存储超过n
个的字符,PostgreSQL 将报错。唯一例外是,如果超出的字符是空格,PostgreSQL 会将空格截断到最大长度(n
)并进行存储。- 如果字符串显式转换为
CHAR(n)
或VARCHAR(n)
,PostgreSQL 会将字符串截断为n
个字段,然后再将其插入表中。 TEXT
数据类型,理论上可以存储无限长度的字符,实际取决于电脑的存储大小。- 特别注意(尤其是MySQL用户):
VARCHAR
(即没有指定长度的VARCHAR
),其行为和TEXT
一样,在PostgreSQL中,VARCHAR
和TEXT
的性能是相同的。 - 特别注意:
CHAR
(即没有指定长度的CHAR
),其行为和CHAR(1)
一样。 - 与其它数据库不同的是,在PostgreSQL 中,三种字符类型之间没有性能差异。
示例
示例数据:
CREATE TABLE character_tests (
id serial PRIMARY KEY,
x CHAR (1),
y VARCHAR (10),
z TEXT
);
使用下面的 SQL 尝试插入数据时,将报错:
INSERT INTO character_tests (x, y, z)
VALUES('Yes','This is a test for varchar','This is a very long text for the PostgreSQL text column');
/*报错
ERROR: value too long for type character(1)
*/
整数类型
常用的整数类型有 SMALLINT
、INTEGER
和 BIGINT
:
类型 | 存储大小 | 最小值 | 最大值 |
---|---|---|---|
SMALLINT | 2字节 | -32,768 | +32,767 |
INTEGER | 4字节 | -2,147,483,648 | +2,147,483,647 |
BIGINT | 8字节 | -9,223,372,036,854,775,808 | +9,223,372,036,854,775,807 |
- 如果尝试存储超出允许范围的值,PostgreSQL 将报错
- 与 MySQL 不同,PostgreSQL 不提供无符号整数类型。——这是最让人无法理解的事情。
序列
序列是一种特殊的数据库对象,它生成整数序列,通常用于自动编号的主键。它类似于其它数据库中的 AUTO_INCREMENT
。
创建新表时,可以通过 SERIAL
来创建序列:
CREATE TABLE table_name(
id SERIAL
);
将 id
字段的数据类型设置为 SERIAL
,PostgreSQL 将执行以下操作:
- 首先,创建一个序列对象,并将序列生成的下一个值设置为该字段的默认值
- 其次,向
id
字段添加NOT NULL
约束,因为序列始终生成一个非空整数 - 第三,将序列的所有者分配给
id
字段,当删除id
字段或者表时,序列对象也会被 删除。
PostgreSQL 提供了3种序列类型:
类型 | 存储大小 | 取值范围 |
---|---|---|
SMALLSERIAL | 2字节 | 1 ~ 32,767 |
SERIAL | 4字节 | 1 ~ 2,147,483,647 |
BIGSERIAL | 8字节 | 1 ~ 9,223,372,036,854,775,807 |
示例
示例数据:
CREATE TABLE fruits(
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL
);
虽然 SERIAL
会隐式给字段加上 NOT NULL
约束,但它并不会隐式加上主键约束。所以你需要手动通过 PRIMARY KEY
给 id
字段加上主键约束。
在插入记录时,请忽略加了序列的字段:
INSERT INTO fruits(name)
VALUES('Orange');
或者,使用 DEFAULT
关键字:
INSERT INTO fruits(id,name)
VALUES(DEFAULT,'Apple');
可以使用 pg_get_serial_sequence()
函数,获取序列的名称:
pg_get_serial_sequence('表名','字段名');
-- 比如本例
pg_get_serial_sequence('fruits','id');
将序列名传递给 currval()
函数,可以获取序列生成的最新值:
currval(序列名);
-- 比如本例
SELECT currval(pg_get_serial_sequence('fruits', 'id'));
正如在讨论 INSERT
语句时所说,你可以通过 RETURNING
子句来返回 id
字段的值:
INSERT INTO fruits(name)
VALUES('Banana')
RETURNING id;
注意,序列生成器操作不是事务安全的。这意味着如果多个并发的数据库连接尝试从序列中获取下一个值,则每个客户端将获得不同的值。如果一个客户端回滚事务,则该客户端的序列号将不会被使用,从而在序列中产生间隙。
浮点类型
浮点类型主要有以下三种:
float(n)
:精度至少为n
,最大为8字节real
或float8
:4字节浮点数numeric(p,s)
:p
位实数,小数点后有s
位。它是精确的数字。
float/real
存储的是近似值,而numeric
可以存储精确性的数字,例如金额。其语法是:
NUMERIC(precision, scale)
precision
:总位数(精度);scale
:小数部分的位数- 例如:
1234.567
,它的总位数(精度,precision
)是7
,小数位数(scale
)为3
NUMERIC
类型最多可保存小数点前131072
位,小数点后16383
位。NUMERIC
的小数位数可以为零或正数:NUMERIC(precision)
表示的就是小数位数为零- 如果同时省略
precision
和scale
,则可以存储任何精度的值,最高可达上述精度和小数位数的限制。 - 在 PostgreSQL 中,
NUMERIC
和DECIMAL
是同义词 - 如果存储的值能接受近似值,使用
float/real
将比numeric
更节省资源
示例
示例数据 :
DROP TABLE IF EXISTS products;
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price NUMERIC(5,2)
);
INSERT INTO products (name, price)
VALUES ('Phone',500.215),
('Tablet',500.214);
由于 price
字段的小数位数是2,PostgreSQL 将 500.215
向上舍入为 500.22
,并将 500.214
向下舍入为 500.21
。
除了保存数字值之外,NUMERIC
还可以保存名为 NaN
的特殊值,它表示非数字。
-- 将ID为1的价格修改为 NaN
UPDATE products
SET price = 'NaN'
WHERE id = 1;
NaN
不等于任何值,包括它本身。NaN = NaN
返回false
- 在排序时(
ORDER BY
),NaN
大于其它具体的数字
日期类型
要存储日期值,可以使用 DATE
类型。它占用4个字节,取值范围在公元前 4713 年 ~ 公元 5874897 年
。它的格式是 yyyy-mm-dd
,比如:2023-07-31
表示 2023年7月31日
。
可以使用 CURRENT_DATE
作为 DATE
字段的默认值。
DROP TABLE IF EXISTS documents;
CREATE TABLE documents (
document_id serial PRIMARY KEY,
header_text VARCHAR (255) NOT NULL,
posting_date DATE NOT NULL DEFAULT CURRENT_DATE
);
INSERT INTO documents (header_text)
VALUES('Billing to customer XYZ');
SELECT * FROM documents;
常用日期函数
PostgreSQL 提供了许多日期函数,我们来看一些常用的函数:
获取当前日期
NOW()
函数返回当前的日期和时间,如果仅要获取日期部分,可以使用 ::
将其转换为 DATE
类型:
SELECT NOW()::date;
另一种方法是使用 CURRENT_DATE
:
SELECT CURRENT_DATE;
格式化输出日期值
日期值的默认格式是 yyyy-mm-dd
,它很符合国人的习惯。但当你的产品面向老外或者在某些特定场景下,可能还是需要对其进行格式化输出。PostgreSQL 提供了 TO_CHAR()
函数,下例为将日期格式化为 dd/mm/yyyy
格式:
SELECT TO_CHAR(NOW()::DATE, 'dd/mm/yyyy');
下例将日期格式化为 Jun 22, 2016
格式:
SELECT TO_CHAR(NOW()::DATE, 'Mon dd, yyyy');
计算两个日期之间的间隔
直接使用减号 -
即可。
SELECT NOW() - '2023-07-01'::DATE AS diff;
/*结果
diff
-------------------------
29 days 14:00:06.100446
*/
计算年龄(年、月、日)
要计算当前日期的年龄(以年、月、日为单位),可以使用 AGE()
函数。
SELECT AGE('2000-12-31'::DATE);
/* 结果
age
-------------------------
22 years 6 mons 30 days
*/
如果传递两个参数,将是第一个日期减去第二个日期:
SELECT AGE('2010-01-01'::DATE, '2000-12-31'::DATE);
/*结果
age
---------------
9 years 1 day
*/
从日期值中提取年、月、日
EXTRACT()
可以从日期中提取年、月、日等部分:
SELECT EXTRACT(YEAR FROM NOW()) AS 年, EXTRACT(MONTH FROM NOW()) AS 月, EXTRACT(DAY FROM NOW()) AS 日;
/*结果
年 | 月 | 日
------+----+----
2023 | 7 | 31
*/
时间类型
TIME
类型用于存储时间:
TIME(精度)
精度
是指小数位数,最多可达6位。TIME
需要8个字节进行存储,允许的范围是 00:00:00 ~ 24:00:00
。
常用格式如下:
格式 | 举例 |
---|---|
HH:MI | 01:02 |
HH:MI:SS | 01:02:03 |
HHMISS | 010203 |
MI:SS.pppppp | 04:59.999999 |
HH:MI:SS.pppppp | 04:05:06.777777 |
HHMISS.pppppp | 040506.777777 |
示例:
CREATE TABLE shifts (
id serial PRIMARY KEY,
shift_name VARCHAR NOT NULL,
start_at TIME NOT NULL,
end_at TIME NOT NULL
);
INSERT INTO shifts(shift_name, start_at, end_at)
VALUES('上午', '08:00:00', '12:00:00'),
('下午', '13:00:00', '17:00:00'),
('晚上', '18:00:00', '22:00:00');
时区
时间还可以带上时区,数据类型为 TIME WITH TIME ZONE
:
字段名 TIME WITH TIME ZONE
常用时间函数
获取当前时间
CURRENT_TIME
的结果中带有时区信息,LOCALTIME
也是获取当前时间,但它获取的是本地时间,所以隐藏了时区信息。
LOCALTIME
也有时区信息,只是在结果中隐藏了
LOCALTIME
也有时区信息,只是在结果中隐藏了
SELECT LOCALTIME;
-- 特定精度
SELECT LOCALTIME(5);
时区转换
语法:
[TIME with time zone] AT TIME ZONE time_zone
举例:
-- 将当前时间转换为中国标准时间(东8区)
SELECT CURRENT_TIME AT TIME ZONE 'PRC';
-- 或者
SELECT CURRENT_TIME AT TIME ZONE 'UTC-8';
-- 或者
SELECT CURRENT_TIME AT TIME ZONE 'Asia/Shanghai';
-- LOCALTIME 同样适用
SELECT LOCALTIME AT TIME ZONE 'Asia/Shanghai';
从时间值中提取小时、分钟、秒
使用 EXTRACT()
函数,可以从时间值中提取小时、分钟、秒
时间的算术运算
-- 计算间隔
SELECT CURRENT_TIME - '10:00'::TIME;
-- 当前时间添加2小时
SELECT CURRENT_TIME + INTERVAL '2 hours' AS result;
PostgreSQL 使用 TIMESTAMP
来存储日期时间。它提供了两种数据类型:
TIMESTAMP
:没有时区的时间戳。当更改数据库服务器的时区时,存储在数据库中的值不会自动更改。TIMESTAMPTZ
:带有时区的时间戳。它会感知服务器的时区,当时区发生改变时,存储在数据库中的值会自动更改。- PostgreSQL 以 UTC 值存储
TIMESTAMPTZ
- 当向
TIMESTAMPTZ
字段插入值时,PostgreSQL 会将其转换为 UTC 值,然后进行存储 - 当从
TIMESTAMPTZ
字段查询值时,PostgreSQL 会将值转换为服务器、用户或当前连接设置的时区的时间值 TIMESTAMPTZ
不存储时区数据,永远只存储 UTC 值。它只是会根据时区,将值进行转换。
- PostgreSQL 以 UTC 值存储
TIMESTAMP
和TIMESTAMPTZ
都是使用8个字节进行存储。
- PostgreSQL 以 UTC 值存储
TIMESTAMPTZ
- 当向
TIMESTAMPTZ
字段插入值时,PostgreSQL 会将其转换为 UTC 值,然后进行存储 - 当从
TIMESTAMPTZ
字段查询值时,PostgreSQL 会将值转换为服务器、用户或当前连接设置的时区的时间值 TIMESTAMPTZ
不存储时区数据,永远只存储 UTC 值。它只是会根据时区,将值进行转换。
这里的”时间戳“更像其它数据库中的
DATETIME
,而不是编程语言中的相同名词。
这里的”时间戳“更像其它数据库中的DATETIME
,而不是编程语言中的相同名词。
更改、显示当前时区
使用 SET TIMEZONE='时区'
可以更改数据库服务器的时区;使用 SHOW TIMEZONE
可以显示服务器当前的时区:
-- 更改时区为中国标准时间
SET TIMEZONE='Asia/Shanghai';
-- 显示当前时区
SHOW TIMEZONE;
/*结果
TimeZone
---------------
Asia/Shanghai
*/
示例
示例数据:
CREATE TABLE timestamp_demo (
ts TIMESTAMP,
tstz TIMESTAMPTZ
);
插入数据:
INSERT INTO timestamp_demo (ts, tstz)
VALUES('2016-06-22 19:10:25-07','2016-06-22 19:10:25-07');
查询:
SELECT * FROM timestamp_demo;
/* 结果
ts | tstz
---------------------+------------------------
2016-06-22 19:10:25 | 2016-06-23 10:10:25+08
*/
将时区设置为纽约:
SET timezone = 'America/New_York';
再次查询:
SELECT * FROM timestamp_demo;
/*结果
ts | tstz
---------------------+------------------------
2016-06-22 19:10:25 | 2016-06-22 22:10:25-04
*/
你会看到:
- 不带时区的
ts
字段,无论时区如何改变,它的值都是固定不变的 - 带时区的
tstz
字段,它的值随着时区的改变而自动发生改变
强烈建议使用
TIMESTAMPTZ
来存储日期时间
强烈建议使用TIMESTAMPTZ
来存储日期时间
常用函数
获取当前日期时间
SELECT NOW();
或者
SELECT CURRENT_TIMESTAMP;
时区转换
将时间戳的值转换为另一个时区:
SELECT TIMEZONE('时区名', 时间戳);
比如:
-- 虽然将一个字符串传递给该函数,PostgreSQL会进行隐式转换
SELECT timezone('Asia/Shanghai','2016-06-01 00:00');
-- 但更建议的是手动进行显式转换
SELECT timezone('Asia/Shanghai','2016-06-01 00:00'::timestamptz);