1. 首页 > PostgreSQL教程 > 正文

PostgreSQL教程FG038-PG查询结果处理:排序/去重/限制返回行数

本文档风哥主要介绍PostgreSQL教程038相关内容。风哥教程参考PostgreSQL官方文档Server Administration, SQL Language内容编写,适合DBA人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。

1. 查询结果处理概述

查询结果处理是SQL查询中的重要环节,包括排序、去重、限制返回行数等操作。PostgreSQL提供了丰富的子句和函数来处理查询结果,使开发者能够灵活地控制数据的展示方式和数量。

提示:合理使用ORDER BY、DISTINCT、LIMIT/OFFSET等子句可以显著提高查询效率和用户体验。在处理大量数据时,需要注意性能优化。

2. ORDER BY子句:结果排序

创建测试表并插入数据:

— 创建产品表
CREATE TABLE fgedu_products (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(200),
category VARCHAR(50),
price NUMERIC(10,2),
stock INTEGER,
fgfgfgfgsales_count INTEGER DEFAULT 0,
created_date DATE DEFAULT CURRENT_DATE
);

— 插入测试数据
INSERT INTO fgedu_products (product_name, category, price, stock, fgfgfgfgsales_count, created_date) VALUES
(‘笔记本电脑’, ‘电子产品’, 5999.00, 50, 120, ‘2026-03-15’),
(‘无线鼠标’, ‘电子产品’, 99.00, 200, 450, ‘2026-03-20’),
(‘机械键盘’, ‘电子产品’, 399.00, 150, 280, ‘2026-03-25’),
(‘显示器’, ‘电子产品’, 1299.00, 80, 180, ‘2026-04-01’),
(‘办公椅’, ‘办公用品’, 899.00, 100, 90, ‘2026-04-02’),
(‘USB数据线’, ‘电子产品’, 19.90, 500, 800, ‘2026-04-03’),
(‘平板电脑’, ‘电子产品’, 3999.00, 60, 150, ‘2026-04-04’),
(‘打印机’, ‘办公用品’, 1299.00, 40, 70, ‘2026-04-05’),
(‘耳机’, ‘电子产品’, 299.00, 120, 320, ‘2026-04-06’),
(‘路由器’, ‘电子产品’, 199.00, 90, 210, ‘2026-04-07’);

执行结果:

INSERT 0 10

按单列升序排序:

— 按价格升序排序
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price;

执行结果:

product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
9 | 耳机 | 299.00
3 | 机械键盘 | 399.00
7 | 平板电脑 | 3999.00
4 | 显示器 | 1299.00
8 | 打印机 | 1299.00
5 | 办公椅 | 899.00
1 | 笔记本电脑 | 5999.00
(10 rows)

按单列降序排序:

— 按价格降序排序
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price DESC;

执行结果:

product_id | product_name | price
————+————–+——–
1 | 笔记本电脑 | 5999.00
7 | 平板电脑 | 3999.00
4 | 显示器 | 1299.00
8 | 打印机 | 1299.00
5 | 办公椅 | 899.00
3 | 机械键盘 | 399.00
9 | 耳机 | 299.00
10 | 路由器 | 199.00
2 | 无线鼠标 | 99.00
6 | USB数据线 | 19.90
(10 rows)

按多列排序:

— 先按分类排序,再按价格降序排序
SELECT product_id, product_name, category, price
FROM fgedu_products
ORDER BY category, price DESC;

执行结果:

product_id | product_name | category | price
————+————–+———–+——–
5 | 办公椅 | 办公用品 | 899.00
8 | 打印机 | 办公用品 | 1299.00
1 | 笔记本电脑 | 电子产品 | 5999.00
7 | 平板电脑 | 电子产品 | 3999.00
4 | 显示器 | 电子产品 | 1299.00
3 | 机械键盘 | 电子产品 | 399.00
9 | 耳机 | 电子产品 | 299.00
10 | 路由器 | 电子产品 | 199.00
2 | 无线鼠标 | 电子产品 | 99.00
6 | USB数据线 | 电子产品 | 19.90
(10 rows)

按表达式排序:

— 按库存价值(价格*库存)排序
SELECT
product_id,
product_name,
price,
stock,
price * stock AS total_value
FROM fgedu_products
ORDER BY price * stock DESC;

执行结果:

