PostgreSQL: PostgreSQL SELECT查询基础

最后更新:2026-08-26

1. 你将学到


2. 故事

Bob 是一家电商平台的数据库工程师。平台每天产生约 10,000 笔订单,过去 30 天累积了 100,000 条记录。运营团队要求他:

  1. 查询最近 30 天所有订单,按金额从高到低排序
  2. 每页展示 50 条,支持翻页
  3. 过滤掉已取消的订单
  4. 展示时去掉重复的客户 ID

Bob 需要用 SELECTWHEREORDER BYLIMIT/OFFSETDISTINCT 来完成任务。


3. Concept:SELECT 基础查询

(1) SELECT 语法概览

SQL
SELECT column1, column2, ...
FROM table_name
[WHERE condition]
[ORDER BY column [ASC|DESC] [NULLS FIRST|NULLS LAST]]
[LIMIT count [OFFSET start]];

SELECT 是 SQL 中最常用的语句,用于从一个或多个表中检索数据。

(2) 查询所有列 vs 指定列

写法 说明 性能 推荐场景
SELECT * 返回所有列 较差,传输冗余数据 快速探索表结构
SELECT col1, col2 只返回指定列 较好,减少 I/O 生产环境查询

▶ 示例:查询所有列

SQL
SELECT * FROM orders;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

▶ 示例:查询指定列

SQL
SELECT order_id, customer_id, total_amount, order_date
FROM orders;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) 列别名

使用 AS 关键字(可省略)为列或表达式取别名,让结果更具可读性。

用法 示例 说明
AS 显式别名 price * qty AS subtotal 推荐写法,清晰
省略 AS price * qty subtotal 合法但可读性差
双引号别名 total_amount AS "Order Total" 别名含空格/大小写时必须

▶ 示例:使用列别名

SQL
SELECT
  order_id,
  total_amount AS amount,
  order_date AS "Order Date",
  total_amount * 0.08 AS tax
FROM orders;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(4) 表达式与计算列

SELECT 不仅能查列,还能写算术表达式、函数调用等,生成计算列。

▶ 示例:计算列

SQL
SELECT
  product_name,
  unit_price,
  unit_price * 1.1 AS price_with_tax,
  ROUND(unit_price * 1.1, 2) AS rounded_price
FROM products;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

4. Concept:DISTINCT 去重

(1) 基本去重

DISTINCT 去除结果集中的重复行,常用于统计唯一值数量。

▶ 示例:查询不重复的客户 ID

SQL
SELECT DISTINCT customer_id
FROM orders;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) 多列去重

DISTINCT 作用于所有指定列的组合,而非单列。

语句 去重范围
SELECT DISTINCT a a 去重
SELECT DISTINCT a, b (a, b) 组合去重
SELECT DISTINCT ON (a) a, b PostgreSQL 特有,按 a 去重并取每组第一行

▶ 示例:多列组合去重

SQL
SELECT DISTINCT customer_id, order_status
FROM orders;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) DISTINCT ON:PostgreSQL 独有语法

DISTINCT ON (expr) 可以按指定表达式去重,并返回每组中的第一行(需配合 ORDER BY 决定取哪行)。

▶ 示例:每个客户最新一笔订单

SQL
SELECT DISTINCT ON (customer_id)
  customer_id, order_id, order_date, total_amount
FROM orders
ORDER BY customer_id, order_date DESC;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

5. Concept:WHERE 条件过滤

(1) 基本比较运算符

运算符 含义 示例
= 等于 order_status = 'completed'
<>!= 不等于 total_amount <> 0
> 大于 total_amount > 100
< 小于 total_amount < 50
>= 大于等于 total_amount >= 100
<= 小于等于 total_amount <= 500

▶ 示例:过滤已完成的订单

SQL
SELECT order_id, customer_id, total_amount
FROM orders
WHERE order_status = 'completed';

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) 日期过滤

日期比较是电商场景最常见的过滤需求之一。

▶ 示例:查询最近 30 天订单

SQL
SELECT order_id, customer_id, total_amount, order_date
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days';

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) 多条件组合

