PostgreSQL 基础语法
本章将介绍 PostgreSQL 的 SQL 语法基础,包括标识符规则、注释、数据定义和操作语句等核心内容。掌握这些基础知识是使用 PostgreSQL 的第一步。
SQL 语法结构
语句结构
SQL 语句由多个元素组成,基本结构如下:
-- SQL 语句的基本结构
SELECT column1, column2 -- 选择列
FROM table_name -- 指定表
WHERE condition -- 过滤条件
GROUP BY column -- 分组
HAVING condition -- 分组过滤
ORDER BY column -- 排序
LIMIT count; -- 限制行数
语句分隔符
PostgreSQL 使用分号 ; 作为语句分隔符。在脚本文件中,每条语句应以分号结束:
-- 多条语句需要分号分隔
SELECT * FROM users;
SELECT * FROM orders;
-- 单条语句在交互式环境中可以省略分号
SELECT version()
大小写规则
PostgreSQL 对不同元素的大小写处理方式不同:
| 元素 | 大小写敏感性 | 说明 |
|---|---|---|
| 关键字 | 不敏感 | SELECT 等同于 select |
| 标识符 | 不敏感(默认) | 未加引号时转换为小写 |
| 字符串 | 敏感 | 'Hello' 不等于 'hello' |
| 加引号标识符 | 敏感 | "Users" 不等于 "users" |
-- 关键字不区分大小写(推荐使用大写)
select * from users; -- OK
SELECT * FROM users; -- OK,推荐
-- 未加引号的标识符转换为小写
CREATE TABLE Users (id INT); -- 创建表 "users"
SELECT * FROM Users; -- 查询表 "users"
SELECT * FROM users; -- 查询表 "users"
-- 加引号的标识符保持原样
CREATE TABLE "Users" (id INT); -- 创建表 "Users"
SELECT * FROM "Users"; -- 必须加引号
SELECT * FROM users; -- 错误!找不到表 "users"
SELECT * FROM Users; -- 错误!转换为 "users"
最佳实践:始终使用小写字母命名表和列,避免使用双引号,保持代码简洁一致。
标识符命名规则
命名规范
标识符(表名、列名、索引名等)需要遵循以下规则:
-- 合法的标识符
CREATE TABLE users (id INT); -- 字母开头
CREATE TABLE user_orders (id INT); -- 字母和下划线
CREATE TABLE order_items_2024 (id INT); -- 包含数字
CREATE TABLE _temp_data (id INT); -- 下划线开头
-- 需要加引号的标识符(包含特殊字符或保留字)
CREATE TABLE "user" (id INT); -- 保留字
CREATE TABLE "order-items" (id INT); -- 包含连字符
CREATE TABLE "2024_orders" (id INT); -- 数字开头
CREATE TABLE "Order Table" (id INT); -- 包含空格
-- 不推荐的命名方式
CREATE TABLE "MyTable" (id INT); -- 大小写混合
CREATE TABLE "select" (id INT); -- 使用保留字
命名约定
-- 推荐的命名约定
-- 表名:小写,使用下划线分隔,使用复数
CREATE TABLE users (...);
CREATE TABLE order_items (...);
CREATE TABLE product_categories (...);
-- 列名:小写,使用下划线分隔,使用单数
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email_address VARCHAR(100),
created_at TIMESTAMP,
is_active BOOLEAN
);
-- 主键命名:表名单数 + _id 或直接 id
CREATE TABLE orders (
id SERIAL PRIMARY KEY, -- 推荐
-- 或
order_id SERIAL PRIMARY KEY
);
-- 外键命名:关联表名单数 + _id
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES orders(id),
product_id INTEGER REFERENCES products(id)
);
-- 索引命名:idx_表名_列名
CREATE INDEX idx_users_email ON users(email_address);
CREATE INDEX idx_orders_customer ON orders(customer_id);
-- 唯一约束命名:uq_表名_列名 或 uk_表名_列名
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email_address);
保留字
PostgreSQL 有大量保留字,不能直接用作标识符:
-- 常见保留字(部分)
-- SELECT, FROM, WHERE, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER
-- TABLE, INDEX, VIEW, DATABASE, SCHEMA, USER, ROLE, GRANT, REVOKE
-- AND, OR, NOT, IN, LIKE, BETWEEN, IS, NULL, TRUE, FALSE
-- ORDER, GROUP, HAVING, LIMIT, OFFSET, JOIN, INNER, OUTER, LEFT, RIGHT
-- 如果必须使用保留字作为标识符,需要加双引号
CREATE TABLE "select" (id INT); -- 不推荐
CREATE TABLE "user" (id INT); -- 不推荐
-- 更好的做法:使用替代名称
CREATE TABLE selections (id INT);
CREATE TABLE app_users (id INT);
注释
PostgreSQL 支持两种注释格式:
单行注释
使用 -- 开始,到行末结束:
-- 这是单行注释
SELECT * FROM users; -- 这也是单行注释
-- 注释可以独占一行
-- 用于解释复杂逻辑
SELECT
id,
name,
salary * 12 AS annual_salary -- 计算年薪
FROM employees;
多行注释
使用 /* */ 包裹,可以跨越多行:
/*
* 这是多行注释
* 可以跨越多行
* 用于详细说明
*/
SELECT * FROM orders;
/*
计算每个部门的统计信息:
- 员工数量
- 平均工资
- 最高工资
- 最低工资
*/
SELECT
department,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary,
MIN(salary) AS min_salary
FROM employees
GROUP BY department;
/* 注释可以嵌套在代码中 */
SELECT id, /* 这是一个内联注释 */ name FROM users;
注释最佳实践
-- 好的注释:解释"为什么",而不是"是什么"
-- 不好的注释(解释显而易见的内容)
SELECT * FROM users; -- 查询所有用户
-- 好的注释(解释业务逻辑)
-- 只查询活跃用户,排除已注销和禁用的账户
-- 因为注销用户的数据已被清除,不应出现在报表中
SELECT * FROM users
WHERE status = 'active'
AND deleted_at IS NULL;
-- 使用注释标记待办事项
-- TODO: 添加缓存优化
-- FIXME: 处理时区问题
-- NOTE: 此查询在数据量大时可能较慢
-- 使用注释分隔代码块
-- ============================================
-- 用户统计查询
-- ============================================
SELECT
COUNT(*) AS total_users,
COUNT(*) FILTER (WHERE status = 'active') AS active_users,
COUNT(*) FILTER (WHERE status = 'inactive') AS inactive_users
FROM users;
数据定义语言 (DDL)
数据定义语言用于定义和修改数据库结构。
创建表 (CREATE TABLE)
-- 基本创建表语法
CREATE TABLE table_name (
column1 data_type [constraints],
column2 data_type [constraints],
...
);
-- 创建用户表
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
birth_date DATE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建带外键的表
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
order_number VARCHAR(20) NOT NULL UNIQUE,
total_amount NUMERIC(10, 2) NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 添加检查约束
CONSTRAINT chk_amount_positive CHECK (total_amount >= 0),
CONSTRAINT chk_status_valid CHECK (status IN ('pending', 'processing', 'completed', 'cancelled'))
);
-- 创建带注释的表
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price NUMERIC(10, 2) NOT NULL
);
COMMENT ON TABLE products IS '商品信息表';
COMMENT ON COLUMN products.id IS '商品唯一标识';
COMMENT ON COLUMN products.name IS '商品名称';
COMMENT ON COLUMN products.price IS '商品价格(单位:元)';
常用约束
-- PRIMARY KEY:主键约束
CREATE TABLE example (
id INTEGER PRIMARY KEY, -- 单列主键
-- 或
-- PRIMARY KEY (id)
);
-- 复合主键
CREATE TABLE order_items (
order_id INTEGER,
product_id INTEGER,
quantity INTEGER,
PRIMARY KEY (order_id, product_id)
);
-- FOREIGN KEY:外键约束
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id), -- 简写形式
-- 或完整形式
-- customer_id INTEGER,
-- FOREIGN KEY (customer_id) REFERENCES customers(id)
-- 带删除/更新行为的外键
manager_id INTEGER REFERENCES users(id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
-- UNIQUE:唯一约束
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE, -- 列级约束
email VARCHAR(100),
CONSTRAINT uq_users_email UNIQUE (email) -- 表级约束
);
-- NOT NULL:非空约束
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price NUMERIC(10, 2) NOT NULL
);
-- CHECK:检查约束
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
salary NUMERIC(10, 2),
age INTEGER,
CONSTRAINT chk_salary_positive CHECK (salary > 0),
CONSTRAINT chk_age_valid CHECK (age >= 18 AND age <= 65)
);
-- DEFAULT:默认值
CREATE TABLE users (
id SERIAL PRIMARY KEY,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
role VARCHAR(20) DEFAULT 'user'
);
修改表 (ALTER TABLE)
-- 添加列
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users ADD COLUMN address TEXT;
-- 添加多个列
ALTER TABLE users
ADD COLUMN city VARCHAR(50),
ADD COLUMN country VARCHAR(50);
-- 删除列
ALTER TABLE users DROP COLUMN phone;
-- 修改列类型
ALTER TABLE users ALTER COLUMN age TYPE BIGINT;
-- 修改列名
ALTER TABLE users RENAME COLUMN name TO username;
-- 添加约束
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);
ALTER TABLE users ADD CONSTRAINT chk_age CHECK (age >= 0);
-- 删除约束
ALTER TABLE users DROP CONSTRAINT uq_users_email;
-- 设置默认值
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active';
-- 删除默认值
ALTER TABLE users ALTER COLUMN status DROP DEFAULT;
-- 设置非空约束
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
-- 删除非空约束
ALTER TABLE users ALTER COLUMN email DROP NOT NULL;
-- 重命名表
ALTER TABLE users RENAME TO app_users;
-- 添加外键
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);
-- 重命名约束
ALTER TABLE users RENAME CONSTRAINT uq_users_email TO uq_users_email_address;
删除表 (DROP TABLE)
-- 删除表
DROP TABLE users;
-- 如果存在则删除(推荐)
DROP TABLE IF EXISTS users;
-- 级联删除(同时删除依赖对象)
DROP TABLE users CASCADE;
-- 查看删除影响
DROP TABLE users CASCADE;
-- NOTICE: drop cascades to constraint fk_orders_user on table orders
清空表 (TRUNCATE)
-- 清空表数据(比 DELETE 快)
TRUNCATE TABLE users;
-- 重置自增序列
TRUNCATE TABLE users RESTART IDENTITY;
-- 级联清空(有外键引用时)
TRUNCATE TABLE users CASCADE;
-- 清空多个表
TRUNCATE TABLE users, orders, products;
数据操作语言 (DML)
数据操作语言用于操作表中的数据。
插入数据 (INSERT)
-- 插入单行
INSERT INTO users (username, email)
VALUES ('john', '[email protected]');
-- 插入单行(所有列)
INSERT INTO users
VALUES (DEFAULT, 'jane', '[email protected]', 'hash123', 'Jane', 'Doe', NULL, TRUE, DEFAULT, DEFAULT);
-- 插入多行
INSERT INTO users (username, email, first_name) VALUES
('alice', '[email protected]', 'Alice'),
('bob', '[email protected]', 'Bob'),
('charlie', '[email protected]', 'Charlie');
-- 从查询结果插入
INSERT INTO users_archive (username, email, created_at)
SELECT username, email, created_at
FROM users
WHERE created_at < '2023-01-01';
-- 插入并返回
INSERT INTO users (username, email)
VALUES ('david', '[email protected]')
RETURNING id, username, created_at;
-- 插入冲突处理 (UPSERT)
INSERT INTO users (username, email)
VALUES ('john', '[email protected]')
ON CONFLICT (username)
DO UPDATE SET email = EXCLUDED.email;
-- 插入冲突时忽略
INSERT INTO users (username, email)
VALUES ('john', '[email protected]')
ON CONFLICT (username) DO NOTHING;
更新数据 (UPDATE)
-- 更新单列
UPDATE users SET status = 'inactive' WHERE id = 1;
-- 更新多列
UPDATE users
SET
first_name = 'John',
last_name = 'Doe',
updated_at = CURRENT_TIMESTAMP
WHERE id = 1;
-- 使用表达式更新
UPDATE products SET price = price * 1.1 WHERE category = 'electronics';
-- 使用 CASE 表达式
UPDATE products
SET price = CASE
WHEN category = 'electronics' THEN price * 1.1
WHEN category = 'clothing' THEN price * 1.05
ELSE price * 1.02
END;
-- 使用子查询更新
UPDATE orders
SET status = 'completed'
WHERE id IN (
SELECT order_id
FROM order_items
GROUP BY order_id
HAVING SUM(quantity) > 10
);
-- 更新并返回
UPDATE users
SET status = 'inactive'
WHERE last_login < '2023-01-01'
RETURNING id, username, status;
-- PostgreSQL 18+ 支持返回新旧值
UPDATE products
SET price = price * 1.1
WHERE category = 'electronics'
RETURNING
name,
OLD.price AS old_price,
NEW.price AS new_price;
删除数据 (DELETE)
-- 删除指定行
DELETE FROM users WHERE id = 1;
-- 条件删除
DELETE FROM users WHERE status = 'inactive';
-- 使用子查询删除
DELETE FROM orders
WHERE customer_id IN (
SELECT id FROM customers WHERE status = 'deleted'
);
-- 删除并返回
DELETE FROM users
WHERE status = 'inactive'
RETURNING id, username, deleted_at;
-- PostgreSQL 18+ 支持返回被删除的值
DELETE FROM products
WHERE stock = 0
RETURNING
OLD.name AS deleted_product,
OLD.price;
-- 删除所有数据(不推荐,使用 TRUNCATE 更快)
DELETE FROM users;
常用运算符
比较运算符
-- 基本比较
SELECT * FROM products WHERE price = 100;
SELECT * FROM products WHERE price != 100;
SELECT * FROM products WHERE price > 100;
SELECT * FROM products WHERE price >= 100;
SELECT * FROM products WHERE price < 100;
SELECT * FROM products WHERE price <= 100;
-- 范围比较
SELECT * FROM products WHERE price BETWEEN 50 AND 100;
SELECT * FROM products WHERE price NOT BETWEEN 50 AND 100;
-- 列表比较
SELECT * FROM products WHERE category IN ('electronics', 'clothing');
SELECT * FROM products WHERE category NOT IN ('electronics', 'clothing');
-- NULL 比较(重要:不能使用 = 或 !=)
SELECT * FROM users WHERE email IS NULL;
SELECT * FROM users WHERE email IS NOT NULL;
-- 模式匹配
SELECT * FROM users WHERE name LIKE '张%'; -- 以"张"开头
SELECT * FROM users WHERE name LIKE '%三'; -- 以"三"结尾
SELECT * FROM users WHERE name LIKE '%小%'; -- 包含"小"
SELECT * FROM users WHERE name LIKE '_三'; -- 第二个字是"三"
SELECT * FROM users WHERE name ILIKE 'ZHANG%'; -- 不区分大小写
逻辑运算符
-- AND:同时满足所有条件
SELECT * FROM products
WHERE category = 'electronics' AND price < 1000;
-- OR:满足任一条件
SELECT * FROM products
WHERE category = 'electronics' OR category = 'clothing';
-- NOT:取反
SELECT * FROM products
WHERE NOT (category = 'electronics');
-- 组合使用(使用括号明确优先级)
SELECT * FROM products
WHERE (category = 'electronics' OR category = 'clothing')
AND price BETWEEN 100 AND 500;
算术运算符
-- 基本运算
SELECT 10 + 5; -- 加法:15
SELECT 10 - 5; -- 减法:5
SELECT 10 * 5; -- 乘法:50
SELECT 10 / 3; -- 整数除法:3
SELECT 10.0 / 3; -- 浮点除法:3.333...
SELECT 10 % 3; -- 取余:1
SELECT 2 ^ 3; -- 幂运算:8
-- 在查询中使用
SELECT
name,
price,
quantity,
price * quantity AS total,
ROUND(price * 1.1, 2) AS price_with_tax
FROM order_items;
字符串运算符
-- 字符串连接
SELECT 'Hello' || ' ' || 'World'; -- 'Hello World'
SELECT CONCAT('Hello', ' ', 'World'); -- 'Hello World'
SELECT CONCAT_WS('-', 'a', 'b', 'c'); -- 'a-b-c'
-- 在查询中使用
SELECT
first_name || ' ' || last_name AS full_name,
CONCAT(email, '@example.com') AS full_email
FROM users;
表达式和函数
CASE 表达式
-- 简单 CASE
SELECT
name,
CASE category
WHEN 'electronics' THEN '电子产品'
WHEN 'clothing' THEN '服装'
WHEN 'food' THEN '食品'
ELSE '其他'
END AS category_cn
FROM products;
-- 搜索 CASE
SELECT
name,
price,
CASE
WHEN price < 100 THEN '低价'
WHEN price < 500 THEN '中价'
WHEN price < 1000 THEN '高价'
ELSE '奢侈品'
END AS price_level
FROM products;
-- 在聚合中使用
SELECT
category,
COUNT(*) AS total,
COUNT(*) FILTER (WHERE price > 500) AS high_price_count,
SUM(CASE WHEN price > 500 THEN price ELSE 0 END) AS high_price_total
FROM products
GROUP BY category;
NULL 处理函数
-- COALESCE:返回第一个非 NULL 值
SELECT COALESCE(phone, email, 'N/A') AS contact FROM users;
-- NULLIF:如果两值相等返回 NULL
SELECT NULLIF(price, 0) FROM products; -- 避免除零错误
-- 在计算中使用
SELECT
price,
discount,
price - COALESCE(discount, 0) AS final_price
FROM products;
-- 安全除法
SELECT price / NULLIF(quantity, 0) AS unit_price FROM orders;
小结
本章我们学习了 PostgreSQL 的基础语法:
- SQL 语法结构:语句组成、分隔符、大小写规则
- 标识符命名:命名规则、保留字、命名约定
- 注释:单行注释、多行注释、最佳实践
- 数据定义语言:CREATE、ALTER、DROP、TRUNCATE
- 数据操作语言:INSERT、UPDATE、DELETE
- 运算符:比较、逻辑、算术、字符串运算符
- 表达式:CASE 表达式、NULL 处理函数
练习
- 创建一个包含各种约束的用户表
- 编写 INSERT 语句插入测试数据
- 使用 UPDATE 语句批量更新数据
- 使用 CASE 表达式对数据进行分类