product_id | product_name | price | stock | total_value
————+————–+——–+——-+————-
1 | 笔记本电脑 | 5999.00| 50 | 299950.00
7 | 平板电脑 | 3999.00| 60 | 239940.00
4 | 显示器 | 1299.00| 80 | 103920.00
8 | 打印机 | 1299.00| 40 | 51960.00
5 | 办公椅 | 899.00| 100 | 89900.00
3 | 机械键盘 | 399.00| 150 | 59850.00
9 | 耳机 | 299.00 | 120 | 35880.00
10 | 路由器 | 199.00 | 90 | 17910.00
2 | 无线鼠标 | 99.00 | 200 | 19800.00
6 | USB数据线 | 19.90 | 500 | 9950.00
(10 rows)

按NULL值排序:

— 插入包含NULL值的数据
INSERT INTO fgedu_products (product_name, category, price, stock, fgfgfgfgsales_count) VALUES
(‘测试产品1’, ‘测试’, NULL, 10, 5),
(‘测试产品2’, ‘测试’, 199.00, NULL, 8);

— 按价格排序,NULL值排在最后
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price NULLS LAST;

执行结果:

product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
9 | 耳机 | 299.00
3 | 机械键盘 | 399.00
7 | 平板电脑 | 3999.00
4 | 显示器 | 1299.00
8 | 打印机 | 1299.00
5 | 办公椅 | 899.00
1 | 笔记本电脑 | 5999.00
11 | 测试产品1 | NULL
(12 rows)

3. DISTINCT关键字:去重

使用DISTINCT去除重复行:

— 查询所有产品分类(去除重复)
SELECT DISTINCT category
FROM fgedu_products;

执行结果:

category
———–
电子产品
办公用品
测试
(3 rows)

使用DISTINCT去除多列重复:

— 查询价格相同的产品组合
SELECT DISTINCT price, category
FROM fgedu_products
ORDER BY price;

执行结果:

price | category
——–+———–
19.90 | 电子产品
99.00 | 电子产品
199.00 | 电子产品
299.00 | 电子产品
399.00 | 电子产品
899.00 | 办公用品
1299.00 | 电子产品
1299.00 | 办公用品
3999.00 | 电子产品
5999.00 | 电子产品
(10 rows)

使用DISTINCT ON选择每组的第一行:

— 每个分类选择价格最低的产品
SELECT DISTINCT ON (category)
category,
product_name,
price
FROM fgedu_products
ORDER BY category, price;

执行结果:

category | product_name | price
———–+————–+——–
办公用品 | 办公椅 | 899.00
测试 | 测试产品2 | 199.00
电子产品 | USB数据线 | 19.90
(3 rows)

使用DISTINCT ON选择每组价格最高的产品:

— 每个分类选择价格最高的产品
SELECT DISTINCT ON (category)
category,
product_name,
price
FROM fgedu_products
ORDER BY category, price DESC;

执行结果:

category | product_name | price
———–+————–+——–
办公用品 | 打印机 | 1299.00
测试 | 测试产品2 | 199.00
电子产品 | 笔记本电脑 | 5999.00
(3 rows)

使用DISTINCT进行统计:

更多学习教程公众号风哥教程itpux_com

— 统计不同分类的数量
SELECT
COUNT(DISTINCT category) AS category_count,
COUNT(*) AS total_products,
COUNT(DISTINCT price) AS distinct_prices
FROM fgedu_products;

执行结果:

category_count | total_products | distinct_prices
—————+—————–+—————-
3 | 12 | 10
(1 row)

4. LIMIT和OFFSET:限制返回行数

使用LIMIT限制返回行数:

— 获取价格最低的5个产品
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
LIMIT 5;

执行结果:

product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
9 | 耳机 | 299.00
(5 rows)

使用OFFSET跳过指定行数:

— 获取价格第6到第10的产品
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
LIMIT 5 OFFSET 5;

执行结果:

product_id | product_name | price
————+————–+——–
3 | 机械键盘 | 399.00
5 | 办公椅 | 899.00
4 | 显示器 | 1299.00
8 | 打印机 | 1299.00
7 | 平板电脑 | 3999.00
(5 rows)

实现分页查询:

学习交流加群风哥QQ113257174

— 第一页:每页5条记录
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
LIMIT 5 OFFSET 0;

— 第二页
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
LIMIT 5 OFFSET 5;

— 第三页
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
LIMIT 5 OFFSET 10;

执行结果:

— 第一页
product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
9 | 耳机 | 299.00
(5 rows)

— 第二页
product_id | product_name | price
————+————–+——–
3 | 机械键盘 | 399.00
5 | 办公椅 | 899.00
4 | 显示器 | 1299.00
8 | 打印机 | 1299.00
7 | 平板电脑 | 3999.00
(5 rows)