ANDORNOT 组合多个条件。优先级:NOT > AND > OR,建议用括号显式控制。

▶ 示例:组合条件过滤

SQL
SELECT order_id, total_amount, order_status
FROM orders
WHERE order_status = 'completed'
  AND total_amount > 500
  AND order_date >= '2025-01-01';

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

6. Concept:ORDER BY 排序

(1) 基本排序

ORDER BY 默认升序(ASC),可指定降序(DESC)。支持按列名、别名、列位置排序。

排序方式 语法 说明
升序 ORDER BY col ASC 默认,可省略 ASC
降序 ORDER BY col DESC 从大到小
按别名 ORDER BY amount DESC 使用 SELECT 中的别名
按位置 ORDER BY 3 DESC 按 SELECT 列表中第 3 列(不推荐)

▶ 示例:按金额降序排列

SQL
SELECT order_id, customer_id, total_amount
FROM orders
ORDER BY total_amount DESC;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) 多列排序

多列排序时,先按第一列排,第一列相同的按第二列排,以此类推。

▶ 示例:先按状态再按金额排序

SQL
SELECT order_id, order_status, total_amount
FROM orders
ORDER BY order_status ASC, total_amount DESC;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) NULLS FIRST / NULLS LAST:PostgreSQL 特色

PostgreSQL 默认将 NULL 视为最大值(升序在末尾,降序在开头)。可以用 NULLS FIRSTNULLS LAST 显式控制 NULL 的位置。

排序 NULL 默认位置 可用调整
ASC NULL 在末尾 NULLS FIRST 将 NULL 移到开头
DESC NULL 在开头 NULLS LAST 将 NULL 移到末尾

▶ 示例:NULLS FIRST/LAST 控制

SQL
SELECT product_name, discount_rate
FROM products
ORDER BY discount_rate DESC NULLS LAST;

输出:

TEXT 📖 仅展示
 count 
-------
     5
(1 row)

7. Concept:LIMIT 与 FETCH FIRST 分页

(1) LIMIT / OFFSET

LIMIT 限制返回行数,OFFSET 跳过指定行数,二者配合实现分页。

▶ 示例:每页 50 条,第 3 页

SQL
SELECT order_id, customer_id, total_amount, order_date
FROM orders
WHERE order_status = 'completed'
ORDER BY total_amount DESC
LIMIT 50 OFFSET 100;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(2) FETCH FIRST:SQL 标准语法

PostgreSQL 同时支持 SQL 标准的 FETCH FIRST 语法,语义与 LIMIT 相同。

语法 等价写法 说明
LIMIT 10 FETCH FIRST 10 ROWS ONLY 标准写法
LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY 标准分页写法

▶ 示例:FETCH FIRST 分页

SQL
SELECT order_id, customer_id, total_amount
FROM orders
ORDER BY total_amount DESC
OFFSET 100 ROWS
FETCH FIRST 50 ROWS ONLY;

输出:

TEXT 📖 仅展示
 id | name     | value 
----+----------+-------
  1 | example  | 42
(1 row)

(3) 分页计算公式

参数 计算方式
页码 page 从 1 开始
每页条数 size 如 50
OFFSET (page - 1) * size
LIMIT size

▶ 示例:动态分页(伪代码)

BASH
page=3
size=50
offset=$(( (page - 1) * size ))
psql -c "SELECT order_id, total_amount FROM orders ORDER BY total_amount DESC LIMIT $size OFFSET $offset;"

输出:

TEXT 📖 仅展示
# psql 命令执行成功

8. 流程图:SELECT 查询执行顺序

100%
flowchart TD
    A[FROM orders] --> B[WHERE filter]
    B --> C[SELECT columns / expressions]
    C --> D[DISTINCT]
    D --> E[ORDER BY sort]
    E --> F[LIMIT / OFFSET paginate]
    F --> G[Result Set]

    style A fill:#e1f5fe
    style B fill:#fff3e0
    style C fill:#e8f5e9
    style D fill:#f3e5f5
    style E fill:#fce4ec
    style F fill:#e0f2f1
    style G fill:#c8e6c9

