下载工作台
Java 数据库实战

SQL 查询:SELECT 与聚合

试读上半部分 · 解锁后可读全文

第 2 章 · SELECT、过滤、排序与聚合查询

本章目标:掌握 SELECT 投影与别名;WHERE 条件(比较、逻辑、IN、BETWEEN、LIKE、NULL);ORDER BYLIMIT 分页;聚合函数 COUNT/SUM/AVG/MIN/MAX;GROUP BY 分组与 HAVING 过滤分组;常用字符串/日期/数值函数;在 shop_db 样本表上完成运营报表 SQL;对照 spring-boot-web 分页与 python-database ch02 平行结构。

学时建议:4~5 小时(含 2.5 小时跟练)

前置:完成 java-database ch01;MySQL 容器 shop-db-mysql 已运行。


2.1 场景说明:运营要看「已上架商品统计」

运营同学通过虚构后台 https://api.example.com/admin 查看数据,底层即对 shop_db 的 SQL。本章先在 mysql 客户端手工建练习用样本表并插入数据(ch05 会改为规范 DDL + 外键)。

典型需求:

需求SQL 能力
已上架商品列表,按价格降序WHERE + ORDER BY
第 2 页,每页 10 条LIMIT OFFSET
各分类商品数、均价GROUP BY + AVG
均价超过 5000 分的分类HAVING
本月新增订单数日期函数 + COUNT

全站字段回顾products.slug UNIQUE、is_publishedprice 整数分;users.emailpassword_hash


2.2 准备样本数据(本章专用)

在 mysql 客户端执行(教学简化表,无完整外键):

USE shop_db;

DROP TABLE IF EXISTS order_lines;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS users;

CREATE TABLE products (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  slug VARCHAR(64) NOT NULL UNIQUE,
  title VARCHAR(200) NOT NULL,
  category VARCHAR(50) NOT NULL,
  price INT NOT NULL COMMENT '单位:分',
  is_published TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  email VARCHAR(255) NOT NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE orders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  status VARCHAR(20) NOT NULL DEFAULT 'pending',
  total_amount INT NOT NULL DEFAULT 0 COMMENT '分',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE order_lines (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  order_id BIGINT NOT NULL,
  product_id BIGINT NOT NULL,
  quantity INT NOT NULL,
  unit_price INT NOT NULL COMMENT '下单时单价,分'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO products (slug, title, category, price, is_published) VALUES
('java-concurrency', 'Java 并发实战', 'books', 4599, 1),
('spring-boot-guide', 'Spring Boot 3 指南', 'books', 5299, 1),
('kotlin-handbook', 'Kotlin 入门', 'books', 3999, 0),
('usb-c-cable', 'USB-C 数据线 1m', 'accessories', 1999, 1),
('mech-keyboard', '机械键盘 K87', 'accessories', 39900, 1),
('shop-mug', '贤紫优选马克杯', 'accessories', 5900, 1);

INSERT INTO users (email, password_hash) VALUES
('user-demo@example.com', '$2a$12$demo_hash_not_real'),
('ops@example.com', '$2a$12$ops_hash_not_real');

INSERT INTO orders (user_id, status, total_amount, created_at) VALUES
(1, 'paid', 9898, '2026-08-01 10:00:00'),
(1, 'pending', 1999, '2026-08-15 14:30:00'),
(2, 'paid', 45800, '2026-08-18 09:00:00');

INSERT INTO order_lines (order_id, product_id, quantity, unit_price) VALUES
(1, 1, 1, 4599),
(1, 2, 1, 5299),
(2, 4, 1, 1999),
(3, 5, 1, 39900),
(3, 6, 1, 5900);

SELECT COUNT(*) AS product_count FROM products;

2.3 SELECT 基础与列别名

-- 查询所有列(生产避免 SELECT *,明确列名利于索引与 JPA)
SELECT id, slug, title, price, is_published
FROM products;

-- 列别名 AS(AS 可省略)
SELECT
  slug,
  title,
  price AS price_cents,
  price / 100.0 AS price_yuan
FROM products
WHERE is_published = 1;

-- 去重
SELECT DISTINCT category FROM products;

-- 常量列
SELECT slug, 'CNY' AS currency, price FROM products LIMIT 3;
要点说明
SELECT 列表表达式、函数、子查询均可
别名同句 ORDER BY 可用别名;WHERE 不可用
DISTINCT对组合列去重:DISTINCT category, is_published

对照 spring-boot-web@Query("SELECT p.slug, p.title, p.price FROM Product p WHERE p.isPublished = true") 等价于指定列 SELECT。


2.4 WHERE 条件表达式

-- 比较
SELECT slug, price FROM products WHERE price >= 3000;

-- 逻辑 AND OR NOT
SELECT slug, title FROM products
WHERE is_published = 1 AND category = 'books';

SELECT slug FROM products
WHERE category = 'books' OR category = 'accessories';

-- IN / NOT IN
SELECT slug FROM products
WHERE category IN ('books', 'accessories');

-- BETWEEN(含边界)
SELECT slug, price FROM products
WHERE price BETWEEN 2000 AND 6000;

-- LIKE 模糊(% 任意长度,_ 单字符)
SELECT slug, title FROM products
WHERE title LIKE '%实战%';

-- NULL 必须用 IS NULL / IS NOT NULL
SELECT slug FROM products WHERE slug IS NOT NULL;

布尔字段:MySQL 用 TINYINT(1)is_published = 1 表真。与 spring-boot-web Boolean isPublished 对应。

常见 API 映射(虚构 GET /api/v1/products?category=books&min_price=3000):

SELECT slug, title, price, category
FROM products
WHERE is_published = 1
  AND category = 'books'
  AND price >= 3000
ORDER BY price ASC;

2.5 ORDER BY 与 LIMIT 分页

-- 单字段排序
SELECT slug, price FROM products
WHERE is_published = 1
ORDER BY price DESC;

-- 多字段:先 category 升序,再 price 降序
SELECT slug, category, price FROM products
ORDER BY category ASC, price DESC;

-- 分页:第 1 页,每页 2 条(OFFSET 从 0 开始)
SELECT slug, title, price FROM products
WHERE is_published = 1
ORDER BY id ASC
LIMIT 2 OFFSET 0;

-- 第 2 页
SELECT slug, title, price FROM products
WHERE is_published = 1
ORDER BY id ASC
LIMIT 2 OFFSET 2;
参数公式
page从 1 开始
page_size每页条数
OFFSET(page - 1) * page_size
LIMITpage_size

性能提示:大 OFFSET 深分页慢(需扫描跳过行);生产可用 keyset paginationWHERE id > ? ORDER BY id LIMIT n,spring-boot-web Pageable 选修)。


2.6 聚合函数

-- 全表聚合(无 GROUP BY 时 SELECT 只能有聚合列或常量)
SELECT
  COUNT(*) AS total_products,
  COUNT(CASE WHEN is_published = 1 THEN 1 END) AS published_count,
  SUM(price) AS sum_price_cents,
  AVG(price) AS avg_price_cents,
  MIN(price) AS min_price,
  MAX(price) AS max_price
FROM products;

-- 忽略 NULL:COUNT(col) 不统计 NULL 行
SELECT COUNT(slug), COUNT(category) FROM products;
函数作用
COUNT(*)行数,含 NULL
COUNT(col)col 非 NULL 行数
SUM/AVG数值列;非数值会转换或报错
MIN/MAX极值;可用于日期字符串

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。