【有道云笔记】九 3.25 数据库、sql单表多表
https://note.youdao.com/s/FjkgqPKp
一、数据库简介
数据在内存:
优点:读写速度快
缺点:程序结束后数据丢失
保存到文件
优点:数据可以永久保存
缺点:
1、频繁的IO操作,效率不高
2、数据的管理非常不方便,需要把所有的数据整体都读取出来才能操作
数据库:
1、数据永久保存
2、数据管理非常方便
卸载:
1、控制面板-程序和功能,卸载MySql
2、C:\ProgramData删除MySql目录
数据库是以表为组织单位存储数据的。
二、单表
PRIMARY KEY 主键,不能重复,唯一确定一条记录 (unique+not null)
表中的任何列都可以作为主键,只要它满足一下条件:
1、任意两行都不具有相同的主键值
2、每一行都必须具有一个主键值(主键列不允许空置NULL)
3、主键列中的值不允许修改或更新
4、主键值不能重用(如果某行从表中删除,它的主键不能赋给以后的新行)
AUTO_INCREMENT 自动增长
varchar(10) char(10)区别:
相同点:都可以最大放10个字符
不同点:char(10)不管输入的是多少都会占10个字符,例如输入名字"张三"只有两个字符,
但是使用char(10)在数据库里面还是占10个字符的空间。
使用varchar(10)最大支持是10个字符,但是实际长度就是输入字符长度,例如输入名字"张三"只有两个字符,
那么在varchar(10)里面就只占两个字符。
Duplicate entry '1' for key 'PRIMARY'
select
数据库中以表为组织单位存储数据。
表类似我们的Java类,每个字段对应类里面的属性。
那么用我们熟悉的java程序来与关系型数据对比,就会发现以下对应关系。
类--------------------表
类中属性-------------表中字段(列)
对象------------------记录(行)
字段(列)可以当成变量(类中属性时变量)
变量是可以计算(操作)
-- 列出所有的数据库 SHOW DATABASES; -- 创建数据库 CREATE DATABASE study DEFAULT CHARACTER SET utf8; -- 删除数据库 DROP DATABASE study; -- ---------------------------------- -- 数据库表的操作 -- 切换数据库 USE study; -- 创建表 CREATE TABLE student( id INT, `name` CHAR(10), age INT, gender CHAR(1) ); -- 查看所有表 SHOW TABLES; -- 查看表的结构 DESC student; -- description -- 删除表 DROP TABLE student; -- 更改表的结构 -- 添加字段 ALTER TABLE student ADD COLUMN address CHAR(10); -- 删除字段 ALTER TABLE student DROP COLUMN address; -- 修改表的字段 ALTER TABLE student CHANGE address addr CHAR(20); -- 修改表的名字 ALTER TABLE student RENAME TO stu; -- 创建表 CREATE TABLE student( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(10), age INT, gender CHAR(1) ); -- * 代表查询所有的列 SELECT * FROM student; -- 插入数据 -- Duplicate entry '1' for key 'PRIMARY' INSERT INTO student(id,`name`,age,gender) VALUES(1,'wangwu',23,'男'); INSERT INTO student(id,`name`,age,gender) VALUES(3,'赵六',23,'男'); INSERT INTO student VALUES(4,'赵六22',33,'男'); -- 插入部分字段值(必须把前面的字段名都写上) INSERT INTO student(`name`,age,gender) VALUES('小张11',23,'男'); -- 一次插入多条数据 INSERT INTO student(`name`,age,gender) VALUES('小张77',23,'男'),('小王',22,'男'); -- 修改数据 UPDATE student SET age=age+1; UPDATE student SET age=age+1,name='zhangsan' WHERE id=7; -- 删除数据 DELETE FROM student; -- 删除表中所有数据(很少使用,是非常危险) DELETE FROM student WHERE age=24; -- 所有age是24的数据都被删除了,可能有多条数据都是age=24 DELETE FROM student WHERE id=12; -- 因为id是主键是唯一的,所以根据id删除只能删除唯一的一条数据 -- TRUNCATE删除表里面所有数据,自增的id会重新初始化为初始值1 TRUNCATE TABLE student; -- 查询数据 -- 显示所有列(字段)数据 -- 学习时候可以写*,但是在企业开发中需要什么字段就写什么字段,检索不需要的列会降低检索速度和应用程序的性能 SELECT * FROM student; SELECT id,`name`,age,gender FROM student; -- 查询指定列 SELECT `name`,age FROM student; -- 查询时候添加常量列,通过as可以起别名 SELECT id,`name`,age AS '年龄','java2403' AS '班级' FROM student; -- 查询时候合并列,字段可以当成java里面的变量来运算 SELECT id,`name`,(php+java) AS '总成绩' FROM student; -- 查询时候去掉重复的记录 SELECT DISTINCT address FROM student; -- 计算字段(列): -- 计算字段并不实际存在于数据库表中,计算字段是运行时在SELECT语句内创建的。 -- 只有数据库知道SELECT语句中哪些列是实际的列表,哪些列是计算字段。 -- 从客户端(如应用程序)来看,计算字段的数据与其他列的数据的返回方式相同。 -- 计算字段实际上没有名字,它只是一个值,如果在SQL查询工具汇总查看一下结果,这样没有什么不好。 -- 但是,一个未命名的列不能用于客户端应用中,因为客户端没有办法引用它。工具会根据计算字段自己起一些名字, -- 很多命名不规范,所以最好给计算字段起别名alias,别名alias是一个字段或值的替换名。 -- 条件查询 where ,数据库表一般包含大量的数据,很少需要检索表中所有行 SELECT * FROM student WHERE `name`='小王'; -- 逻辑条件: and(同时成立) or(只要有一个成立) SELECT * FROM student WHERE `name`='小王' AND address='青岛'; SELECT * FROM student WHERE `name`='小王' OR address='北京'; -- 比较运算: > < >= <= != 不等于也可以写成:<> SELECT * FROM student WHERE java>=70 AND java<=80; -- between and (等价于>= and <=) SELECT * FROM student WHERE java BETWEEN 70 AND 80; -- 查询地址不是青岛的学生信息 <> SELECT * FROM student WHERE address != '青岛'; -- 空值NULL:无值(no value),它与字段包含0、空字符串或紧紧包含空格不同 SELECT * FROM student WHERE address IS NULL; -- IS NOT NULL -- 聚合查询 -- 聚合查询函数:sum(),avg(),max(),min(),count() -- 统计学生php的总成绩(sum求和) SELECT SUM(php) AS 'php总成绩' FROM student; -- 统计学生php的平均值 SELECT AVG(php) AS 'php平均值' FROM student; -- 统计学生php的最大值 SELECT MAX(php) AS 'php最大值' FROM student; -- 统计学生表里面一共有多少学生 SELECT COUNT(*) AS '总人数' FROM student; SELECT COUNT(id) AS '总人数' FROM student; SELECT COUNT(address) AS '总人数' FROM student; -- 注意:如果指定列明,则COUNT会忽略指定列的值为NULL的行,用*则不忽略。 -- 查询排序 desc:descending asc:ascending -- 语法:order by 字段 asc/desc 默认是asc升序,可以不写 SELECT * FROM student ORDER BY php; SELECT * FROM student ORDER BY php ASC; SELECT * FROM student ORDER BY php DESC; -- 多个条件排序 -- 需求:先按照php降序,java升序(整体是按照php降序,如果php相同的数据再按照java标准排序) SELECT * FROM student ORDER BY php DESC, java ASC; -- order by要放在sql语句的最后 -- 分组查询(group by) -- 需求:查询男女分别有多少人 -- 分组查询(group by) -- 需求:查询男女分别有多少人 -- 性别 人数 -- 男 3 -- 女 2 SELECT gender AS '性别',COUNT(*) AS '人数' FROM student GROUP BY gender; -- 分组之后的条件筛选用HAVING SELECT gender AS '性别',COUNT(*) AS '人数' FROM student GROUP BY gender HAVING COUNT(*)>1;
总结:
1、如果分组中包含具有NULL值的行,则NULL将作为一个分组返回。
2、GROUP BY字句必须出现在WHERE字句之后,ORDER BY字句之前。
3、WHERE过滤行(行级过滤),而HAVING过滤分组(组级过滤),WHERE所有技术和选项都适用于HAVING,语法使用一样。
字段属性设置:
1、not null: 不为空,表示该字段不能放"null"这个值。不写,则默认是可以为空
2、auto_increment: 设定int类型字段的值可以"自增长",即其值无需"写入",而会自动获得并增加
此属性必须随同 primary key 或 unique key 一起使用。primary key = unique key + not null
3、[primary] key: 设定为主键。是唯一键"加强":不能重复并且不能使用null,并且可以作为确定任意一行数据的"关键值",最常见的类似:where id= 8; 或 where user_name = 'zhangsan';
通常,每个表都应该有个主键,而且大多数表,喜欢使用一个id并自增长类型作为主键。
但:一个表只能设定一个主键。
4、unique [key] : 设定为唯一键:表示该字段的所有行的值不可以重复(唯一性)。
Duplicate entry 'zhangsan' for key 'name'
5、default '默认值': 设定一个字段在没有插入数据的时候自动使用的值。
6、comment '字段注释'
CREATE TABLE teacher( id INT PRIMARY KEY AUTO_INCREMENT, `name` CHAR(10) NOT NULL, age INT COMMENT '年龄', address CHAR(10) DEFAULT '中国', -- 插入数据时候如果不赋值,默认值是"中国" UNIQUE KEY(`name`) -- 唯一键,代表这个字段不能重复 ); -- Duplicate entry 'zhangsan' for key 'name' INSERT INTO teacher(`name`) VALUES('zhangsan');
三、多表
学生表、班级表、课程表、班级课程表
关系型数据库:MySql、SqlServer、Oracle
相同的数据出现多次绝不是一件好事,这是关系数据库设计的基础。关系表的设计就是要把信息分解成多个表,一个数据一个表,各表通过某些共同的值互相连接,所以才叫关系数据库。
将数据存储到多个表能更有效的存储,更方便的处理,但这些好处是有代价的:如果数据存储在多个表中,怎么用一条SELECT语句就检索出数据呢?答案是使用:子查询、联结
非关系型数据库:Redis ,MongoDB 速度非常快(日志信息)
可以理解为一个大的Map结构
-- 多对多 -- 班级表 CREATE TABLE banji( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(10) NOT NULL ); INSERT INTO banji(`name`) VALUES('java1807'),('java1812'); SELECT * FROM banji; -- 学生表 reference:参考,引用 CREATE TABLE student( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(10) NOT NULL, age INT, gender CHAR(1), banji_id INT, FOREIGN KEY(banji_id) REFERENCES banji(id) ); INSERT INTO student(`name`,age,gender,banji_id) VALUES('张三',20,'男',1),('李四',21,'男',2),('王五',20,'女',1); -- Cannot add or update a child row: a foreign key constraint fails (`java1812`.`student`, CONSTRAINT `student_ibfk_1` FOREIGN KEY (`banji_id`) REFERENCES `banji` (`id`)) INSERT INTO student(`name`,age,gender,banji_id) VALUES('张三',20,'男',3); SELECT * FROM student; -- 课程表 CREATE TABLE course( id INT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(10) NOT NULL, credit INT COMMENT '学分' ); INSERT INTO course(`name`,credit) VALUES('Java',5),('UI',4),('H5',4); SELECT * FROM course; -- 班级课程表 CREATE TABLE banji_course( -- id int PRIMARY KEY AUTO_INCREMENT, banji_id INT, course_id INT, PRIMARY KEY(banji_id,course_id), -- 联合主键 FOREIGN KEY(banji_id) REFERENCES banji(id), -- banji_id既是联合主键又是外键 FOREIGN KEY(course_id) REFERENCES course(id) -- course_id既是联合主键又是外键 ); INSERT INTO banji_course(banji_id,course_id) VALUES(1,1),(1,3),(2,1),(2,2),(2,3); SELECT * FROM banji_course; -- 子查询:嵌套查询,一个查询语句是另一个查询语句的条件 -- 查询班级是java1812班所有学生信息 SELECT * FROM student WHERE banji_id=2; SELECT id FROM banji WHERE `name`='java1812'; SELECT * FROM student WHERE banji_id=(SELECT id FROM banji WHERE `name`='java1812'); -- 班级是java1807班或者java1812班所有学生信息 SELECT * FROM student WHERE banji_id=1 OR banji_id=2; SELECT * FROM student WHERE banji_id IN(1,2); SELECT id FROM banji WHERE `name`='java1807' OR `name`='java1812'; -- 1,2 SELECT * FROM student WHERE banji_id IN(SELECT id FROM banji WHERE `name`='java1807' OR `name`='java1812'); -- 计算字段使用子查询:班级id 班级名字 班级人数 -- 执行这条查询遵循下面的步骤: -- 1、从banji表检索班级列表 -- 2、对检索出的每个banji,统计其在student表中的数量 SELECT id, `name`, (SELECT COUNT(*) FROM student WHERE student.banji_id=banji.id) AS total_count FROM banji ORDER BY `name`;
total_count是一个计算字段,它是由括号中的子查询建立的,该子查询对检索出的每个banji执行一次
总结:
1、"=":要求子查询只有一个结果。 "in":子查询可以有多个结果。
2、子查询的SELECT语句只能查询单个列,企图检索多个列将返回错误。
3、能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。
子查询也可以使用下面的联结来实现
四、等值连接
从左表中取出每一条记录,去右表中与所有的记录进行匹配:匹配必须是某个条件在左表中与右表中相同最终才会保留结果,否则不保留
笛卡尔积:
由没有联结条件的表关系返回的结果为笛卡尔积。检索出的行的数目将时第一个表中的行数乘以第二个表中的行数。通俗的说就是查询所得的结果行数是两张表行数的乘积。
返回笛卡尔积的联结,也称为叉联结cross join。
等值连接:
注意:联结查询非常消耗资源,因此应该注意,不要联结不必要的表。联结的表越多,性能下降越厉害。
五、内联结
内连接有两种写法,一种是inner join,另一种是join,这两种写法都是一样的,可以理解为join是inner join的缩写。还可以看出,等值连接和内连接的效果一样,但是开发中建议使用内连接
-- 列出所有学生学习的课程名称
-- 学生姓名 班级名称 课程名称 学分
SELECT *
FROM student s,banji b,banji_course bc,course c
WHERE s.banji_id=b.id AND b.id=bc.banji_id AND bc.course_id=c.id;
总结:多表查询主要是注意下面两点
1、整个查询涉及到几张表,涉及到几张表就连接这几张表。
2、如果涉及到这几张表的关系搞不清楚,画一下ER图,弄清楚表和表之间的关系(就是根据外键建立的关系)
-- 班级名称 学生数量 SELECT * FROM student as s INNER JOIN banji as b ON s.banji_id=b.id;
把inner join之后查询的结果当成一张表来使用, 在这个结果集里面根据班级id统计每个班级下面学生数量。
-- 班级名称 学生数量 SELECT b.name AS '班级名称',COUNT(*) AS '学生数量' FROM student AS s INNER JOIN banji AS b ON s.banji_id=b.id GROUP BY b.id;
六、inner join on、left join on、right join on区别
inner join on 只有左右两个表有关联的才查询出来
left join on 左表中都显示出来,右表没有显示空
right join on 右表都显示,左表没有显示空
左连接,也成为左外连接:从左表那里返回所有的行,即使在右表中没有匹配的行.left join on
SELECT * FROM student as s RIGHT JOIN banji as b on s.banji_id=b.id;
七、模糊查找
语法形式:字段 like '要查找字符'
说明:
1、like模糊查找用于对字符类型的字段进行字符匹配查找。
2、要查找的字符中,有两个特殊含义的字符:% , _:
2.1: %含义是:代表0或多个的任意字符
2.2: _含义是:代表1个任意字符
2.3: 这里的字符都是指现实中可见的一个"符号",而不是字节。
3、语法:like '%关键字%'
SELECT * FROM student WHERE `name` LIKE '张%'; -- 以张开头
SELECT * FROM student WHERE `name` LIKE '张_'; -- 以张开头,而且名字是两个字
SELECT * FROM student WHERE `name` LIKE '%张%'; -- 名字里面只要有张就可以
注意: NULL
通配符%看起来像是可以匹配任何东西,但有个例外,这就是NULL,
SELECT * FROM student WHERE `name` LIKE '%';
不会匹配name为NULL的行
注意:
SQL的通配符很有用,但这种功能是有代价的,即通配符搜索要消耗更长的处理时间,使用通配符的技巧:
1、不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
2、在确实需要使用通配符时,也尽量不要把它们用在搜索模式的开始处 '%张'。把通配符置于开始处,搜索起来是最慢的。
八、作业
作业: create table goods ( goods_id mediumint(8) unsigned primary key auto_increment, goods_name varchar(120) not null default '', cat_id smallint(5) unsigned not null default '0', brand_id smallint(5) unsigned not null default '0', goods_sn char(15) not null default '', goods_number smallint(5) unsigned not null default '0', shop_price decimal(10,2) unsigned not null default '0.00', market_price decimal(10,2) unsigned not null default '0.00', click_count int(10) unsigned not null default '0' ) ; insert into `goods` values (1,'kd876',4,8,'ecs000000',1,1388.00,1665.60,9), (4,'诺基亚n85原装充电器',8,1,'ecs000004',17,58.00,69.60,0), (3,'诺基亚原装5800耳机',8,1,'ecs000002',24,68.00,81.60,3), (5,'索爱原装m2卡读卡器',11,7,'ecs000005',8,20.00,24.00,3), (6,'胜创kingmax内存卡',11,0,'ecs000006',15,42.00,50.40,0), (7,'诺基亚n85原装立体声耳机hs-82',8,1,'ecs000007',20,100.00,120.00,0), (8,'飞利浦9@9v',3,4,'ecs000008',1,399.00,478.79,10), (9,'诺基亚e66',3,1,'ecs000009',4,2298.00,2757.60,20), (10,'索爱c702c',3,7,'ecs000010',7,1328.00,1593.60,11), (11,'索爱c702c',3,7,'ecs000011',1,1300.00,0.00,0), (12,'摩托罗拉a810',3,2,'ecs000012',8,983.00,1179.60,13), (13,'诺基亚5320 xpressmusic',3,1,'ecs000013',8,1311.00,1573.20,13), (14,'诺基亚5800xm',4,1,'ecs000014',1,2625.00,3150.00,6), (15,'摩托罗拉a810',3,2,'ecs000015',3,788.00,945.60,8), (16,'恒基伟业g101',2,11,'ecs000016',0,823.33,988.00,3), (17,'夏新n7',3,5,'ecs000017',1,2300.00,2760.00,2), (18,'夏新t5',4,5,'ecs000018',1,2878.00,3453.60,0), (19,'三星sgh-f258',3,6,'ecs000019',12,858.00,1029.60,7), (20,'三星bc01',3,6,'ecs000020',12,280.00,336.00,14), (21,'金立 a30',3,10,'ecs000021',40,2000.00,2400.00,4), (22,'多普达touch hd',3,3,'ecs000022',1,5999.00,7198.80,16), (23,'诺基亚n96',5,1,'ecs000023',8,3700.00,4440.00,17), (24,'p806',3,9,'ecs000024',100,2000.00,2400.00,35), (25,'小灵通/固话50元充值卡',13,0,'ecs000025',2,48.00,57.59,0), (26,'小灵通/固话20元充值卡',13,0,'ecs000026',2,19.00,22.80,0), (27,'联通100元充值卡',15,0,'ecs000027',2,95.00,100.00,0), (28,'联通50元充值卡',15,0,'ecs000028',0,45.00,50.00,0), (29,'移动100元充值卡',14,0,'ecs000029',0,90.00,0.00,0), (30,'移动20元充值卡',14,0,'ecs000030',9,18.00,21.00,1), (31,'摩托罗拉e8 ',3,2,'ecs000031',1,1337.00,1604.39,5), (32,'诺基亚n85',3,1,'ecs000032',4,3010.00,3612.00,9); -- 分类表 create table category ( cat_id smallint unsigned auto_increment primary key, cat_name varchar(90) not null default '', parent_id smallint unsigned ); INSERT INTO `category` VALUES (1,'手机类型',0), (2,'CDMA手机',1), (3,'GSM手机',1), (4,'3G手机',1), (5,'双模手机',1), (6,'手机配件',0), (7,'充电器',6), (8,'耳机',6), (9,'电池',6), (11,'读卡器和内存卡',6), (12,'充值卡',0), (13,'小灵通/固话充值卡',12), (14,'移动手机充值卡',12), (15,'联通手机充值卡',12);
select where常用运算符:
NOT可以否定IN、BETWEEN、EXISTS
-- 1:主键为32的商品 -- 2:不属第3栏目的所有商品(category中id为3) -- 3:本店价格高于3000元的商品 -- 4:本店价格低于或等于100元的商品 -- 5:取出第4栏目或第11栏目的商品 -- 6:取出100<=价格<=500的商品 -- BETWEEN AND是能取到开始和结束的值,等价于>= and <= -- 7:取出不属于第3栏目且不属于第11栏目的商品(and,或not in分别实现) -- 8:取出价格大于100且小于300,或者大于4000且小于5000的商品() -- 要适当的加括号(括号的优先级比AND和OR优先级高),不加括号数据也正确,只是巧合,因为AND优先级要高于OR优先级,写出有歧义的 语句并不能显出你多厉害 -- 任何时候使用AND和OR操作符时候,都应该加括号明确的分组操作符,不要过分依赖默认求值顺序,及时它确实如你希望的那样。 使用括号没有什么坏处,它能消除歧义。 -- select * from goods where () OR (); -- 9:取出第3个栏目下面价格<1000或>3000,并且点击量>5的系列商品 -- 10:取出第1个栏目下面的商品(注意:1栏目下面没商品,但其子栏目下有) -- 11:取出名字以"诺基亚"开头的商品 -- like 模糊匹配 -- % 通配任意字符 -- _ 通配单一字符 -- 12:取出名字为"诺基亚nxx"的手机 -- 13:取出名字不以"诺基亚"开头的商品 -- 14:取出第3个栏目下面价格在<1000或者>3000,并且点击量>5 "诺基亚"开头的系列商品 -- 15:把goods表中商品名为'诺基亚xxxx'的商品,改为'HTCxxxx', -- 提示:大胆的把列看成变量,参与运算,甚至调用函数来处理 . -- substr(),concat(),trim(),ltrim(),rtrim() SELECT goods_id,goods_name FROM goods WHERE goods_name LIKE '诺基亚%';
SELECT goods_id,SUBSTR(goods_name, 4) FROM goods WHERE goods_name LIKE '诺基亚%'; SELECT goods_id,CONCAT('HTC',SUBSTR(goods_name,4)) FROM goods WHERE goods_name LIKE '诺基亚%'; public static void main(String[] args) { String goodsName = "诺基亚原装5800耳机"; String name = "HTC" + goodsName.substring(3); System.out.println(name);//HTC原装5800耳机 } -- 15:计算指定分类(cat_id=3)下面商品的平均价格,计算评价价格时候去掉重复价格的 -- 只包含不同的值,指定DISTINCT参数 SELECT AVG(DISTINCT shop_price) AS avg_price FROM goods WHERE cat_id=3; -- 16:组合聚集函数,SELECT可以根据需要包含多个聚集函数 -- goods_count price_min price_max price_avg SELECT COUNT(*) AS goods_count, MIN(shop_price) AS price_min, MAX(shop_price) AS price_max, AVG(shop_price) AS price_avg FROM goods; order by 与 limit: LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数。 LIMIT 接受一个或两个数字参数。参数必须是一个整数常量。 如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0(而不是 1) -- 1、按照栏目由低到高排序,栏目内部按照价格由高到低排序 -- 2、取出价格最高的前三名商品 -- limit offset,rowcount -- limit 偏移到哪个位置,往下数几个 -- 3、取出点击量第三名到第五名的商品
group by having:
1、这个店积压的货款:
int goods_num = 17;
double shop_price = 58.0;
System.out.println(goods_num * shop_price);
SELECT goods_number*shop_price FROM goods;
SELECT SUM(goods_number*shop_price) FROM goods;
-- 2、查询该店每个栏目下挤压的货款
-- 3、查询该店每个栏目下挤压的货款 > 100
备注:我们经常发现,用 GROUP BY 分组的数据确实是以分组顺序输出的。但并不总是这样,这不是 SQL 规范所要求的。此外,即使特定的 DBMS 总是按给出的 GROUP BY 子句排序数据,用户也可能会要求以不同的顺序排序。应该提供明确的 ORDER BY 子句,即使其效果等同于 GROUP BY 子句。
SELECT cat_id,SUM(goods_number*shop_price) AS total_price
FROM goods GROUP BY cat_id HAVING total_price>100
ORDER BY total_price,cat_id;