9. 综合示例

Bob 完成了运营的需求:查询最近 30 天已完成订单,按金额降序分页,展示客户 ID 去重统计。

SQL
-- Step 1: Recent 30-day completed orders, paginated by amount DESC
SELECT
  order_id,
  customer_id,
  total_amount,
  total_amount * 0.08 AS tax_amount,
  order_date AS "Order Date",
  order_status
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY total_amount DESC NULLS LAST
LIMIT 50 OFFSET 0;

-- Step 2: Count distinct customers in those orders
SELECT COUNT(DISTINCT customer_id) AS unique_customers
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days';

-- Step 3: Each customer's latest order (DISTINCT ON)
SELECT DISTINCT ON (customer_id)
  customer_id,
  order_id,
  total_amount,
  order_date
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY customer_id, total_amount DESC;

-- Step 4: Top 10 orders with computed total (price + tax)
SELECT
  order_id,
  customer_id,
  total_amount,
  ROUND(total_amount * 1.08, 2) AS total_with_tax,
  order_date
FROM orders
WHERE order_status = 'completed'
  AND order_date >= CURRENT_DATE - INTERVAL '30 days'
ORDER BY total_with_tax DESC
FETCH FIRST 10 ROWS ONLY;

❓ 常见问题

Q SELECT * 在生产环境中可以用吗?
A 不建议。SELECT * 会返回所有列,增加网络传输和内存开销,且表结构变更时可能破坏应用逻辑,应始终显式列出需要的列。
Q DISTINCT 和 GROUP BY 去重有什么区别?
A DISTINCT 只去除重复行,GROUP BY 除了去重还能做聚合计算。如果只是去重不看聚合值,DISTINCT 更直观;需要聚合时用 GROUP BY。
Q LIMIT OFFSET 分页在大偏移量时性能如何?
A OFFSET 需要跳过前 N 行,偏移量越大越慢。大数据量分页推荐用 keyset pagination(基于排序键的 WHERE 过滤),如 WHERE order_id > last_seen_id LIMIT 50。
Q NULLS FIRST/LAST 是标准 SQL 吗?
A 不是,这是 PostgreSQL 的扩展语法。MySQL 和 SQL Server 不支持,需用 CASE 或 COALESCE 模拟。
Q DISTINCT ON 可以不用 ORDER BY 吗?
A 语法上可以,但如果不指定 ORDER BY,每组取哪一行是不确定的。必须配合 ORDER BY 才能保证结果可预测。
Q 列别名能在 WHERE 中使用吗?
A 不能。SQL 执行顺序中 WHERE 在 SELECT 之前,别名此时还不存在。但别名可以在 ORDER BY 中使用,因为 ORDER BY 在 SELECT 之后执行。
Q FETCH FIRST 和 LIMIT 完全等价吗?
A 功能上完全等价。FETCH FIRST 是 SQL:2008 标准语法,LIMIT 是 PostgreSQL 传统语法。建议新代码用 FETCH FIRST 提升可移植性。
Q ORDER BY 列不在 SELECT 列表中可以吗?
A 可以。PostgreSQL 允许 ORDER BY 引用未出现在 SELECT 中的列,但如果使用了 DISTINCT 或 GROUP BY,则排序列必须在 SELECT 或 GROUP BY 中。

📖 小节


📝 作业

  1. ⭐ 编写查询,从 products 表中选择 product_nameunit_price,按价格升序排列,只返回前 10 条。

  2. ⭐⭐ 编写查询,从 orders 表中选择最近 7 天的订单,过滤出 total_amount > 200 的记录,按 order_date DESC 排列,使用 FETCH FIRST 20 ROWS ONLY

  3. ⭐⭐⭐ 使用 DISTINCT ON 查询每个 customer_id 金额最大的订单(提示:需要 ORDER BY customer_id, total_amount DESC),并统计这些客户中有多少位的最大单笔金额超过 1,000 USD。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