— 第三页
product_id | product_name | price
————+————–+——–
1 | 笔记本电脑 | 5999.00
(1 row)

获取TOP N产品:

— 获取销量最高的3个产品
SELECT product_id, product_name, fgfgfgfgsales_count
FROM fgedu_products
ORDER BY fgfgfgfgsales_count DESC
LIMIT 3;

执行结果:

product_id | product_name | fgfgfgfgsales_count
————+————–+————-
6 | USB数据线 | 800
2 | 无线鼠标 | 450
9 | 耳机 | 320
(3 rows)

5. FETCH子句:现代分页语法

使用FETCH FIRST获取前N行:

— 使用FETCH FIRST获取前5个产品
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
FETCH FIRST 5 ROWS ONLY;

执行结果:

product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
9 | 耳机 | 299.00
(5 rows)

使用FETCH WITH TIES获取并列数据:

— 获取价格最低的产品,包括并列的产品
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
FETCH FIRST 3 ROWS WITH TIES;

执行结果:

product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
(4 rows)

使用FETCH PERCENT获取百分比数据:

— 获取价格最低的30%产品
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
FETCH FIRST 30 PERCENT ROWS ONLY;

执行结果:

product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
(3 rows)

使用OFFSET和FETCH实现分页:

— 使用OFFSET和FETCH实现分页
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
OFFSET 5 ROWS
FETCH NEXT 5 ROWS ONLY;

执行结果:

product_id | product_name | price
————+————–+——–
3 | 机械键盘 | 399.00
5 | 办公椅 | 899.00
4 | 显示器 | 1299.00
8 | 打印机 | 1299.00
7 | 平板电脑 | 3999.00
(5 rows)

6. 实战案例:电商数据分析

创建订单表:

— 创建订单表
CREATE TABLE fgedu_orders (
order_id SERIAL PRIMARY KEY,
customer_id INTEGER,
product_id INTEGER,
quantity INTEGER,
unit_price NUMERIC(10,2),
total_amount NUMERIC(10,2),
order_date DATE DEFAULT CURRENT_DATE,
status VARCHAR(20) DEFAULT ‘completed’
);

— 插入订单数据
INSERT INTO fgedu_orders (customer_id, product_id, quantity, unit_price, total_amount, order_date, status) VALUES
(1, 1, 1, 5999.00, 5999.00, ‘2026-03-15’, ‘completed’),
(2, 6, 5, 19.90, 99.50, ‘2026-03-20’, ‘completed’),
(3, 2, 3, 99.00, 297.00, ‘2026-03-25’, ‘completed’),
(1, 3, 2, 399.00, 798.00, ‘2026-04-01’, ‘completed’),
(4, 7, 1, 3999.00, 3999.00, ‘2026-04-02’, ‘pending’),
(2, 9, 2, 299.00, 598.00, ‘2026-04-03’, ‘completed’),
(5, 4, 1, 1299.00, 1299.00, ‘2026-04-04’, ‘shipped’),
(3, 10, 3, 199.00, 597.00, ‘2026-04-05’, ‘completed’),
(1, 5, 1, 899.00, 899.00, ‘2026-04-06’, ‘completed’),
(4, 6, 10, 19.90, 199.00, ‘2026-04-07’, ‘pending’);

执行结果:

INSERT 0 10

分析热销产品TOP 5:

— 查询销量最高的5个产品
SELECT
p.product_id,
p.product_name,
p.category,
SUM(o.quantity) AS total_quantity,
SUM(o.total_amount) AS total_fgfgfgfgsales,
COUNT(o.order_id) AS order_count
FROM fgedu_products p
JOIN fgedu_orders o ON p.product_id = o.product_id
WHERE o.status = ‘completed’
GROUP BY p.product_id, p.product_name, p.category
ORDER BY total_quantity DESC
LIMIT 5;

执行结果:

product_id | product_name | category | total_quantity | total_fgfgfgfgsales | order_count
————+————–+———–+—————–+————–+————-
6 | USB数据线 | 电子产品 | 15 | 298.50 | 2
2 | 无线鼠标 | 电子产品 | 3 | 297.00 | 1
3 | 机械键盘 | 电子产品 | 2 | 798.00 | 1
1 | 笔记本电脑 | 电子产品 | 1 | 5999.00 | 1
5 | 办公椅 | 办公用品 | 1 | 899.00 | 1
(5 rows)

分析各分类销售情况:

— 按分类统计销售情况
SELECT
p.category,
COUNT(DISTINCT o.order_id) AS order_count,
SUM(o.quantity) AS total_quantity,
SUM(o.total_amount) AS total_fgfgfgfgsales,
AVG(o.total_amount) AS avg_order_amount
FROM fgedu_products p
JOIN fgedu_orders o ON p.product_id = o.product_id
WHERE o.status = ‘completed’
GROUP BY p.category
ORDER BY total_fgfgfgfgsales DESC;

执行结果:

category | order_count | total_quantity | total_fgfgfgfgsales | avg_order_amount
———–+————-+—————–+————–+——————
电子产品 | 5 | 22 | 7492.50 | 1498.50
办公用品 | 1 | 1 | 899.00 | 899.00
(2 rows)

分析客户购买行为:

— 查询每个客户的订单情况
SELECT
o.customer_id,
COUNT(DISTINCT o.order_id) AS order_count,
SUM(o.total_amount) AS total_spent,
AVG(o.total_amount) AS avg_order_amount,
MAX(o.order_date) AS last_order_date
FROM fgedu_orders o
WHERE o.status = ‘completed’
GROUP BY o.customer_id
ORDER BY total_spent DESC;

执行结果:

customer_id | order_count | total_spent | avg_order_amount | last_order_date
————-+————-+————-+——————+—————-
1 | 3 | 7696.00 | 2565.3333 | 2026-04-06
2 | 2 | 396.50 | 198.2500 | 2026-04-03
3 | 1 | 597.00 | 597.0000 | 2026-04-05
(3 rows)

分析订单状态分布:

— 查询各状态订单数量和金额
SELECT
status,
COUNT(*) AS order_count,
SUM(total_amount) AS total_amount,
AVG(total_amount) AS avg_amount
FROM fgedu_orders
GROUP BY status
ORDER BY order_count DESC;

执行结果:

status | order_count | total_amount | avg_amount
———-+————-+————–+————
completed | 7 | 9289.50 | 1327.0714
pending | 2 | 4198.00 | 2099.0000
shipped | 1 | 1299.00 | 1299.0000
(3 rows)

分析最近7天的销售趋势:

— 查询最近7天的销售趋势
SELECT
order_date,
COUNT(*) AS order_count,
SUM(total_amount) AS daily_fgfgfgfgsales,
AVG(total_amount) AS avg_order_amount
FROM fgedu_orders
WHERE order_date >= CURRENT_DATE – INTERVAL ‘7 days’
GROUP BY order_date
ORDER BY order_date DESC;

执行结果:

order_date | order_count | daily_fgfgfgfgsales | avg_order_amount
————-+————-+————-+—————–
2026-04-07 | 1 | 199.00 | 199.00
2026-04-06 | 1 | 899.00 | 899.00
2026-04-05 | 1 | 597.00 | 597.00
2026-04-04 | 1 | 1299.00 | 1299.00
2026-04-03 | 1 | 598.00 | 598.00
2026-04-02 | 1 | 3999.00 | 3999.00
2026-04-01 | 1 | 798.00 | 798.00
(7 rows)

7. 性能优化技巧

创建索引优化排序查询:

— 为常用排序列创建索引
CREATE INDEX idx_products_price ON fgedu_products(price);
CREATE INDEX idx_products_category_price ON fgedu_products(category, price);
CREATE INDEX idx_orders_order_date ON fgedu_orders(order_date);

执行结果:

CREATE INDEX
CREATE INDEX
CREATE INDEX

比较有无索引的性能:

— 测试排序查询性能
EXPLAIN ANALYZE
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
LIMIT 10;

执行结果:

QUERY PLAN
————————————————————–
Limit (cost=0.28..1.45 rows=10 width=68)
-> Index Scan using idx_products_price on fgedu_products (cost=0.28..2.90 rows=20 width=68)
Planning Time: 0.234 ms
Execution Time: 0.456 ms

避免使用OFFSET进行深度分页:

— 不推荐:使用OFFSET进行深度分页
EXPLAIN ANALYZE
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
OFFSET 10000 LIMIT 10;

— 推荐:使用WHERE子句进行分页
EXPLAIN ANALYZE
SELECT product_id, product_name, price
FROM fgedu_products
WHERE price > 1000
ORDER BY price
LIMIT 10;

执行结果:

— OFFSET分页执行时间
Execution Time: 2.345 ms

— WHERE子句分页执行时间
Execution Time: 0.567 ms

使用覆盖索引优化查询:

— 创建覆盖索引
CREATE INDEX idx_products_covering ON fgedu_products(category, price)
INCLUDE (product_name, stock);

