赞
踩
方法一:创建表的时候创建
CREATE TABLE mytable(
id int primary key not null,
username VARCHAR(16) not null,
index myindex(username)
)
方法二:后期插入
-- 添加索引
CREATE index myindex ON mytable(username);
-- 或
ALTER table mytable ADD INDEX myindex(username);
-- 查看索引
show index from mytable
-- 删除索引
drop index myindex ON mytable;
-- 或
ALTER TABLE mytable DROP INDEX myindex;
-- explain 查询当前语句所使用的索引
EXPLAIN select * from mytable where username = "张三"
EXPLAIN select * from mytable where id =1
运行结果如下
如果possible_keys为null则没使用索引
常见的索引类型有:主键索引、唯一索引、普通索引、全文索引、组合索引。
具体查看索引是否有效,可以使用explain命令查询。
事务(Transaction):一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)。
事务只和DML(增删改)语句有关,或者说DML语句才有事务。(DML)数据操纵语言。
举例:银行转账,执行了两条修改语句,必须两条语句都执行成功才提交事务,否则一条成功,另一条失败就回滚事务。
在mysql中,事务的常规操作包括:开启事务、提交事务与回滚事务。
-- 开启事务
START TRANSACTION
-- 或
BEGIN TRANSACTION
commit;
rollback;
4.举例
手动开启事务,向t_act表中插入一行记录,提交事务。代码如下所示:
start transaction; -- 手动开启事务
insert into t_act values(3,300); -- 更新操作
commit; -- 提交事务,commit之后即改变底层数据库
select * from t_act;
手动开启事务,向t_act表中插入一行记录,回滚事务。代码如下所示:
start transaction; -- 手动开启事务
insert into t_act values(4,200); -- 更新操作
rollback; -- 回滚事务
select * from t_act; -- 回滚事务会撤销更新操作,代码执行前后数据表中的数据没有发生任何变化。
事务的隔离级别有4个:
事务隔离级别与一致性的关系也可以用下表表示:
表结构和数据: create table tb_test( id int not null primary key auto_increment, text varchar(200) default null ) insert tb_test(text) values('first row'),('second row'),('third row') ------------------------------------------------------------------------------- 未提交读(READ UNCOMMITED): A会话: start transaction; update tb_test set text='1 row' where id=1 B会话: SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; select * from tb_test where id=1; A会话: Rollback; select * from tb_test B会话: select * from tb_test where id=1; 此时会发现B会话可以拿到A未提交的数据,即“脏数据”,如果拿着脏数据去参与运算,肯定会发生错误。 并且发现在B会话中两次查询的结果不一致,即“不可重复读” 接下来演示“幻读”,幻读即“两次查询的结果的行数不一致” B会话: commit;(注意:当开启了一个事务后,一定要以commit或rollback结束事务) A会话: START TRANSACTION; insert tb_test(text) values('forth row') B会话: SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; select * from tb_test; A会话: rollback B会话: select * from tb_test; commit; 此时发现B会话拿到了A会话中添加的行,但是A会话最终回滚了该行数据,此类虚幻的数据即“幻读” ------------------------------------------------------------------------------- 已提交读(READ COMMITTED): B会话: select * from tb_test; -- 观测数据 B会话: SET TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; SELECT * FROM tb_test A会话: START TRANSACTION; UPDATE tb_test SET TEXT='1 row' where id=1; B会话: SELECT * FROM tb_test 此时会发现B会话中查询到的依然是A修改之前的数据,避免了“脏读”出现。 A会话: commit; B会话: SELECT * FROM tb_test commit; 当A会话提交事务后,数据真正被改写,此时B会话中已经查到了被修改后的数据。 ------------------------------------------------------------------------------- 重复读(REPEATABLE READ): A会话: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; SELECT * FROM tb_test 注意观测此时的A会话中第三行结果 B会话: START TRANSACTION; update tb_test set text='3 row' where id=3; SELECT * FROM tb_test 注意观测此时的B会话中第三行结果 A会话: SELECT * FROM tb_test 注意观测此时的A会话中第三行结果,与之前A会话的结果完全一致,不受B会话的任何影响 B会话: commit; A会话: commit; SELECT * FROM tb_test 当A会话提交事务后,得到的数据才与B会话一致。 ------------------------------------------------------------------------------- 可串行化(SERIALIZEABLE): 可串行化隔离级别最高,不会出现脏读、幻读、不可重复读,但是由于独占性太高,会显著阻塞数据库并发性能。 A会话: SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; SELECT * FROM tb_test B会话: update tb_test set text='2 row' where id=2; 此时会发现B会话纹丝不动。 A会话: commit; 此时B会话的命令会立刻执行成功。
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库显得尤其重要,也更加复杂。
与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
3.2.1、MySQL中悲观锁的实现主要有两种:共享锁(S)与排它锁(X),它们分别对应数据的读和写操作。
注意:共享锁之间是兼容的,而排它锁与其他任意锁都不兼容。因此我们在数据库中可以并行读,但只能串行写,只有这样才不会发生线程竞争。
3.2.2、锁的粒度
锁的粒度就是你要锁定的范围是多大。使用锁时,锁定的东西越少,对其他竞争者影响越小,这个东西的大小就是锁粒度。
例如卫生间里的洗漱台,马桶,浴缸,如果分别隔开(三个区域可以分别锁定,粒度小),就可以同时给三个人使用。你关上浴室的门,别人还是可以洗手的;但如果卫生间的区域不隔开,一个人进卫生间锁上门,其他人就进不去了。可见,锁的粒度越小,锁定的资源越少,对其他用户的影响就越小,当然,对系统的开销也越大。
锁的粒度分类:
创建存储过程,代码如下所示:
CREATE PROCEDURE mypro(in a int, in b int, out sum int)
BEGIN
SET sum = a + b;
END
运行结果如下:
也可以在Navicat客户端“函数”节点下查看过程,如下图所示:
语法如下:
call mypro(1,2,@s);--调用存储过程
select @s;--显示过程输出结果
运行结果如下:
分析前面任务中的语法如下:
存储过程根据参数可分为四种类别:
MySQL中的存储过程类似java中的方法。既然如此,在存储过程中也同样可以使用变量。java中的局部变量作用域是变量所在的方法,而MySQL中的局部变量作用域是所在的存储过程。
DECLARE var_name[,...] type [DEFAULT value]
declare用于声明变量;
variable_name表示变量名称;
datatype为MySQL的数据类型;
default用于声明默认值;
例如:
declare name varchar(20) default "张三";
SET 变量名 = 表达式值 [,variable_name = expression ...]
在存储过程中使用变量,代码如下所示:
create PROCEDURE mypro2() -- 定义存储过程
BEGIN
DECLARE name varchar(20); -- 定义变量
set name='张三'; -- 给变量赋值
select * from mytable where username = name;
END
CALL mypro2(); -- 调用过程
IF语句包含多个条件判断,根据结果为TRUE、FALSE执行语句,与编程语言中的if、elseif、else语法类似。
使用:
-- if条件语句
create PROCEDURE mypro3(in num int)
BEGIN
if num<0 then -- 条件开始
select '负数';
elseif num=0 THEN
select '为0';
else
select '正数';
END IF; -- 条件结束
end;
call mypro3(-1)
case是另一个条件判断的语句,类似于编程语言中的choose、when语法。MySQL中的case语句有两种语法格式。
使用
-- case条件语句
create PROCEDURE mypro4(in num int)
BEGIN
CASE
WHEN num<0 then select '负数';
WHEN num=0 then select '为0';
ELSE select '正数';
END CASE;
END
call mypro4(5)
while语句的用法和java中的while循环类似。
使用:
-- while循环
create PROCEDURE mypro5(out sum int)
BEGIN
DECLARE i int DEFAULT 0;
set sum=0;
WHILE i<10 do -- 循环开始
set i = i + 1;
set sum = sum + i;
end WHILE; -- 循环结束
end
-- 调用过程
call mypro5(@sum);
select @sum
repeat语句的用法和java中的do…while语句类似,都是先执行循环操作,再判断条件,区别是repeat表达式值为false时才执行循环操作,直到表达式值为true停止。
使用
-- repeat循环
create procedure mypro6(out sum int)
BEGIN
declare i int default 0;
set sum = 0;
REPEAT -- 循环开始
set i=i+1;
set sum = sum+i;
UNTIL i>=10
end REPEAT;
END
call mypro6(@sum);
select @sum
循环语句,用来重复执行某些语句。执行过程中可使用leave语句或iterate跳出循环,也可以嵌套IF等判断语句。
leave语句效果相当于java中的break,用来终止循环;
iterate语句效果相当于java中的continue,用来结束本次循环操作,进入下一次循环。
使用:
-- loop循环语句 create procedure mypro7(out sum int) BEGIN DECLARE i int default 0; set sum = 0; loop_tcc:LOOP -- 循环开始 set i = i+1; set sum = sum+i; IF i>=10 THEN LEAVE loop_tcc; -- 离开圈圈 end if; end LOOP loop_tcc; -- 循环结束 END call mypro7(@sum); select @sum
存储过程的管理主要包括:显示过程、显示过程源码、删除过程。比较简单的方式就是利用navicat客户端工具进行管理,鼠标点击操作即可,如下图所示:
显示全部存储过程,代码如下所示:
-- 显示所有存储过程
SHOW PROCEDURE STATUS;
结果如下:
显示特定数据库的存储过程,代码如下:
-- 显示特定数据库的存储过程
SHOW PROCEDURE STATUS WHERE db = 'mysql1';
结果如下:
显示特定模式的存储过程,要求显示名称中包含“my”的存储过程,代码如下所示:
-- 显示特定模式的存储过程
SHOW PROCEDURE STATUS WHERE name like '%my%';
-- 显示存储过程源码
SHOW CREATE PROCEDURE mypro;
结果:
drop PROCEDURE mypro;
视图中存储了可以返回结果集的sql查询语句,当用在from子句时,内部的sql查询语句就会被执行。
MySQL视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。
优点:
缺点
基于单表创建视图
-- 创建视图
create view view1
AS
select studentname,sex,age from student
创建视图的语法只是在原来的select语句前添加create view 视图名 as
就可以了。
创建带别名的视图
-- 创建带别名的视图
create view view2(姓名,性别,年龄)
AS
select studentname,sex,age from student
select * from view2
结果如下:
基于多表创建视图
-- 创建多表的视图
create view view3
as
select s.studentname,s.sex,s.age,c.clazzname
from student s join clazz c on s.clazzid=c.clazzid
select * from view3
- 结果如下: ![1645779652582](E:\SrpingNotePad\img\1645779652582.png) ### 3、视图更新数据 在MySQL中,视图不仅是可查询的,而且是可更新的。这意味着您可以使用INSERT或UPDATE语句通过可更新视图插入或更新基表的行。另外,您可以使用DELETE语句通过视图删除底层表的行。 但是,要创建可更新视图,定义视图的SELECT语句不能包含以下任何元素: 1. 聚合函数; 2. distinct子句; 3. groupby子句; 4. having子句; 5. union和unionall子句; 6. 外连接; **注意:不建议使用基于多表创建的视图进行更新操作。** - 测试: ```mysql -- 创建单表视图 create view view1 AS select studentname,sex,age from student -- 创建多表的视图 create view view3 as select s.studentname,s.sex,s.age,c.clazzname from student s join clazz c on s.clazzid=c.clazzid select * from view1 -- 基于单表视图的添加(成功) insert into view1 values('张三','女',30); -- 基于单表视图的修改(成功) update view1 set age = age+1; select * from view3; -- 基于多表视图的添加(失败) insert into view3 values('李四','女',30); -- 基于多表视图的修改(成功) update view3 set age = age+1;
有时候,创建一个视图来显示表的部分数据。然而,简单视图是可更新的,因此可以更新通过视图不可见的数据。此更新使视图不一致。为了确保视图的一致性,在创建或修改视图时使用WITHCHECKOPTION子句。
测试:
-- 创建一个年龄只能在30岁以上的视图 create view view4 as select studentname,sex,age from student where age>=30 -- 虽然添加成功了,但是不合理view4视图里看不到年龄在30岁以下的数据 -- 从示例结果中可以看到,新插入的学生在视图中是查不到的,为了确保视图的一致性,用户只能显示或更新通过视图可见的数据,则在创建或修改视图时使用WITH CHECK OPTION。 insert into view4 values("王五","男",21); -- 创建一个年龄只能在30岁以上的视图2 关键字:WITH CHECK OPTION create view view5 as select studentname,sex,age from student where age>=30 WITH CHECK OPTION -- 添加失败,因为年龄小于30岁 insert into view5 values("王五","男",21);
没有特殊理由的情况下,建议都带上WITHCHECKOPTION。
视图管理最简单的方式就是通过navicat
客户端工具鼠标点击操作。
-- 查看视图定义
show create view view2;
结果:
视图本质上也是表,因此查看视图可以使用showtables命令查看。
-- 展示视图
show tables
结果:
使用alter view
替换create view
就可以实现视图的修改操作,还可以使用CREATE OR REPLACE VIEW
语句来创建或替换现有视图。如果一个视图已经存在,MySQL只会修改视图。如果视图不存在,MySQL将创建一个新的视图。
-- 修改视图
ALTER view view1
AS
select studentname,sex,age from student
使用drop view 视图名
命令就可以删除视图了。
-- 删除视图
drop view view1
MySQL中的函数和JavaScript中的函数作用类似,就是执行特定任务的代码块。
其实,大家已经有过在MySQL中使用函数的经验了,比如我们获得系统时间,可以用now()
函数,求平均值可以用avg()
函数等等。这些系统定义好的函数我们称为系统函数,可以直接拿来使用,但有些时候我们需要完成特定功能,就需要自己定义函数。用户自己定义的函数,称为自定义函数。
语法:
create function 函数名([参数列表]) returns数据类型
begin
sql语句;
return 值;
end
创建无参函数,返回学号为“3”的学生姓名。代码如下所示:
-- 创建函数 查询学号为3的学生姓名
create FUNCTION myfun1() returns varchar(20)
BEGIN
DECLARE name varchar(20);
select studentname into name from student where studentid=3;
return name;
end
-- 调用函数
select myfun1();
结果:
创建带参数的函数 根据学号查询学生姓名
-- 创建带参数的函数 根据学号查询学生姓名
create FUNCTION myfun2(sid int) returns varchar(20)
BEGIN
DECLARE name varchar(20);
select studentname into name from student where studentid=sid;
return name;
end
select myfun2(2);
注意语法中的几个要点:
returns
,returns后跟返回值类型,类型必须是MySQL中的类型;into
关键词。-- 显示所有函数
show function STATUS
结果:
-- 显示函数源码
show create function myfun1
结果:
-- 删除函数
drop function myfun1
函数和过程统称为存储在数据库中的实现特定功能的代码块,但是它们之间也有一些区别:
select
进行调用;过程使用call
进行调用;in\out\inout
参数。 触发器是和表关联的特殊的存储过程,可以在插入,删除或修改表中的数据时触发执行,比数据库本身标准的功能有更精细和更复杂的数据控制能力。
语法:
创建触发器的技巧就是记住触发器的四要素:
table
;insert/update/delete
;after/before
;语法如下:
CREATE TRIGGER trigger_name trigger_time trigger_event ON tb_name FOR EACH ROW trigger_stmt
trigger_name
:触发器的名称;tirgger_time
:触发时机,为BEFORE或者AFTER;trigger_event
:触发事件,为INSERT、DELETE或者UPDATE;tb_name
:表示建立触发器的表名,在哪张表上建立触发器;trigger_stmt
:触发器的程序体,可以是一条SQL语句或者是用BEGIN和END包含的多条语句;FOR EACH ROW
表示任何一条记录上的操作满足触发事件都会触发该触发器。注意:对同一个表相同触发时间的相同触发事件,只能定义一个触发器。
使用
假如你正在开发一个网上购物的应用,使用的数据库(shop)中有一张商品表(product)和订单表(orders),表结构及测试数据如下所示:
CREATE TABLE `orders` ( `oid` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单编号', `pid` int(11) NOT NULL COMMENT '商品编号', `num` int(11) NOT NULL COMMENT '购买数量', PRIMARY KEY (`oid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `product` ( `pid` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品编号', `pname` varchar(255) NOT NULL COMMENT '商品名称', `num` int(11) NOT NULL COMMENT '库存', PRIMARY KEY (`pid`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; INSERT INTO `product` VALUES ('1', 'p30', '10'); INSERT INTO `product` VALUES ('2', 'mate20', '50'); INSERT INTO `product` VALUES ('3', 'nova5', '100');
当用户购买商品时,需要往订单表插入订单记录,同时更新商品表库存信息。例如用户买了3个p30,我们需要做两件事:
往订单表插入一条记录:
insert into orders values(null,1,3);
更新 p30 的库存:
update product set num = num-3 where pid=1;
这两条 sql 语句实现的是一个业务操作,从数据库的层面来讲,这样的业务我们就可以用触发器来处理。
任务: 创建触发器,当用户购买 p30 时,同时更新 p30 库存记录,代码如下所示:
-- 创建触发器
create TRIGGER mytg1
after insert on orders -- 在orders表添加之后触发
for each row -- 针对每一行(固定写法)
BEGIN
update product set num = num - 3 where pid=1;
end
-- 往订单表插入记录
insert into orders values(null,1,3)
-- 查询商品表商品库存更新情况
select * from product
结果:
测试数据商品表p30有10台,当订单表插入3台p30的订单记录后,触发器就会执行更新商品表的操作,使p30库存减少3。当然这里还存在一个问题,因为代码中的数值是写死的,无论用户买什么商品,买几个,每次购买都会让p30的库存减3,所以我们需要将固定数值替换成变量,这就要用到触发订单表触发器的新纪录。
MySQL中定义了NEW和OLD,用来表示触发器的所在表中,触发了触发器的那一行数据:
创建触发器,当用户购买商品时,同时更新对应商品库存记录,代码如下所示:
-- 删除之前的触发器 drop trigger if EXISTS mytg1 -- 创建添加的触发器 create TRIGGER mytg1 after insert on orders -- 在orders表添加之后触发 for each row -- 针对每一行(固定写法) BEGIN -- 会把新添加的数据放到new里面,使用新数据的时候只需要new.列名 update product set num = num - new.num where pid=new.pid; end -- 往订单表插入记录(id为1的商品库存减3) insert into orders values(null,1,3) -- 插入其他商品记录进行测试(id为2的商品库存减5) insert into orders values(null,2,5) -- 查询商品表商品库存更新情况 select * from product
用户下订单,同时更新商品库存的问题已经解决了,但是问题并不总是那么简单,一个完整的应用程序还要考虑很多问题。比如用户不想要了,撤销订单,商品的库存是否要恢复呢?
创建触发器,当用户删除订单时,同时更新对应商品库存记录,代码如下所示:
-- 创建删除的触发器
create trigger mytg2
after delete on orders -- 在orders表数据被删除之后触发
for each ROW
BEGIN
-- old代表被删除的数据 当订单表数据被删除时,对应商品的库存就会加回来
update product set num = num+old.num where pid=old.pid;
END
-- 删除订单记录
delete from orders where oid=7
-- 查询商品表商品库存更新情况
select * from product
两者在一般的触发器中并没有什么区别,但是有的时候有区别,比如刚才的案例中,我们细想一下还会发现一个问题,如果我们购买的商品数量超过库存会怎样?
-- 往订单表插入记录
insert into orders values(null,1,20);
-- 查询商品表商品库存更新情况
select * from product;
结果:
P30的库存被买成负数了,这明显是错误的,想要避免这个问题,就需要使用before触发器,下订单前,先查看商品库存,如果购买数量超过库存,则修改订单数量为商品最大库存。
创建触发器,当用户删除订单时,同时更新对应商品库存记录,当订单商品数量超过库存时,修改订单数量为最大库存,代码如下所示:
-- 删除创建的添加触发器,防止两个添加的触发器同时触发 drop trigger if EXISTS mytg3 -- 如果订单的商品数量超过库存,则把订单的数量变为库存的最大值 create trigger mytg3 before insert on orders -- 在订单表添加之前执行 for EACH ROW BEGIN DECLARE n int default 0; -- 定义变量n -- 把订单的商品库存数量查询到赋值给变量n select num into n from product where pid=new.pid; -- 如果订单数量大于对应商品的库存数量则把订单的数量改为对应商品库存的最大数量 IF new.num>n then set new.num=n; end if; update product set num=num-new.num where pid=new.pid; END -- 添加订单数量大于商品库存测试 insert into orders values(null,1,20)
结果:
从运行结果可以看到,虽然用户购买了20台p30,但实际只购买了10台,并且商品的库存也变为0,不会出现负数了。
drop trigger if EXISTS 触发器名称
-- 查看触发器
show triggers
游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行相应的操作。游标有下面这些特征:
创建游标:
创建游标的语法包含四个部分:
declare 游标名 cursor for select 语句
;open 游标名
;fetch 游标名 into 变量名[,变量名]
;close 游标名
;使用:
创建一个过程p1,使用游标返回test数据库中student表的第一个学生信息。代码如下所示:
-- 定义存储过程 使用游标 create PROCEDURE p1() BEGIN -- 定义三个变量用于存放查询出来的三个列 DECLARE id int; declare name varchar(20); -- 定义的变量名不能和查询的列名一致,不然查不出来 declare sage int; -- 声明游标 declare mc CURSOR for select studentid,studentname,age from student; -- 打开游标 open mc; -- 提取结果 FETCH mc into id,name,sage; -- 显示获取的结果 select id,name,sage; -- 关闭游标 close mc; END -- 调用过程 call p1();
结果:
在test数据库创建一个student2表,创建一个过程p2,使用游标提取student表中所有学生信息插入到student2表中。代码如下所示:
-- 把查出来的数据循环添加的新表中 create PROCEDURE p2() BEGIN -- 定义变量 declare id int; declare name varchar(20); declare sage int; -- 声明游标 declare mm CURSOR for select studentid,studentname,age from student; -- 打开游标 open mm; -- 提取结果 LOOP -- 循环提取数据 FETCH mm into id,name,sage; -- 将提取的每一行数据插入到student2中 insert into student2 values(id,name,sage); end LOOP; -- 关闭游标 close mm; end call p2();
虽然数据都插入到student2表中了,但是在调用p2的时候会报错。具体解决方法请查看下图:
由于各种原因,数据库中的重复事件发生很多。查找重复值是使用数据库时必须处理的重要任务之一。例如在一个购物应用中,商品表信息如下所示:
使用sql语句查询表中重复的商品记录,代码如下所示:
-- 使用sql语句查询表中重复的商品记录,代码如下所示:
-- 使用分组以后查询的只能是被分组的列和聚合函数,having后跟分组的条件
select pname,count(pname)
from product
group by pname
having count(pname)>1
结果:
任务二、使用sql语句查询表中商品名称和数量都重复的记录,代码如下所示:
-- 使用sql语句查询表中商品名称和数量都重复的记录,代码如下所示:
select pname,num,count(*)
from product
group by pname,num
having count(pname)>1 and count(num)>1
结果:
在上一小节中,我们展示了如何在表中找到重复的值。当确定了表中有重复的行,您可能需要删除它们来清理这些不必要的数据。
任务一、使用sql语句删除商品名称重复的记录,只保留id最大的记录,代码如下所示:
-- 使用sql语句删除商品名称重复的记录,只保留id最大的记录,代码如下所示:
delete p1 from product p1 inner join product p2
where p1.pname=p2.pname and p1.pid<p2.pid;
-- 查询商品表
select * from product;
结果:
删除后再查询商品表,发现id为1和4的p30记录都被删掉了。如果要保留id最小的记录,把p1.pid<p2.pid中的小于号,改成大于号就行了。
有些业务需要从表中选择随机记录,比如:
在博客中选择一些随机的帖子,并在侧边栏中显示;
在店铺商品中选择一些随机的商品,并推荐到侧边导购栏;
在画廊中选择随机图片,并将其用作精选照片。
任务一、使用sql语句从student表中随机选择1名学生记录,代码如下所示
-- 使用sql语句从student表中随机选择1名学生记录,代码如下所示:
-- rand():随机排序 limit:取第一条,取3条:limit 3
select * from student
order by rand()
limit 1
运行两次结果分别如下:
从结果中看到,两次查询获得的学生记录是不一样的。实现原理是rand()
函数为表中每一行记录生成一个随机值,order by
通过这个随机值进行排序,最后limit
只返回第一行。由于每次rand()的值都是不一样的,所以最终返回的结果也不一样。
使用MAX
或MIN
函数可以轻松选择数据库表中最高或最低的记录。但是,选择第n个最高记录有点棘手。
任务一、查询库存第2多的商品信息,代码如下所示:
-- 查询库存第2多的商品信息,代码如下所示:
-- 先降序排一下取前两条,然后把查出来的结果当做一张表进行升序排序取第一条
select * from
(select * from product order by num desc limit 2) as t1
order by num ASC
limit 1;
先按库存降序排列,查询库存最多的2个商品;再以前面查询结果按照升序排列,取第1个记录。当然这个过程也可以简化为以下代码:
-- 简化 limit n-1,1 查询排序后的第几位 n-1需要提前自己计算出具体数值
select * from product order by num limit 1,1
任务一、现有两张表,请你查询出A1字段中,存在t_a表,但是不存在t_b表的数据,表数据如下图所示:
代码如下:
-- 现有两张表,请你查询出A1字段中,存在t_a表,但是不存在t_b表的数据,表数据如下图所示:
select * from t_a where a1 not in (select a1 from t_b)
上面代码先通过子查询得到t_b表中a1字段的所有值,然后在外部查询中使用not in
进行排除。这种方式本身没有错,但是在大量数据(百万级以上)的情况下,尤其是A1字段带有索引的情况下,查询速度会非常慢。除了not in
还可以使用left join
实现,代码如下所示:
-- 上面那个数据大的话查询慢,没使用索引,优化:
select t_a.* from t_a left join t_b
on t_a.a1=t_b.a1
where t_b.b1 is null
结果:
上面代码也可以得到相同的结果,但left join
的效率比not in
要高很多,并且不会让A1字段的索引失效。
注意:代码中有两个条件,分别跟在on和where后面,能不能把where改成and?答案是不能,on和where后面都可以跟条件,但是在left join
中效果是不一样的,on后面跟连接条件,先做表连接,where是对连接查询结果再进行筛选。在inner join
中,因其机制不同,条件放在on后面还是where后面查询结果虽然是一样的,但执行原理和效率上也有一些区别。
现在有一张成绩表,表结构及数据如下所示:
任务一、为了方便查看每个同学的成绩,使用sql语句查询出如下结果:
代码如下:
-- 行转列 为了方便查看每个同学的成绩,使用sql语句查询出如下结果:
select user_name,
MAX(CASE course WHEN '数学' THEN score ELSE 0 END) as 数学,
MAX(CASE course WHEN '语文' THEN score ELSE 0 END) as 语文,
MAX(CASE course WHEN '英语' THEN score ELSE 0 END) as 英语,
sum(score) as 总分
from test_tb_grade
group by user_name
EXISTS
关键字后面的参数是一个任意的子查询,系统对子查询进行运算以判断它是否返回行,如果至少返回一行,那么EXISTS
的结果是TRUE
,此时外层查询语句将进行查询;如果子查询没有返回任何行,那么EXISTS
返回的结果是FALSE
,此时外层语句将不进行查询。
任务一、查询成绩表中科目编号为2的考试成绩中是否存在不及格的学生,如果存在不及格的学生就将参加科目编号2考试的学生编号和成绩全部查询显示出来,代码如下所示:
-- exists 查询 返回true将外层查询语句进行查询,false则不查询外层语句
-- 查询成绩表中科目编号为2的考试成绩中是否存在不及格的学生,
-- 如果存在不及格的学生就将参加科目编号2考试的学生编号和成绩全部查询显示出来,
-- 代码如下所示:
select * from exam where subid=2 and exists
(select * from exam where score<60)
ALL
用在子查询前,通过比较运算符将一个表达式或列的值与子查询返回的一列值中的每一行进行比较,只要有一次比较的结果为FALSE
,则ALL
测试返回FALSE
。
任务一、查询成绩比科目编号为“1”的这门课程的所有成绩都大的学生考试信息,代码如下所示:
-- 查询成绩比科目编号为“1”的这门课程的所有成绩都大的学生考试信息,代码如下所示:
-- 最大值为80
select * from exam where score>
(select max(score) from exam where subid=1)
-- 或 any
select * from exam where score>all(select score from exam where subid=1)
结果:
在这里,>ALL表示大于每一个值。换句话说,它表示大于最大值。例如,>ALL(1,2,3)表示大于3。ANY与子查询在一起使用时,按照比较运算符、表达式或字段对子查询的结果的每一行进行一次计算和比较。只要有一次满足条件,那么ANY的结果就是真。
任务二、查询成绩比科目编号为“1”的任意一个成绩都大的考试信息,代码如下所示:
-- 查询成绩比科目编号为“1”的任意一个成绩都大的考试信息,代码如下所示:any
select * from exam where score>any(select score from exam where subid=1)
结果:
all取比所有数都大的数,any取比随意一个数大的数 all:所有 any:任意
all(1,2,3)相当于大于3 >any(1,2,3)相当于大于1就满足。
注意:=ANY
运算符与IN
等效。<>ANY
运算符则不同于NOT IN
。<>ANY(A,B,C)
表示不等于A,或者不等于B,或者不等于C。NOT IN(A,B,C)
表示不等于A、不等于B并且不等于C。<>ALL
与NOT IN
表示的意思相同。
MySQL UNION
操作符用于连接两个以上的SELECT语句的结果组合到一个结果集合中并删除重复的数据。
任务一、在test数据库中有一张学生表student和一张教师表teacher,表结构及测试数据如下所示:
要求查询所有师生的信息,代码如下所示:
-- union合并查询
-- 在test数据库中有一张学生表student和一张教师表teacher,要求查询所有师生的信息
select * from student
union
select * from teacher
结果:
代码中使用union
将两个表合并到了一起,但是发现一个问题,学生表和老师表中都有一个Jack的记录,合并结果中却只有一个jack记录。这是因为union
在合并时会删除重复记录,相当于distinct
,如果不想去重,可以使用union all
。
-- union默认去重,union all不去重
select * from student
union all
select * from teacher
结果:
注意:使用union查询有以下几个要点:
union
联合的两个select
必须拥有相同数量的列;union
因为要去重,效率远不如union all
。 储存在表中的数据不都是计算机应用程序所需要的,我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。这就是计算字段要发挥的作用。
任务一、在test数据库中有一张users表,表结构及测试数据如下:
在存储用户信息时,姓和名是分开的,现在要求通过sql查询出用户信息,要求姓名用一个字段显示,代码如下:
-- 计算列
-- 在存储用户信息时,姓和名是分开的,
-- 现在要求通过sql查询出用户信息,要求姓名用一个字段显示,代码如下:
select * from users
-- concat拼接
select id,CONCAT(first_name,last_name) as name from users
结果:
计算列除了拼接字符串,还可以进行算术计算。
任务二、在test数据库中有一张goods表,表结构及测试数据如下:
price字段表示单价,discount字段表示折扣,要求查询出商品信息,并计算出商品的售价(即单价*折扣),代码如下:
-- price字段表示单价,discount字段表示折扣,
-- 要求查询出商品信息,并计算出商品的售价(即单价*折扣),代码如下:
select * from goods
select id,name,price*discount as 折扣价,stock from goods
结果:
在sqlserver
和oracle
数据库中,都有系统定义的排名函数,很遗憾,mysql
中目前还没有对排名查询的支持,这就需要我们自己去实现排名查询。
任务一、在schooldb数据库中有一张exam表,表结构及测试数据如下:
要求查询出,科目编号为2的科目成绩及排名,代码如下:
-- @rownum:记录当前行号当做排名
select subid,score,@rownum:=@rownum+1 as rank
from exam a,(select @rownum:=0) b
where subid=2
order by score desc
结果(rank排序)如下:
任务二、从查询结果中看到,排名3,4,5的同学分数是一样的,排名却不一样,分数一样的人,名次也要一样,修改代码如下:
-- 如果成绩相等则排名也相等
select subid,score,
CASE
-- 如果当前行的成绩等于上一行的,则直接显示行号
when @prev=score THEN @rownum
-- 如果当前行的成绩不等于上一行的,则把当前行的成绩赋值给prev变量再让行号加1再显示
when @prev:=score THEN @rownum:=@rownum+1
END as rank
-- @prev存储上一行的成绩
from exam a,(select @rownum:=0,@prev:=null) b
where subid=2
order by score desc
结果(rank排序):
任务三、要求当出现并列名次时,后面的同学进行跳跃排名,实现代码如下:
select subid,score,
-- if(@prev=score,@rownum,@inc):三元运算符 为true则选前面的结果,false为后面的结果
-- @rownum:存储名次,@prev:上一行的分数,@inc:行号
-- 如果当前分数和上一行分数相等,则取上一条数据的名次,如果不相等则取行号(行号一直在+1)
@rownum:=if(@prev=score,@rownum,@inc) as rank, -- 1
@inc:=@inc+1, -- 2
@prev:=score -- 98
from exam a,(select @rownum:=0,@prev:=null,@inc:=1) b
where subid=2
order by score desc
结果(rank排序):
在test数据库中有一张sx_target表,统计各个地区用户点击量,表结构及数据如下:
任务一、查询每个城市每个月份的点击量,代码如下:
SELECT city_name,state_month,sum(sx_sum)
from sx_target
GROUP BY city_name,state_month
结果:
任务二、在现有统计基础上,显示每个城市的合计,以及所有城市的合计,代码如下:
SELECT city_name,state_month,sum(sx_sum)
from sx_target
-- with rollup用来在分组的基础上再进行统计,例如group by a,b with rollup,先根据(a,b)统计,然后根据(a)统计,然后根据(null)统计。
GROUP BY city_name,state_month with rollup
结果:
一些初期从事开发工作的同学会经常问一些莫名奇妙的问题,比如在公司能连数据库,回家就连不上了;能查到数据,但是修改总是失败,面对数据库优化一无所知等等,瞬间就暴漏了自己的水平。本章内容主要是对数据库的管理和维护,这部分内容有助于开发人员快速适应企业级开发时应用的数据库环境,更有助于运维、实施人员快速掌握工作必备技能。
MySQL实现了复杂的访问控制和权限系统,允许您创建用于处理客户端操作的全面的访问规则,并有效防止未经授权的客户端访问数据库系统。
当客户端连接到服务器时,MySQL访问控制有两个阶段:
MySQL在安装时会自动创建一个名为mysql
的数据库,mysql
数据库中有一张user
表,包含用户账户及权限信息。
任务一、查询数据库当前用户信息,代码如下:
-- 使用mysql数据库
use mysql
select * from user
结果:
任务一、创建一个新用户dbadmin,密码为pwd123,代码如下所示:
-- 创建用户
-- identified by用于指定密码,@localhost表示该用户只能从本机登录,如果希望用户可以从任意主机连接,可以使用@’%’。
create user dbadmin@localhost identified by 'pwd123'
select * from user
结果:
任务二、查看新用户dbadmin的权限,代码如下所示:
-- 查看新用户dbadmin的权限
show grants for dbadmin@localhost
结果:
要授予用户权限,您可以使用GRANT语句。
任务一、给新用户dbadmin授予超级管理员权限,代码如下所示:
-- 给用户授予超级管理员权限
-- with grant option:给予了给别人授权的权限
grant all on *.* to dbadmin@localhost with grant option
-- 查看权限
show grants for dbadmin@localhost
结果:
查询结果表明dbadmin
用户拥有所有数据库的所有表的权限,with grant option
表示允许用户向其他用户授权,这样dbadmin
就拥有了超级管理员权限。
授予用户部分权限,代码如下:
-- 创建用户lalala
create user lalala IDENTIFIED by 'lalala'
-- 授予对mysql1库的查询,修改,删除权限
GRANT select,update,delete on mysql1.* to lalala
lalala
账号,密码为lalala
,该用户只能操作mysql1
数据库中的表,并且只能查询、修改和删除表中的数据,如果用lalala
用户在mysql1
数据库创建表就会报错。任务二、使用lalala
用户连接mysql
,然后在mysql1
数据库中新建一张emp表,观察是否能够成功。
create table emp(
id int PRIMARY key,
name varchar(20)
)
结果:
从运行结果可以看到,lalala
的权限不能够在mysql1
数据库创建表,但是可以查询,修改和删除
在MySQL数据库中,可以使用REVOKE撤销用户权限。
任务一、撤销lalala
用户的select
权限,代码如下:
-- 撤销查询权限
revoke select on mysql1.* from lalala;
-- 查看权限
show grants for lalala
结果:
从结果可以看到,lalala
已经不具备查询权限了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。