目录
一、创建数据库
1、详细写法
--创建数据库
create database DBTEST
on --数据文件
(
name = 'DBTEST', --逻辑名称
filename = 'E:\Data\DBTEST.mdf', -- 物理路径和名称
size = 5MB, --文件的初始大小
filegrowth = 2MB --文件增长方式可以写大小也可以写百分比
)
log on --日志文件
(
name = 'DBTEST_log', --逻辑名称
filename = 'E:\Data\DBTEST.ldf', -- 物理路径和名称
size = 5MB, --文件的初始大小
filegrowth = 2MB --文件增长方式可以写大小也可以写百分比
)
删除数据库
drop database DBTEST --删除数据库
建立数据库前,可以先判定数据库中有没有该名称的数据库,如果有就删除,没有就创建
此处理方式最好只在学习阶段使用,在正式生产环境中慎用,操作不当可能会删除重要数据
if exists(select * from sys.databases where name = 'DBTEST')
drop database DBTEST
--创建数据库
create database DBTEST
on --数据文件
(
name = 'DBTEST', --逻辑名称
filename = 'E:\Data\DBTEST.mdf', -- 物理路径和名称
size = 5MB, --文件的初始大小
filegrowth = 2MB --文件增长方式可以写大小也可以写百分比
)
log on --日志文件
(
name = 'DBTEST_log', --逻辑名称
filename = 'E:\Data\DBTEST.ldf', -- 物理路径和名称
size = 5MB, --文件的初始大小
filegrowth = 2MB --文件增长方式可以写大小也可以写百分比
)
与手动创建效果相同
2、简略写法
--创建数据库简写
create database DBTEST1
如果按照上述方式创建数据库,数据库的数据文件和日志文件的相关信息,全部采取默认值
二、创建数据表
建表操作在数据库中执行
建表前,如果该表已经存在,则能在数据库视图中查看到该表
name:表名称
type:表类型 U---用户自定义
--切换数据库
use DBTEST
--创建表基本语法
--create table 表名
--(
-- 字段名1 数据类型
-- 字段名2 数据类型
--)
--判断表是否存在
if exists(select * from sys.objects where name = 'Department' and type = 'U')
drop table Department
--建表(职位、职级、员工)
create table Department
(
--部门编号 primary key:主键,唯一标识表中的每一行记录
-- identity(1,1):自动增长,初始值1,增长步长1
DepartmentID int primary key identity(1,1),
--部门名称
DepartmentName nvarchar(50) not null,
--部门描述
DepartmentRemark text
)
--职级
create table [Rank]
(
--职级编号 primary key:主键,唯一标识表中的每一行记录
-- identity(1,1):自动增长,初始值1,增长步长1
RankID int primary key identity(1,1),
--职级名称
RankName nvarchar(50) not null,
--职级描述
RankRemark text
)
--员工
create table People
(
PeopleId int primary key identity(1,1), --员工编号
DepartmentID int references Department(DepartmentID) not null,--部门(引用外键)
RankID int references [Rank](RankID) not null,--职级(引用外键)
PeopleName nvarchar(50) not null, --姓名
PeopleSex nvarchar(1) default('男') check(PeopleSex='男' or PeopleSex='女') not null, --性别
PeopleBirth datetime not null, --出生日期
PeopleSalary decimal(12,2) check(PeopleSalary>=1000 and PeopleSalary<=1000000), --月薪
PeoplePhone varchar(20) unique not null, --电话号码
PeopleAddress nvarchar(50), --地址
PeopleAddTime smalldatetime default(getdate()) --添加时间
)
外键:引用其他表的主键。如果将其他表的主键作为该表的字段,则在存储该字段数据时会检测数据是否符合主键所在的表
SQL语言中的字符串类型
--C#中字符串为string,但是SQL中没有string这种数据类型
--SQL中的字符串如下:
--char:定长,char(10),无论存储数据是否真的到了10个字节,都要占用10个字节
--char(10)存储'ab',仍然占用10个字节
--varchar:变长,varchar(10),最多占用10个字节
--varchar(10)存储'ab',占用2个字节
--text:长文本
--char,varchar,text前面加n:存储unicode字符,对中文友好
--varchar(50):存储100个字母或者50个汉字
--nvarchar(50):存储100个字母或者100个汉字
三、表结构和约束的维护
注:下述操作在实际业务中尽量减少使用,一般会考虑周全,做好充足预留
1、添加列
--添加列
--alter table 表名 add 新列名 数据类型
--给员工添加一列邮箱
alter table People add PeopleMail varchar(200)
2、删除列
--删除列
--alter table 表名 drop column 列名
--删除邮箱这一列
alter table People drop column PeopleMail
3、修改列
--修改列
--alter table 表名 alter column 列名 数据类型
--修改地址varchar(300)为varchar(200)
alter table People alter column PeopleAddress varchar(200)
--当该列没有数据时执行成功,如果该列已有数据,数据长度大于修改后或者数据类型不符合则会报错
4、维护约束(删除、添加)
----维护约束(删除、添加) --删除约束 alter table 表名 drop constraint 约束名 --删除月薪的约束 alter table People drop constraint CK__People__PeopleSe__48CFD27E --添加约束(check约束) alter table 表名 add constraint 约束名 check(表达式) --添加约束(主键) alter table 表名 add constraint 约束名 primary key(列名) --添加约束(唯一) alter table 表名 add constraint 约束名 unique(列名) --添加约束(默认值) alter table 表名 add constraint 约束名 default 默认值 for 列名 --添加约束(外键) alter table 表名 add constraint 约束名 foreign key 列名 references 关联表名(列名(主键))
四、数据的插入、修改、删除
(1)插入数据
1、标准写法
--向部门表插入数据
insert into Department(DepartmentName,DepartmentRemark)
values('市场部','.....')
insert into Department(DepartmentName,DepartmentRemark)
values('软件部','.....')
insert into Department(DepartmentName,DepartmentRemark)
values('企划部','.....')
2、简写
--简写:不建议使用,当调整表的结构时会插入错误数据
insert into Department values('硬件部','.....')
3、一次性插入多行数据
--一次性插入多行数据
insert into Department(DepartmentName,DepartmentRemark)
select '测试部','......' union
select '实施部','......' union
select '产品部','......'
示例:
------------向职级表中插入数据
insert into [Rank](RankName,RankRemark)
values('初级','.....')
insert into [Rank](RankName,RankRemark)
values('中级','.....')
insert into [Rank](RankName,RankRemark)
values('高级','.....')
------------向员工表插入数据
insert into People([DepartmentID],[RankID],[PeopleName],[PeopleSex],[PeopleBirth],[PeopleSalary],[PeoplePhone],
[PeopleAddress])
values(10,1,'刘备','男','1988-8-8',5000,13888888888,'北京')
(2)数据的修改
--修改数据
update 表名 set 字段1=值1,字段2=值2 where 条件
(3)数据的删除
---删除数据
--方式1
delete from 表名 where 条件
delete from Department --删除表中所有数据,表对象结构依然存在
delete from [Rank]
delete from People
--方式2
drop table People --删除表对象
--方式3
truncate table People --清空表数据,表对象结构依然存在
truncate与delete区别:
1、truncate清空所有数据后面不能带条件,delete可以删除所有数据也可以带条件,删除符合条件的数据
2、自动编号
假设表中的自动编号为1、2、3、4、5
使用truncate清空数据后,编号仍然是1、2、3、4、5
使用delete删除数据,删除的自动编号将永远不存在了
即:如果使用了delete删除了所有数据之后再添加数据,编号变成了6、7、8、9、10
五、数据查询
SQL中常用的运算符
=:等于,比较是否相等及赋值
!=:比较不等于
>:比较大于
<:比较小于
>=:比较大于等于
<=:比较小于等于
IS NULL:比较为空
IS NOT NULL:比较不为空
in:比较是否在其中
like:模糊查询
Between.....and..... :比较是否在两者之间
and:逻辑与
or:逻辑或
not:逻辑非(条件成立,表达式则不成立;条件不成立,表达式则成立)
1、基础查询
--查询所有列所有行
select * from People --*代表所有列
select * from [Rank]
select * from Department
--查询指定列(姓名、性别、生日、月薪、电话)
select PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone from People
--查询指定列(姓名、性别、生日、月薪、电话)显示中文列名
select PeopleName 姓名,PeopleSex 性别,PeopleBirth 生日,PeopleSalary 月薪,PeoplePhone 电话 from People
--查询出员工所在的城市(不需要重复数据显示)
select distinct(PeopleAddress) from People
--假设准备加工资(上调20%),查询出加工资前和加工资后的数据对比的员工数据
select PeopleName,PeopleSex,PeopleSalary 加薪前工资,PeopleSalary*1.2 加薪后工资 from People
2、条件查询
--查询性别为女的员工
select * from People where PeopleSex='女'
--查询工资大于等于10000元的员工信息
select * from People where PeopleSalary>=10000
--查询性别为女,工资大于等于10000元的员工信息(多条件)
select * from People where PeopleSex='女' and PeopleSalary >=10000
--查询月薪大于等于10000的员工,或者月薪大于等于8000的女员工
select * from People where PeopleSalary>=10000 or (PeopleSalary>=8000 and PeopleSex = '女')
--查询出生年月在1980-1-1之后,而且月薪大于10000的女员工
select * from People where PeopleBirth>=1980-1-1 and PeopleSalary >10000 and PeopleSex = '女'
--查询月薪在10000到20000之间的员工信息
--方式1
select * from People where PeopleSalary>=10000 and PeopleSalary <=20000
--方式2
select * from People where PeopleSalary between 10000 and 20000
--查询出地址在武汉或者北京的员工信息
--方式1
select * from People where PeopleAddress = '武汉' or PeopleAddress = '北京'
--方式2
select * from People where PeopleAddress in('武汉','北京')
--排序
--查询所有员工信息,根据工资排序,降序
--asc(ascend):升序,默认升序 desc(descend):降序
select * from People order by PeopleSalary desc
--查询所有员工信息,根据姓名长度排序(降序)
select * from People order by len(PeopleName) desc
--查询出工资最高的5个人信息
select top 5 * from People order by PeopleSalary desc
--查询出工资最高的10%的员工信息
select top 10 percent * from People order by PeopleSalary desc
--null:空值
--查询出地址没有填写的员工信息
select * from People where PeopleAddress is null
--查询出地址已经填写的员工信息
select * from People where PeopleAddress is not null
--查询80后的员工信息
select * from People where PeopleBirth>='1980-1-1' and PeopleBirth <'1990-1-1'
select * from People where PeopleBirth between '1980-1-1' and '1989-12-31'
select * from People where year(PeopleBirth) between '1980' and '1989'
--查询30-40岁,月薪在15000到30000之间的员工
select * from People where (year(GETDATE())-year(PeopleBirth))>=30 and (year(GETDATE())-year(PeopleBirth))<=40
and PeopleSalary>=15000 and PeopleSalary<=30000
select * from People where (year(GETDATE())-year(PeopleBirth)) between 30 and 50
and PeopleSalary between 15000 and 30000
--查询工资比王昭君高的员工信息
select * from People where PeopleSalary>
(select PeopleSalary from People where PeopleName='王昭君')
--查询出和刘备在一个城市的员工信息
select * from People where PeopleAddress =
(select PeopleAddress from People where PeopleName = '刘备')
--查询出星座是巨蟹座的员工信息(6.22-7.22)
select * from People where
(MONTH(PeopleBirth)=6 and DAY(PeopleBirth)>=22) or (MONTH(PeopleBirth)=7 and DAY(PeopleBirth)<=22)
--查询出生肖是鼠的员工信息
--1998 虎 1998%12=6
-- 鼠 、牛、虎、兔、龙、蛇、马、羊、猴、鸡、狗、猪
-- 4 5 6 7 8 9 10 11 0 1 2 3
select * from People where year(PeopleBirth) % 12 = 4
--查询所有员工信息,添加一列,显示生肖
select *,
case
when year(PeopleBirth)%12=4 then '鼠'
when year(PeopleBirth)%12=5 then '牛'
when year(PeopleBirth)%12=6 then '虎'
when year(PeopleBirth)%12=7 then '兔'
when year(PeopleBirth)%12=8 then '龙'
when year(PeopleBirth)%12=9 then '蛇'
when year(PeopleBirth)%12=10 then '马'
when year(PeopleBirth)%12=11 then '羊'
when year(PeopleBirth)%12=0 then '猴'
when year(PeopleBirth)%12=1 then '鸡'
when year(PeopleBirth)%12=2 then '狗'
when year(PeopleBirth)%12=3 then '猪'
else ''
end
生肖 from People
--when之后的判定依据一致,可简化代码
select *,
case year(PeopleBirth)%12
when 4 then '鼠'
when 5 then '牛'
when 6 then '虎'
when 7 then '兔'
when 8 then '龙'
when 9 then '蛇'
when 10 then '马'
when 11 then '羊'
when 0 then '猴'
when 1 then '鸡'
when 2 then '狗'
when 3 then '猪'
else ''
end 生肖
from People
3、模糊查询
模糊查询使用like关键字和通配符结合来实现,通配符具体含义如下:
%:代表匹配0个字符,1个字符或多个字符
_:代表匹配有且只有一个字符
[]:代表匹配范围内
[^]:代表匹配不在范围内
--查询姓刘的员工信息
select * from People where PeopleName like '刘%'
--查询名字中含有“尚”的员工信息
select * from People where PeopleName like '%尚%'
--查询姓刘的员工,名字是两个字
select * from People where PeopleName like '刘_'
select * from People where SUBSTRING(PeopleName,1,1) = '刘' and len(PeopleName) = 2
--查询出电话号码是138开头的员工信息
select * from People where PeoplePhone like '138%'
--查询出电话号码是138开头的,第四位好像是7或者8,最后一个号码是5
select * from People where PeoplePhone like '138[7,8]%5'
--查询出电话号码是138开头的,第四位好像是2-5之间,最后一个号码不是2和3
select * from People where PeoplePhone like '138[2,3,4,5]%[^2,3]'
select * from People where PeoplePhone like '138[2-5]%[^2-3]'
4、分组查询
--分组查询
--(1)根据员工所在地区分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资
--方案1:使用union实现 缺点:union使用场景有限制,必须列数一致 代码量大
select COUNT(*) 人数,MAX(PeopleSalary) 最高工资,MIN(PeopleSalary) 最低工资,
SUM(PeopleSalary) 工资总和,AVG(PeopleSalary) 平均工资 from People
where PeopleAddress='北京'
union
select COUNT(*) 人数,MAX(PeopleSalary) 最高工资,MIN(PeopleSalary) 最低工资,
SUM(PeopleSalary) 工资总和,AVG(PeopleSalary) 平均工资 from People
where PeopleAddress='武汉'
--方案2:group by分组
select PeopleAddress 地区, COUNT(*) 人数,MAX(PeopleSalary) 最高工资,MIN(PeopleSalary) 最低工资,
SUM(PeopleSalary) 工资总和,AVG(PeopleSalary) 平均工资 from People
group by PeopleAddress
--(2)根据员工所在地区分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资
--注意:1985年及以后出生的员工不参与统计
select PeopleAddress 地区, COUNT(*) 人数,MAX(PeopleSalary) 最高工资,MIN(PeopleSalary) 最低工资,
SUM(PeopleSalary) 工资总和,AVG(PeopleSalary) 平均工资
from People
where PeopleBirth<'1985-1-1'
group by PeopleAddress
--(3)根据员工所在地区分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资
--注意:要求筛选出员工人数至少在2人及以上的记录,并且1985年及以后出生的员工不参与统计
select PeopleAddress 地区, COUNT(*) 人数,MAX(PeopleSalary) 最高工资,MIN(PeopleSalary) 最低工资,
SUM(PeopleSalary) 工资总和,AVG(PeopleSalary) 平均工资
from People
where PeopleBirth<'1985-1-1'
group by PeopleAddress
having COUNT(*)>=2
--普通条件放在group by前面,含有聚合函数的条件放在group by后面,因为先分好组才能计算组内的数据
5、多表查询
--多表查询
--笛卡尔乘积
select * from People,Department
--查询结果是将People所有记录与Department所有记录依次排列组合形成新的结果
--People中16条数据 Department中6条数据 则该查询结果为16*6 = 96条记录
--简单多表查询
--(1)查询员工信息,显示部门名称
select * from People,Department where People.DepartmentID = Department.DepartmentID
--(2)查询员工信息,显示职级名称
select * from People,[Rank] where People.RankID = [Rank].RankID
--(2)查询员工信息,显示部门名称,显示职级名称
select * from People,Department,[Rank]
where People.DepartmentID = Department.DepartmentID and People.RankID = [Rank].RankID
--内连接查询
--(1)查询员工信息,显示部门名称
select * from People inner join Department on People.DepartmentID = Department.DepartmentID
--(2)查询员工信息,显示职级名称
select * from People inner join [Rank] on People.RankID = [Rank].RankID
--(2)查询员工信息,显示部门名称,显示职级名称
select * from People
inner join Department on People.DepartmentID = Department.DepartmentID
inner join [Rank] on People.RankID = [Rank].RankID
--简单多表查询与内连接共同的特点:不符合主外键关系的数据不会被显示出来
--外连接(左外连接,右外连接,全外连接)
--左外连:以左表为主表进行数据显示,主外键关系找不到的数据null取代 A left join B = B right join A
--(1)查询员工信息,显示部门名称
select * from People left join Department on People.DepartmentID = Department.DepartmentID
--右外连:以右表为主表进行数据显示,主外键关系找不到的数据null取代
--(1)查询员工信息,显示部门名称
select * from People right join Department on People.DepartmentID = Department.DepartmentID
--全外连:两张表的数据,无论是否符合关系,都要显示
--(1)查询员工信息,显示部门名称
select * from People full join Department on People.DepartmentID = Department.DepartmentID
--自连接(自己连接自己)
--同一个表中存在父子关系情况下可以使用
create table Dept
(
DeptId int primary key, --部门编号
DeptName varchar(50), --部门名称
ParentId int --上级部门名称
)
truncate table Dept
--一级
insert into Dept(DeptId,DeptName,ParentId)values (1,'软件部',0)
insert into Dept(DeptId,DeptName,ParentId)values (2,'硬件部',0)
--二级
insert into Dept(DeptId,DeptName,ParentId)values (3,'软件研发部',1)
insert into Dept(DeptId,DeptName,ParentId)values (4,'软件测试部',1)
insert into Dept(DeptId,DeptName,ParentId)values (5,'软件实施部',1)
insert into Dept(DeptId,DeptName,ParentId)values (6,'硬件研发部',2)
insert into Dept(DeptId,DeptName,ParentId)values (7,'硬件测试部',2)
insert into Dept(DeptId,DeptName,ParentId)values (8,'硬件实施部',2)
select * from Dept
--部门编号 部门名称 上级名称
--3 软件研发部 软件部
--.............................
select A.DeptId 部门编号,A.DeptName 部门名称,B.DeptName from Dept A
inner join Dept B on A.ParentId = B.DeptId
注:
简单多表查询与内连接共同特点:不符合主外键关系的数据不会被显示出来
外连接与内连接的区别:
内连接:不符合主外键关系的数据不会显示出来
外连接:根据外连接的类型不同
(1)左外连:以左表为主进行数据显示,主外键关系不符合的数据null取代
(2)右外连:以右表为主进行数据显示,主外键关系不符合的数据null取代
(3)全外连:两张表的数据,无论是否符合关系,都要显示
六、聚合函数
1、常用的聚合函数
sqlserver中聚合函数主要有:
count:求数量
max:求最大值
min:求最小值
sum:求和
avg:求平均值
2、示例
--聚合函数
--(1)求员工总人数
select COUNT(*) 人数 from People
--(2)求最大值,求最高工资
select MAX(PeopleSalary) 最高工资 from People
--(3)求最小值,求最少工资
select MIN(PeopleSalary) 最低工资 from People
--(4)求和,求所有员工工资总和
select SUM(PeopleSalary) 工资总和 from People
--(5)求平均值,求所有员工的平均工资
select AVG(PeopleSalary) 平均工资 from People
select ROUND(AVG(PeopleSalary),2) 平均工资 from People
--(6)求数量、最大值、最小值、总和、平均值在一行显示
select COUNT(*) 人数,MAX(PeopleSalary) 最高工资,MIN(PeopleSalary) 最低工资,
SUM(PeopleSalary) 工资总和,AVG(PeopleSalary) 平均工资 from People
--(7)查询出北京地区的员工人数,总工资,最高工资,最低工资和平均工资
select COUNT(*) 人数,MAX(PeopleSalary) 最高工资,MIN(PeopleSalary) 最低工资,
SUM(PeopleSalary) 工资总和,AVG(PeopleSalary) 平均工资 from People
where PeopleAddress='北京'
--(8)求出工资比平均高的人员信息
select * from People where PeopleSalary>
(select ROUND(AVG(PeopleSalary),2) 平均工资 from People)
--(9)求数量,年龄最大值,年龄最小值,年龄总和,年龄平均值,在一行显示
--方案1
select COUNT(*) 人数,
MAX(YEAR(GETDATE())-YEAR(PeopleBirth)) 年龄最大值,
MIN(YEAR(GETDATE())-YEAR(PeopleBirth)) 年龄最小值,
SUM(YEAR(GETDATE())-YEAR(PeopleBirth)) 年龄总和,
AVG(YEAR(GETDATE())-YEAR(PeopleBirth)) 年龄平均值
from People
--方案2
select DATEDIFF(YEAR,PeopleBirth,GETDATE()) from People
select COUNT(*) 人数,
MAX(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄最大值,
MIN(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄最小值,
SUM(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄总和,
AVG(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄平均值
from People
--(10)计算出月薪在10000以上的男性员工的最大年龄,最小年龄和平均年龄
select '月薪大于10000' 月薪,'男' 性别,
MAX(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄最大值,
MIN(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄最小值,
AVG(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄平均值
from People where PeopleSalary>10000 and PeopleSex='男'
--(11)统计出所有女员工数量以及最大年龄最小年龄和平均年龄
select COUNT(*) 数量,
MAX(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄最大值,
MIN(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄最小值,
AVG(DATEDIFF(YEAR,PeopleBirth,GETDATE())) 年龄平均值
from People where PeopleSex='女'
--(2)求出年龄比平均年龄高的人员信息
select * from People where DATEDIFF(YEAR,PeopleBirth,GETDATE())>
(select AVG(YEAR(GETDATE())-YEAR(PeopleBirth)) from People 平均年龄)
七、数据库设计
1、数据库结构设计三范式
第一范式:是对属性的原子性,要求属性具有原子性,不可再分解。
如有如下表结构设计:
create table Student
(
StuId varchar(20) primary key,--学号
StuName varchar(20) not null,--学生姓名
StuContact varchar(50) not null--联系方式
)
insert into Student(StuId,StuName,StuContact)
values('001','刘备','QQ:2319440854;Tel:15779160135')
如果我有对电话查询的要求,则会加大查询难度
上述设计不符合第一范式,联系方式这一列并不是不可再分的最小单元,应修改为如下结构
create table Student
(
StuId varchar(20) primary key,--学号
StuName varchar(20) not null,--学生姓名
Tel varchar(20) not null,--电话
QQ varchar(20) not null--QQ
)
insert into Student(StuId,StuName,Tel,QQ)
values('001','刘备','15779160135','QQ:2319440854')
第二范式:对表数据的唯一性,要求表数据要有唯一标识,即实体的唯一性,即不存在部分依赖
如有如下表结构设计:
--学生选课表
create table StudentCourse
(
StuId varchar(20),--学号
StuName varchar(20) not null,--学生姓名
CourseId varchar(20),--课程编号
CourseName varchar(20),--课程名称
CourseScore int not null--分数
)
insert into StudentCourse(StuId,StuName,CourseId, CourseName,CourseScore)
values('001','刘备','001','语文',80)
insert into StudentCourse(StuId,StuName,CourseId, CourseName,CourseScore)
values('001','刘备','002','数学',70)
insert into StudentCourse(StuId,StuName,CourseId, CourseName,CourseScore)
values('002','关羽','003','英语',80)
insert into StudentCourse(StuId,StuName,CourseId, CourseName,CourseScore)
values('003','张飞','003','英语',90)
表中学生这个实体,在记录中存在多条,不能保证唯一性,存在冗余,课程同理
造成结果:
(1)数据的冗余
(2)维护困难:比如添加一名学生但该学生还未开始选课;比如语文只有刘备选了,刘备退学后删除时则把课程也删除了;加入英语被1000个人选课,如果要修改课程名称则相对繁琐
上述设计中有两个事物,一个是学生信息,一个是课程信息,很显然这两个事物都没有保证实体的唯一性,这里的姓名依赖学号,课程名称依赖课程编号,所有不符合第二范式。
修改为如下结构:
--课程表
create table Course
(
CourseId int primary key identity(1,1),
CourseName varchar(20) not null,
CourseContent text --课程介绍
)
insert into Course(CourseName,CourseContent)values('HTML','静态网页的制作')
insert into Course(CourseName,CourseContent)values('WinForm','Windows应用程序开发')
--学生表
create table Student
(
StuId int primary key identity(1,1) ,--学号
StuName varchar(20) not null,--学生姓名
Sex char(2) not null,--学生性别
)
insert into Student(StuName,Sex)values('刘备','男')
insert into Student(StuName,Sex)values('关羽','男')
--考试信息表
create table Exam
(
ExamId int primary key identity(1,1),--选课成绩编号
StuId int not null,--学生编号
CourseId int not null,--课程编号
Score int not null--考试分数
)
insert into Exam(StuId,CourseId,Score) values(1,1,90)
insert into Exam(StuId,CourseId,Score) values(1,2,80)
insert into Exam(StuId,CourseId,Score) values(2,2,85)
第三范式:要求任何字段不能由其他字段派生出来,要求字段没有冗余即不存在传递依赖
如有如下表结构设计:
create table Student
(
StuId varchar(20) primary key,--学号
StuIdName varchar(20) not null,--学生姓名
ProfessionalId int not null,--专业编号
ProfessionalName varchar(50),--专业名称
ProfessionalRemark varchar(200)--专业介绍
)
insert into Student(StuId,StuIdName,ProfessionalId,ProfessionalName,ProfessionalRemark)
values('001','刘备',1,'计算机','最牛的专业')
insert into Student(StuId,StuIdName,ProfessionalId,ProfessionalName,ProfessionalRemark)
values('002','关羽',2,'工商管理','管理学的基础专业')
insert into Student(StuId,StuIdName,ProfessionalId,ProfessionalName,ProfessionalRemark)
values('003','张飞',1,'计算机','最牛的专业')
上述设计中专业名称字段和专业介绍字段,在数据库中会产生很多冗余数据,不满足第二范式,优化方案如下:
create table Professional
(
ProfessionalId int primary key identity(1,1),--专业编号
ProfessionalName varchar(50),--专业名称
ProfessionalRemark varchar(200) --专业介绍
)
create table Stduent
(
StuId varchar(20) primary key,
StuName varchar(20) not null,
ProfessionalId int not null
)
insert into Professional(ProfessionalName,ProfessionalRemark)values('计算机','最牛的专业')
insert into Professional(ProfessionalName,ProfessionalRemark)values('工商管理','管理学的基础专业')
insert into Stduent(StuId,StuName,ProfessionalId)values('001','刘备',1)
insert into Stduent(StuId,StuName,ProfessionalId)values('002','关羽',2)
insert into Stduent(StuId,StuName,ProfessionalId)values('003','张飞',1)
2、表关系
(1)一对一
(2)一对多(多对一)
(3)多对多
八、数据库的高级应用
在学习数据库的高级应用之前,先建立一个示例数据库
--业务需求说明--------------------------------------
--模拟银行业务,设计简易版的银行数据库表结构,要求可以完成以下基本功能需求
--1、银行开户(注册个人信息)及开卡(办理银行卡)(一个人可以办理多张银行卡,但是最多只能办理3张)
--2、存钱
--3、查询余额
--4、取钱
--5、转账
--6、查看交易记录
--7、账户挂失
--8、账户注销
--表设计----------------------------------
--1、账户信息表
--2、银行卡表
--3、交易信息表(存钱和取钱的记录)
--4、转账信息表
--5、状态信息变化表(银行卡状态1:正常,2:挂失,3:冻结,4:注销 )
--表结构设计
create table AccountInfo--账户信息表
(
AccountId int primary key identity(1,1),--账户编号
AccountCode varchar(20) not null,--身份证号码
AccountPone varchar(20) not null,--电话号码
RealName varchar(20) not null,--真实姓名
OpenTime smalldatetime not null--开户时间
)
create table BankCard--银行卡
(
CardNo varchar(30) primary key,--银行卡卡号
AccountId int references AccountInfo(AccountId) not null,--账户编号(与账户信息形成主外键关系)
CardPwd varchar(30) not null,--银行卡密码
CardMoney money not null,--银行卡余额
CardState int not null,--1:正常 2:挂失 3:冻结 4:注销
CardTime smalldatetime default(getdate())
)
create table CardExchange--交易信息表(存钱和取钱的记录)
(
ExchangeId int primary key identity(1,1),--交易自动编号
CardNo varchar(30) references BankCard(CardNo),--银行卡号(与银行卡表形成主外键关系)
MoneyInBank money not null,--存钱金额
MoneyOutBank money not null,--取钱金额
ExchangeTime smalldatetime not null--交易时间
)
create table CardTransfer--转账信息表
(
TransferId int primary key identity(1,1),--转账自动编号
CardNoOut varchar(30) not null,--转出银行卡号(与银行卡表形成主外键关系)
CardNoIn varchar(30) not null,--转入银行卡号(与银行卡表形成主外键关系)
TransferMoney money not null,--交易金额
TransferTime smalldatetime not null--交易时间
)
create table CardStateChange--状态信息变化表(存储银行卡状态1:正常 2:挂失 3:冻结 4:注销)
(
StateId int primary key identity(1,1),--状态信息自动编号
CardNo varchar(30) references BankCard(CardNo) not null,--银行卡号(与银行卡表形成主外键关系 )
OldState int not null,--银行卡原始状态
NewState int not null,--银行卡新状态
StateWhy varchar(200) not null,--状态变化原因
StateTime smalldatetime default(getdate()),--记录产生时间
)
--为刘备、关羽、张飞三个人进行开户开卡的操作
--刘备身份证:123456789 银行卡号:423556566576754
--关羽身份证:145787687 银行卡号:876545365656657
--张飞身份证:415656667 银行卡号:255676776785643
insert into AccountInfo(AccountCode,AccountPone,RealName,OpenTime)
values('123456789','15779160135','刘备',GETDATE())
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('423556566576754','1','123456',0,1,GETDATE())
insert into AccountInfo(AccountCode,AccountPone,RealName,OpenTime)
values('145787687','15352166545','关羽',GETDATE())
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('876545365656657','2','123456',0,1,GETDATE())
insert into AccountInfo(AccountCode,AccountPone,RealName,OpenTime)
values('415656667','15637877635','张飞',GETDATE())
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('255676776785643','3','123456',0,1,GETDATE())
--进行存钱操作,刘备存钱2000元,关羽存钱8000元,张飞存钱500000元
update BankCard set CardMoney=CardMoney+2000 where CardNo = '423556566576754'
insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)
values('423556566576754',2000,0,GETDATE())
update BankCard set CardMoney=CardMoney+8000 where CardNo = '876545365656657'
insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)
values('876545365656657',8000,0,GETDATE())
update BankCard set CardMoney=CardMoney+500000 where CardNo = '255676776785643'
insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)
values('255676776785643',500000,0,GETDATE())
--转账:刘备给张飞转账1000元
update BankCard set CardMoney=CardMoney+1000 where CardNo = '255676776785643'
update BankCard set CardMoney=CardMoney-1000 where CardNo = '423556566576754'
insert into CardTransfer(CardNoIn,CardNoOut,TransferMoney,TransferTime)
values('255676776785643','423556566576754',1000,GETDATE())
建立的数据库;
1、信息打印
--信息打印
print 'hello,sql'
select 'hello,sql'
2、变量
(1)局部变量
局部变量以@开头,先声明,再赋值
--局部变量
--声明
declare @str varchar(20)
--赋值方式1
set @str = 'I like Sql'
--赋值方式2
select @str = 'I like Sql'
print @str
--set和select进行赋值时的区别
--set:赋值变量指定的值
--select:一般用于表中查询出的数值赋值给变量,如果查询结果有多条,取最好一条赋值
--exp:select @a = 字段名 from 表名
--当前表最后一行的某个字段值给@a
(2)全局变量
全局变量以@@开头,由系统定义和维护
@@ERROR:返回执行的上一个语句的错误号
@@IDENTITY:返回最后插入的标识值
@@MAX_CONNECTIONS:返回允许同时进行的最大用户连接数
@@ROWCOUNT:返回受上一语句影响的行数
@@SERVERNAME:返回运行SQL Server的本地服务器的名称
@@SERVICENAME:返回SQL Server正在其下运行的注册表项的名称
@@TRANCOUNT:返回当前连接的活动事务数
@@LOCK_TIMEOUT:返回当前会话的当前锁定超时设置(毫秒)
示例:
--为赵云此人进行开户开卡操作,赵云身份证:423543253
insert into AccountInfo(AccountCode,AccountPone,RealName,OpenTime)
values('423543253','15777881135','赵云',GETDATE())
declare @AccountId int
set @AccountId = @@IDENTITY
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('4235879656576754',@AccountId,'123456',0,1,GETDATE())
select * from AccountInfo
--求出张飞的银行卡卡号和余额,张飞身份证:415656667
--方案1
select CardNo 卡号,CardMoney 余额 from AccountInfo
inner join BankCard on AccountInfo.AccountId = BankCard.AccountId
where AccountCode = '415656667'
--方案2
declare @AccountId int
select @AccountId = (select AccountId from AccountInfo where AccountCode = '415656667')
select CardNo 卡号,CardMoney 余额 from BankCard where AccountId = @AccountId
(3)go语句
go语句作用1:等待go语句之前代码执行完成之后才能执行后面的代码
go语句作用2:改变局部变量的作用范围
--go语句
--(1)等待go语句之前代码执行完成之后才能执行后面的代码
create database Test
go --如果没有go语句
use Test --则在use Test时会报错,因为数据库还未创建成功,无法切换
create table AccountInfo--账户信息表
(
AccountId int primary key identity(1,1),--账户编号
AccountCode varchar(20) not null,--身份证号码
AccountPone varchar(20) not null,--电话号码
RealName varchar(20) not null,--真实姓名
OpenTime smalldatetime not null--开户时间
)
--(2)批处理结束的一个标志
declare @num int--@num在这三句代码中作用范围是全局
set @num=100
set @num =100
declare @num1 int--@num在这代码中作用范围是go之前
set @num1=100
go
set @num1 =100
go
declare @num1 int--@num在这代码中作用范围是两个go之间
set @num1=100
go
set @num1 =100
3、运算符
SQL中使用的运算符分为7种
算数运算符:加(+)、减(-)、乘(*)、除(/)、模(%)
逻辑运算符:AND、OR、LIKE、BETWEEN、IN、EXISTS、NOT、ALL、ANY
赋值运算符:=
字符串运算符:+
比较运算符:=、>、<、>=、<=、<>
位运算符:&、|、^
复合运算符:+=、-=、/=、%=、*=
--已知长方形的长和宽,求长方形的周长和面积
declare @c int = 10
declare @k int = 5
declare @zc int
declare @area int
set @zc = (@c+@k)*2
set @area = @c*@k
print '长方形周长:'+ convert(varchar(10),@zc)
print '长方形面积:'+ convert(varchar(10),@area)
--
print '长方形周长:'+ cast(@zc as varchar(10))
print '长方形面积:'+ cast(@area as varchar(10))
--C#或者Java中数字与字符串相加会进行字符串的拼接,但在C#中会报错
--(2)查询银行卡状态为正常,并且余额超过10万的银行卡信息
select * from BankCard where CardState=1 and CardMoney>100000
--(3)查询出银行卡状态为1或者余额为0的银行卡信息
select * from BankCard where CardState=1 or CardMoney=0
--(4)查询出姓名中含有‘刘’的账户信息以及银行卡信息
select * from AccountInfo
inner join BankCard on AccountInfo.AccountId = BankCard.AccountId
where RealName like '%刘%'
--(5)查询出余额在2000-5000之间的银行卡信息
select * from BankCard where CardMoney>=2000 and CardMoney<=5000
select * from BankCard where CardMoney between 2000 and 5000
--(6)查询出银行卡状态为冻结或者注销的银行卡信息
select * from BankCard where BankCard.CardState in(3,4)
--(7)关羽身份证号:145787687,关羽到银行来开户,
--查询身份证账户表是否存在,不存在则进行开户开卡,存在则不开户直接开卡
declare @AccountId int
if EXISTS(select AccountId from AccountInfo where AccountCode='145787687')
begin
select @AccountId=(select AccountId from AccountInfo where AccountCode='145787687')
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('36787887576754',@AccountId,'123456',0,1,GETDATE())
end
else
begin
insert into AccountInfo(AccountCode,AccountPone,RealName,OpenTime)
values('145787687','15765754135','关羽',GETDATE())
set @AccountId = @@IDENTITY
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('3678788757675',@AccountId,'123456',0,1,GETDATE())
end
--扩展:上面需求添加一个限制即一个人最多只能开3张银行卡
declare @AccountId int --账户编号
declare @CardCount int --银行卡数目
if EXISTS(select AccountId from AccountInfo where AccountCode='145787687')
begin
select @AccountId=(select AccountId from AccountInfo where AccountCode='145787687')
select @CardCount = (select COUNT(*) from BankCard where AccountId=@AccountId)
if @CardCount<=2
begin
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('36787887576754',@AccountId,'123456',0,1,GETDATE())
end
else
begin
print '您名下的银行卡太多了,最多只能有3张银行卡'
end
end
else
begin
insert into AccountInfo(AccountCode,AccountPone,RealName,OpenTime)
values('145787687','15765754135','关羽',GETDATE())
set @AccountId = @@IDENTITY
insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState,CardTime)
values('3678788757675',@AccountId,'123456',0,1,GETDATE())
end
--(8)查询银行卡账户余额,是不是所有账户余额都超过了3000
if 3000<All(select CardMoney from BankCard)
begin
print'所有的银行卡余额都超过了3000'
end
else
begin
print'不是所有的银行卡余额都超过了3000'
end
--(9)查询银行卡账户余额,是否含有账户余额超过300000的信息
if 300000<ANY(select CardMoney from BankCard)
begin
print'有的银行卡余额超过了300000'
end
else
begin
print'没有银行卡余额超过了300000'
end
4、流程控制
(1)选择分支结构
1、if-else结构
某用户银行卡号为:,该用户执行取钱操作,取钱5000元,余额充足则进行取钱操作,并提示‘取钱成功’,否则提示‘余额不足’
--(1)某用户银行卡号为:876545365656657,该用户执行取钱操作,取钱5000元,余额充足则进行取钱操作,并提示‘取钱成功’
--否则提示‘余额不足’
declare @balance money
select @balance = (select CardMoney 余额 from BankCard where CardNo = '876545365656657')
if @balance>=5000
begin
update BankCard set CardMoney=CardMoney-5000
insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)
values('876545365656657',0,5000,GETDATE())
print '取钱成功'
end
else
begin
print '余额不足'
end
2、case-when结构
--查询银行卡信息,将银行卡状态1,2,3,4分别转换为汉字“正常、挂失、冻结、注销”,
--并且根据银行卡余额显示银行卡等级
--30万以下为“普通用户”,30万及以上为“VIP用户”
--显示列分别为卡号,身份证,姓名,余额,用户等级,银行卡状态
select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,
case
when CardMoney>=300000 then 'VIP用户'
else '普通用户'
end 用户等级,
case CardState
when 1 then '正常'
when 2 then '挂失'
when 3 then '冻结'
when 4 then '注销'
else '异常'
end 银行卡状态
from BankCard
inner join AccountInfo on BankCard.AccountId=AccountInfo.AccountId
(2)循环结构
--循环结构---------------------------
--(1)循环打印1-10
declare @i int = 1
while @i<=10
begin
print @i
set @i=@i+1
end
--(2)循环打印九九乘法表
--特殊字符:char(9):制表符 char(10):换行符
declare @a int =1
declare @b int =1
declare @str varchar(1000) =''
while @a<=9
begin
while @b<=@a
begin
set @str = @str + convert(varchar(10),@a)+'x'+ convert(varchar(10),@b)+'='+convert(varchar(10),@a*@b)+char(9)
set @b+=1
end
print @str
set @b =1
set @a+=1
set @str = ''
end
5、子查询
--(1)赵云的银行卡号为:4235879656576754
--查询出余额比赵云多的银行卡信息,显示卡号、身份证、姓名、余额
--方案一
declare @balance1 money
select @balance1 = (select CardMoney from BankCard where CardNo = '4235879656576754')
select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from BankCard
inner join AccountInfo on BankCard.AccountId=AccountInfo.AccountId
where CardMoney>@balance1
--方案二:子查询
select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from BankCard
inner join AccountInfo on BankCard.AccountId=AccountInfo.AccountId
where CardMoney>(select CardMoney from BankCard where CardNo = '4235879656576754')
--(2)从所有账户信息中查询出余额最高的交易明细(存钱取钱信息)
--如果多个人余额一样并且都是最高,下面查询只能查出一个
select * from CardExchange where CardNo = (select top 1 CardNo from BankCard order by CardMoney desc)
--如果多个人余额一样并且都是最高,需要都查出来
select * from CardExchange where CardNo in
(select CardNo from BankCard where CardMoney=(select MAX(CardMoney) from BankCard))
--(3)查询有取款记录的银行卡及账户信息,显示卡号、身份证、姓名、余额
select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from AccountInfo
inner join BankCard on AccountInfo.AccountId = BankCard.AccountId
where CardNo in(select CardNo from CardExchange where MoneyOutBank>0)
--(4)查询出没有存款记录的银行卡及账户信息,显示卡号,身份证,姓名,余额
select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from AccountInfo
inner join BankCard on AccountInfo.AccountId = BankCard.AccountId
where CardNo not in(select CardNo from CardExchange where MoneyInBank>0 )
--(5)关羽的银行卡号为“876545365656657”,查询当天是否有收到转账
if exists(select * from CardTransfer where
CardNoIn = '876545365656657' and CONVERT(varchar(22),GETDATE(),23) = CONVERT(varchar(22),TransferTime,23))
begin
print '当天有收到转账'
end
else print '当天没有收到转账'
--(6)查询出交易次数(存款取款操作)最多的银行卡账户信息,
--显示:卡号,身份证,姓名,余额,交易次数
--方案1:该种方案假如有多个相同交易次数则只能显示一个
select top 1 BankCard.CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,交易次数 from AccountInfo
inner join BankCard on AccountInfo.AccountId = BankCard.AccountId
inner join (select COUNT(*) 交易次数,CardNo from CardExchange
group by CardNo) Temp on BankCard.CardNo = Temp.CardNo
order by 交易次数 desc
--方案2
select BankCard.CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,交易次数 from AccountInfo
inner join BankCard on AccountInfo.AccountId = BankCard.AccountId
inner join (select COUNT(*) 交易次数,CardNo from CardExchange
group by CardNo) Temp on BankCard.CardNo = Temp.CardNo
where 交易次数 =
(select MAX(交易次数) from (select COUNT(*) 交易次数,CardNo from CardExchange
group by CardNo) temp1)
--(7)查询出没有转账交易记录的银行账户信息,显示卡号,身份证,姓名,余额
select BankCard.CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from AccountInfo
inner join BankCard on AccountInfo.AccountId = BankCard.AccountId
where BankCard.CardNo not in(select CardNoIn from CardTransfer)
and BankCard.CardNo not in(select CardNoOut from CardTransfer)
6、分页
--分页
--假设每页5条数据
--查询第一页
select top 5 * from Student
--等价于
select top 5 * from Student where StuId not in(select top 0 StuId from Student)
--查询第二页
select top 5 * from Student where StuId not in(select top 5 StuId from Student)
--查询第三页
select top 5 * from Student where StuId not in(select top 10 StuId from Student)
--归纳规律为:
select top 页码大小 * from Student where StuId not in(select top 页码大小*(当前页-1) StuId from Student)
--将页码大小与当前页做成变量
--分页方案1:top方式分页,当数据量大时,效率不高
declare @PageSize int = 6
declare @PageIndex int = 1
select top(@PageSize) * from Student where StuId not in(select top (@PageSize*(@PageIndex-1)) StuId from Student)
--分页方案2:使用row_number分页
--规律归纳为:
select * from
(select ROW_NUMBER() OVER(order by StuId) ROWID,* from Student) TEMP
where ROWID between 页码大小*(当前页-1)+1 and 当前页*页码大小
--代码表示如下:
declare @PageSize int = 5
declare @PageIndex int = 2
select * from
(select ROW_NUMBER() OVER(order by StuId) ROWID,* from Student) TEMP
where ROWID between @PageSize*(@PageIndex-1)+1 and @PageIndex*@PageSize
7、事务
1、概念
将多步操作捆绑在一起,这样的多步操作要么全部成功,要么全部失败
2、作用
使用事务可以确保数据库的一致性和完整性,避免数据出现异常或不一致的情况
3、代码示例
@@ERROR会在每执行一句SQL语句存入一个值,如果执行成功则为0,执行失败则为错误代码
--事务
--(1)假设刘备取款6000,(添加Check约束,设置账户余额必须>=0),
--要求:使用事务实现,修改余额和添加取款记录两步操作使用事务
alter table BankCard add constraint ck_money check(CardMoney>=0)
print @@error
begin transaction
declare @myError int = 0
update BankCard set CardMoney = CardMoney - 6000 where CardNo = '423556566576754'
set @myError += @@ERROR
insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)
values('423556566576754',0,6000,GETDATE())
set @myError += @@ERROR
if @myError = 0
begin
commit transaction
print '取款成功'
end
else
begin
rollback transaction
print '取款失败'
end
select * from CardExchange
select * from BankCard
--(2)假设刘备向张飞转账1000元,(添加Check约束,设置账户余额必须>=0)
--分析步骤有三步:
--(1):张飞添加1000元(2)刘备扣除1000元(3)生成转账记录 使用事务解决此问题
begin transaction
declare @ExeError int = 0
update BankCard set CardMoney = CardMoney+1000 where CardNo = '255676776785643'
set @ExeError += @@ERROR
update BankCard set CardMoney = CardMoney-1000 where CardNo = '423556566576754'
set @ExeError += @@ERROR
insert into CardTransfer(CardNoOut,CardNoIn,TransferMoney,TransferTime)
values('423556566576754','255676776785643',1000,GETDATE())
set @ExeError += @@ERROR
if @ExeError = 0
begin
commit transaction
print '转账成功'
end
else
begin
rollback transaction
print '转账失败'
end
8、索引
作用:提高检索查询效率
分类:按存储结构区分:”聚集索引(聚类索引)“、”非聚集索引(非聚类索引)“
聚集索引:根据数据行的键值