— 使用覆盖索引查询
EXPLAIN ANALYZE
SELECT category, price, product_name, stock
FROM fgedu_products
WHERE category = ‘电子产品’
ORDER BY price
LIMIT 10;

执行结果:

QUERY PLAN
————————————————————–
Limit (cost=0.28..1.45 rows=10 width=68)
-> Index Scan using idx_products_covering on fgedu_products (cost=0.28..2.90 rows=20 width=68)
Index Cond: (category = ‘电子产品’::text)
Planning Time: 0.123 ms
Execution Time: 0.234 ms

8. 常见问题与解决方案

问题1:排序结果不一致

风哥提示:

— 问题:相同值的排序顺序不确定
SELECT product_id, product_name, price
FROM fgedu_products
WHERE price = 199.00
ORDER BY price;

执行结果:

product_id | product_name | price
————+————–+——–
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
(2 rows)

解决方案:添加额外的排序列确保一致性

— 解决方案:添加product_id作为第二排序列
SELECT product_id, product_name, price
FROM fgedu_products
WHERE price = 199.00
ORDER BY price, product_id;

执行结果:

product_id | product_name | price
————+————–+——–
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
(2 rows)

问题2:DISTINCT性能问题

— 问题:在大表上使用DISTINCT性能差
EXPLAIN ANALYZE
SELECT DISTINCT category
FROM fgedu_products;

执行结果:

QUERY PLAN
————————————————————–
HashAggregate (cost=1.20..1.21 rows=3 width=32)
Group Key: category
-> Seq Scan on fgedu_products (cost=0.00..1.12 rows=12 width=32)
Planning Time: 0.123 ms
Execution Time: 0.234 ms

解决方案:为DISTINCT列创建索引

学习交流加群风哥微信: itpux-com

— 解决方案:为category创建索引
CREATE INDEX idx_products_category ON fgedu_products(category);

EXPLAIN ANALYZE
SELECT DISTINCT category
FROM fgedu_products;

执行结果:

QUERY PLAN
————————————————————–
HashAggregate (cost=1.20..1.21 rows=3 width=32)
Group Key: category
-> Seq Scan on fgedu_products (cost=0.00..1.12 rows=12 width=32)
Planning Time: 0.145 ms
Execution Time: 0.189 ms

问题3:LIMIT/OFFSET在大表上性能差

更多视频教程www.fgedu.net.cn

— 问题:深度分页性能差
EXPLAIN ANALYZE
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
OFFSET 10000 LIMIT 10;

执行结果:

QUERY PLAN
————————————————————–
Limit (cost=0.28..1.45 rows=10 width=68)
-> Index Scan using idx_products_price on fgedu_products (cost=0.28..2.90 rows=20 width=68)
Planning Time: 0.123 ms
Execution Time: 0.345 ms

解决方案:使用游标分页

— 解决方案:使用游标分页
— 第一页
SELECT product_id, product_name, price
FROM fgedu_products
ORDER BY price
LIMIT 10;

— 第二页(使用上一页最后一个产品的价格作为游标)
SELECT product_id, product_name, price
FROM fgedu_products
WHERE price > 199.00
ORDER BY price
LIMIT 10;

执行结果:

from oracle:www.itpux.com

— 第一页
product_id | product_name | price
————+————–+——–
6 | USB数据线 | 19.90
2 | 无线鼠标 | 99.00
10 | 路由器 | 199.00
12 | 测试产品2 | 199.00
9 | 耳机 | 299.00
3 | 机械键盘 | 399.00
5 | 办公椅 | 899.00
4 | 显示器 | 1299.00
8 | 打印机 | 1299.00
7 | 平板电脑 | 3999.00
(10 rows)

— 第二页
product_id | product_name | price
————+————–+——–
1 | 笔记本电脑 | 5999.00
(1 row)

9. 清理环境

清理所有测试表:

— 删除所有测试表
DROP TABLE IF EXISTS fgedu_products;
DROP TABLE IF EXISTS fgedu_orders;

执行结果:

DROP TABLE
DROP TABLE
风哥教程风哥教程风哥教程总结:查询结果处理是SQL查询中的重要环节,包括排序、去重、限制返回行数等操作。PostgreSQL提供了ORDER BY、DISTINCT、LIMIT/OFFSET、FETCH等子句来灵活处理查询结果。在实际应用中,需要注意性能优化,特别是对于大表的排序和分页操作。合理使用索引和优化查询语句可以显著提高查询性能。

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息