SQL Server学习笔记
目录
一.数据库及SQL Server简介
1.数据库
1.1.数据库概念
1.2.应用场景
1.3.数据表展现形式
1.4.主流关系型数据库
2.SQL Server
2.1.SQL Server的安装(服务、管理工具)
2.2.打开数据库
2.2.1.启动SQL Server服务
2.2.2.打开SQL Server Management Studio,连接到数据库
二.数据库基本操作
1.建库
2.建表
3.表内数据维护
三.数据库的迁移
1.数据库的分离与附加
1.1.分离
1.2.附加
2.数据库的备份与还原
2.1.备份
2.2.还原
3.利用数据库脚本进行保存和迁移
3.1.生成数据库脚本
3.2.利用脚本对数据库进行迁移
编辑四.利用脚本操作数据库
一些SQL Server数据类型
一些SQL Server约束和函数
1.创建数据库
1.1.创建数据的基本语法
1.2.创建数据库的简写方式
2.建表
2.1.创建表的基本语法
3.修改表结构
3.1.添加列(字段)的基本语法
3.2.删除列(字段)的基本语法
3.3.修改列(字段)数据类型的基本语法
4.维护约束
4.1.删除约束的基本语法
4.2.添加约束的基本语法
2.1.添加check约束
2.2.添加主键约束
2.3.添加唯一约束
2.4.添加默认值约束
2.5.添加外键约束
5.修改表数据
5.1.插入数据的基本语法
1.1.一次插入单行
1.2.一次插入多行
5.2.修改数据的基本语法
2.1.修改某一列的数据
2.2.删除某一行的数据
关于SQL Server中的几种删除语句的区别
6.基本查询
6.1.查询所有列的基本语法
6.2.查询指定列的基本语法
6.3.查询指定列并在查询结果里为列增加别名的基本语法
6.4.查询指定列并不显示查询结果内重复值的基本语法
6.5.查询指定列进行一定操作(但实际并未操作)的基本语法
7.条件查询与排序查询
7.1.SQL中常用的算数和逻辑运算符
7.2.条件查询
7.3.排序查询
8.对于含有空值null的数据的操作
9.模糊查询
10.聚合函数
11.分组查询
12.多表查询
12.1.笛卡尔乘积
12.2.简单多表查询
12.3.内连接查询
12.4.外连接查询
12.5.多表查询综合示例
12.6.自连接
五.数据库设计
1.数据库结构设计三范式
1.1.第一范式
1.2.第二范式
1.3.第三范式
2.表关系
2.1.一对一
2.2.一对多(多对一)
2.3.多对多
3.数据库案例设计
六.使用T-SQL编程
1.信息打印
2.变量
2.1.局部变量
2.2.全局变量
3.go语句
4.运算符
5.流程控制
5.1.选择分支结构
5.2.循环结构
6.子查询
补充:一些SQL Server的时间函数
7.分页
使用top分页(当数据量很大时,查询效率很低)
使用row_number分页
8.事务
9.索引
SQL Server索引类型
创建索引的方式
10.视图
11.游标
12.函数
13.触发器
14.存储过程
14.1. 没有输入参数,没有输出参数的存储过程
14.2.有输入参数,没有输出参数的存储过程
14.3.有输入参数,没有输出参数,但是有返回值的存储过程(返回值必须整数)
14.4.有输入参数,有输出参数的存储过程
14.5.具有既能输入又能输出的参数的存储过程
一.数据库及SQL Server简介
1.数据库
1.1.数据库概念
数据库是按照数据结构来组织、存储和管理数据的仓库。是一个长期存储在计算机内部的、有组织的、可共享的、统一管理的大量数据的集合
1.2.应用场景
在软件系统中无处不在,几乎所有的软件系统背后都有数据库,例如QQ,淘宝等
1.3.数据表展现形式
1.4.主流关系型数据库
SQL Server, MySql, Oracle等
2.SQL Server
2.1.SQL Server的安装(服务、管理工具)
在微软官网即可下载安装SQL Server服务和管理工具(SSMS)程序:SQL Server 下载 | Microsoft
安装时最好选择混合模式身份验证,方便以后操作
具体的安装教程可以在网络上搜索
2.2.打开数据库
2.2.1.启动SQL Server服务
方式一:命令行启动
输入cmd打开命令行,再输入net start mssqlserver即可启动(不一定所有的SQL Server版本都支持命令行启动)
方式二:SQL Server配置管理器启动
进入SQL Server配置管理器中的SQL Server服务,右键MSSQLSERVER启动
方式三:Windows服务
控制面板-系统和安全-服务-SQL Server(MSSQLSERVER)即可右键启动
2.2.2.打开SQL Server Management Studio,连接到数据库
如果安装时选择了混合模式身份验证,则有两种方式连接到数据库:
方式一:Windows身份验证
适用于服务器和管理工具都在本地机器上,并且当前已经进入了本机操作系统
服务器名称打.(有的机器不一定可以)或本地计算机名即可连接到本地计算机数据库
方式二:SQL Server身份验证
适用于服务器与管理工具所在机器距离很远的情况下,使用登录名和密码登录管理工具就可以实现远程连接数据库
此时要选择对应服务器的服务器名称
二.数据库基本操作
1.建库
右键数据库-新建数据库-输入名称即创建完毕
2.建表
右键表-新建-表-输入对应数据
在对应列名的列属性框中打开标识规范的(是标识)即可让该列的编号自动增长
右键对应的列名左边可设置主键,主键可以唯一标识一条数据
每张表最好都设置一个类似编号的字段作为主键用于唯一标识数据,一般将主键放在第一个字段
完成数据填充设置之后保存并设置好表名即创建完毕
注:若建库、建表完成后没有及时在左侧显示出来,右击数据库或表进行刷新即可
3.表内数据维护
右键需要维护的表-编辑前200行即可对该表格的前200行的数据进行增删改查。
若要自定义编辑的行数,可以点击工具-选项-SQL Server对象资源管理器-命令进行修改,如图所示,当值为0的时候操作的既是所有行。
三.数据库的迁移
1.数据库的分离与附加
1.1.分离
分离与删除的区别:分离之后SQL Server中就没有对应数据库了,但是文件依然保存在本地,删除之后,本地所有文件也消失了。
右键所选数据库-属性-文件,找到数据库的存储位置
进入存储位置可以找到对应数据库的文件,有.mdf和.ldf两个文件,前者是数据库数据文件(用于保存数据库数据),后者是数据库日志文件(用于保存数据库操作日志)
然后右键所选数据库-任务-分离(勾选上删除连接和更新统计信息)
分离后才能够对.mdf和.ldf文件进行复制或删除等操作,否则会显示文件已打开而失败,这里以另一个asd数据库为例,如下:
分离复制带走后即可附加至别的机器上
注:数据库一旦被分离,原服务器上对应的数据库就无法访问了
1.2.附加
右键要转移至的机器数据库-附加-添加目标.mdf即可附加成功
2.数据库的备份与还原
2.1.备份
右键所选数据库-任务-备份(勾选完整备份)-选择保存路径-添加文件名.bak即可备份带走
注:进行备份操作时,对应的数据库在服务器上仍然可以被正常访问
2.2.还原
右键要还原至的机器数据库-还原数据库-设备-选择备份的.bak文件即可
3.利用数据库脚本进行保存和迁移
3.1.生成数据库脚本
右键所选数据库-任务-生成脚本-编写整个数据库及所有数据库对象的脚本-下一步-高级-要编写脚本的数据类型选择架构和数据-选择保存路径-生成的.sql文件即为数据库脚本
架构即数据库表格的结构,数据即表格中的数据
3.2.利用脚本对数据库进行迁移
用要转移至机器上的SSMS打开保存的.sql文件再执行,然后刷新即可还原数据库
四.利用脚本操作数据库
点击新建查询即可出现编写SQL脚本的界面(注意选好当前操作的数据库对象),点击执行
即可运行整个脚本,也可以选中脚本中的某些语句单独执行,若一条语句太长也支持分行写。
注:SQL Server对数据库语言和具体某字段的查询结果大小写默认都是不敏感的,若要修改,需要到设置内或使用SQL语句分别进行修改
一些SQL Server数据类型
注:在SQL Server中的注释符是-- ;字符串使用的是单引号\'\';
- 在char,varchar,text前面加n表示存储的是Unicode字符,对中文友好,通常中文需要占用2个字节,但加n后使用Unicode字符只占用1个字节
- 精度大小:decimal>money>float,float误差最大
- datatime,smalldatetime如果不具体写上时分秒,系统会默认赋为全0
- int,decimal,float,money等数字类型在赋值时不需要加单引号,而其他类型大部分需要加上单引号(日期类型虽然可以不加单引号,但是为保证兼容性和准确性还是推荐加上,字符串则必须加上单引号)
- 对日期进行大小比较时,系统会自动将字符类型的日期(带了单引号)转为数值型进行比较
- SQL Server中没有double类型数据
一些SQL Server约束和函数
select ROUND(123.45454,3,1) --123.45400
year
:年,quarter
:季度,month
:月,dayofyear
:一年中的第几天,day
:天,week
:周,weekday
:工作日,hour
:小时,minute
:分钟,second
:秒,millisecond
:毫秒
primary key和unique有区别,并且同一个字段不能同时有这两个约束,异同点如下:
外键:如果一张表的某一字段是另一张表的主键字段,那么就称其为这张表的外键,若要查询一张表的主外键关系,可以在表的设计内右键选择关系,进入表和列规范即可查看
1.创建数据库
1.1.创建数据的基本语法
create database 库名 --没有逗号
on
(
name = \'库名\',
filename = \'物理路径和名称\',--后缀写为.mdf
size = 5MB,
filegrowth = 2MB --最后一行没有逗号
)
log on
(
name = \'库名_log\',
filename = \'物理路径和名称\',--需要加_log,后缀写为.ldf,
size = 5MB,
filegrowth = 2MB --最后一行没有逗号
)
以创建一个名为DBTEST的数据库为例
create database DBTEST --创建一个名为DBTEST的数据库on--数据文件(name = \'DBTEST\',--逻辑名称,通常与数据库名称一致filename = \'D:\\SQLDATA\\DBTEST.mdf\',--物理路径和名称,后缀写为.mdfsize = 5MB, --文件的初始大小filegrowth = 2MB --文件大小的增长方式,也可按百分比增长如filegrowth = 10%,注意最后一行没有,逗号)log on --日志文件(name = \'DBTEST_log\',--逻辑名称,通常与数据库名称一致,需要加_logfilename = \'D:\\SQLDATA\\DBTEST_log.ldf\',--物理路径和名称,需要加_log,后缀写为.ldf,最好与数据文件放一起size = 5MB, --文件的初始大小filegrowth = 2MB --文件大小的增长方式,也可按百分比增长如filegrowth = 10%,注意最后一行没有,逗号)
如果有重名数据库,则会创建失败,此时可以修改新创建的数据库名称,或者右键删除重名的数据库,或者编写如下脚本删除重名数据库:
--如果没有对应名称数据库,则删除会失败--因此需要进行删除前的判断if exists(select * from sys.databases where name = \'DBTEST\'--sys.databases在master的视图内,是系统数据库的表格,存放着当前数据库内所有库的信息 drop database DBTEST--如果存在名为DBTEST的数据库,则进行删除
注:尽量不要使用类似上面的删库代码,非常危险!!!仅限学习时使用
1.2.创建数据库的简写方式
create database DBTEST--创建数据库的简写方式,没有逗号
此时,数据与日志文件设置均为默认值
2.建表
切换当前操作数据库可以用鼠标点击切换也可以使用命令:
use DBTEST --没有逗号,切换数据库
2.1.创建表的基本语法
create table 表名--没有逗号
(
字段名1 数据类型(长度) 约束条件,
字段名2 数据类型(长度) 约束条件--最后一句不加逗号
)
创建一张部门表:
--建一张部门表create table Department--没有逗号( DepartmentID int primary key identity(1,1),--部门编号 --SQL Server中没有string类型 DepartmentName nvarchar(50) not null,--部门名称 DepartmentRemark text--部门描述)
如果有重名表,则会创建失败,此时可以修改新创建的表名称,或者右键删除重名的表,或者编写如下脚本删除重名表:
--判断表是否存在if exists(select * from sys.objects where name = \'Department\' and type = \'U\')--sys.objects在当前数据库的视图内,存放当前数据库的对象的信息,type = \'U\'表示这张表是用户创建的 drop table Department--若存在名为Department的表格,则进行删除
注:尽量不要使用类似上面的删表代码,非常危险!!!仅限学习时使用
创建一张职级表:
若表名与关键字重名发生冲突时,可以修改表名或者在当前重名的名称外面加上[]即可使用
--建一张职级表create table [Rank]--与关键字Rank冲突,加上[]( 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 = \'女\'),--性别--默认值为男,且添加约束只能为男或女 PeopleBirth smalldatetime not null,--生日 PeopleSalary decimal(12,2) check(PeopleSalary >=1000 and PeopleSalary <=1000000),--月薪--添加约束只能在1000到1000000之间 PeoplePhone varchar(20) unique not null,--电话--添加唯一性约束,即电话号码不能相同 PeopleAddress nvarchar(300),--居住地址 PeopleAddTime smalldatetime default(getdate())--添加时间--默认值为当前系统时间)
3.修改表结构
3.1.添加列(字段)的基本语法
alter table 表名 add 新列名 数据类型 PS:可加上约束条件 --没有逗号
如给员工表添加一列邮箱:
alter table People add PeopleMail varchar(200) not null--给员工表添加邮箱字段
3.2.删除列(字段)的基本语法
alter table 表名 drop column 列名(字段) --没有逗号
如给员工表删除邮箱字段:
alter table People drop column PeopleMail--删除员工表的邮箱字段
3.3.修改列(字段)数据类型的基本语法
alter table 表名 alter column 列名 数据类型 --没有逗号
如修改员工地址nvarchar(300)为nvarchar(200) :
alter table People alter column PeopleAddress nvarchar(200)--修改员工地址数据类型为nvarchar(200)
注:若表中已经有数据,且某些数据与修改后的数据类型冲突,则会报错,如上述操作中若表中已经有长度为201的地址,则修改会报错
4.维护约束
4.1.删除约束的基本语法
alter table 表名 drop constraint 约束名 --没有逗号
当前表的约束具体信息可以在对象资源管理器对应表的目录下面的列、键、约束等中找到:
以删除月薪的check约束为例:
alter table People drop constraint CK__People__PeopleSa__22AA2996--删除月薪的约束条件
约束名也可以在系统表sys.objects中查询(比较麻烦),或者拥有权限的话也可以直接在约束框内用鼠标对约束进行直接删除
4.2.添加约束的基本语法
alter table 表名 add constraint 自定义约束名称 约束 --没有逗号
关于约束名称的自定义有一定的规范:
一、便于识别约束类型
一般需要在命名时包含约束类型的缩写,可以让开发者和数据库管理员迅速识别约束的类型,例如:
- 主键约束(Primary Key):使用前缀PK
_
。- 外键约束(Foreign Key):使用前缀FK
_
。- 唯一性约束(Unique):使用前缀UQ
_
。- 检查约束(Check):使用前缀CK
_
。- 非空约束(Not Null):使用前缀NN
_
。二、确保命名唯一
数据库中的约束命名必须是唯一的,以防止命名冲突。通过在约束名称中包含表名或列名,可以确保命名的唯一性。
三、保持命名规范性
为了保持数据库设计的一致性,所有约束命名应该遵循统一的命名规则。制定和遵循命名规范有助于团队成员在开发和维护过程中快速理解和修改数据库对象。以下是一些常见的命名规范:
- 使用大写字母和下划线分隔单词。
- 名称长度适中,不宜过长或过短。
- 避免使用保留字或特殊字符。
例如,在为Orders表的OrderID列创建主键约束时,可以命名为PK
_
Orders_
OrderID。
2.1.添加check约束
alter table 表名 add constraint 自定义约束名称 check(表达式) --没有逗号
如给工资添加check约束:
alter table People add constraint CK_People_PeopleSalarycheck(PeopleSalary >=1000 and PeopleSalary <=1000000)--给月薪添加check约束
2.2.添加主键约束
alter table 表名 add constraint 自定义约束名称 primary key(列名) --没有逗号
2.3.添加唯一约束
alter table 表名 add constraint 自定义约束名称 unique(列名) --没有逗号
2.4.添加默认值约束
alter table 表名 add constraint 自定义约束名称 default 默认值 for 列名 --没有逗号
2.5.添加外键约束
alter table 表名 add constraint 自定义约束名称 foreign key(当前表列名) references 关联表名(列名PS:一般是关联表的主键) --没有逗号
5.修改表数据
5.1.插入数据的基本语法
1.1.一次插入单行
insert into 表名(列名,列名,......列名)--若某列有自动值或想使用默认值可以不用加列名,若无自动值或默认值,则会自动置NULL(前提是该字段可以为NULL)
--加上了就需要手动赋值,否则会报错
values(值,值,......值)
可以省略字段名称,简写为insert into 表名 values(值,值,......值),此写法在给字段赋值的时候,必须保证顺序和数据表结构中字段顺序完全一致,不推荐使用此种写法,因为数据表结构变化的时候,还按原表的字段顺序赋值数据会出错或产生错误数据。完整写法能保证值与列一一对应
如完整写法向部门表,职级表和员工表插入数据:
insert into Department(DepartmentName,DepartmentRemark)values(\'市场部\',\'......\')insert into Department(DepartmentName,DepartmentRemark)values(\'软件部\',\'......\')insert into Department(DepartmentName,DepartmentRemark)values(\'企划部\',\'......\')--完整写法向部门表插入三条数据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(1,3,\'刘备\',\'男\',\'1984-7-9\',20000,\'13554785452\',\'成都\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(1,2,\'孙尚香\',\'女\',\'1987-7-9\',15000,\'13256854578\',\'荆州\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(1,1,\'关羽\',\'男\',\'1988-8-8\',12000,\'13985745871\',\'荆州\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(2,1,\'张飞\',\'男\',\'1990-8-8\',8000,\'13535987412\',\'宜昌\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(2,3,\'赵云\',\'男\',\'1989-4-8\',9000,\'13845789568\',\'宜昌\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(3,3,\'马超\',\'男\',\'1995-4-8\',9500,\'13878562568\',\'香港\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(3,2,\'黄盖\',\'男\',\'1989-4-20\',8500,\'13335457412\',\'武汉\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(3,1,\'貂蝉\',\'女\',\'1989-4-20\',6500,\'13437100050\',\'武汉\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(2,2,\'曹操\',\'男\',\'1987-12-20\',25000,\'13889562354\',\'北京\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(2,3,\'许褚\',\'男\',\'1981-11-11\',9000,\'13385299632\',\'北京\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(2,1,\'典韦\',\'男\',\'1978-1-13\',8000,\'13478545263\',\'上海\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(1,1,\'曹仁\',\'男\',\'1998-12-12\',7500,\'13878523695\',\'深圳\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(1,3,\'孙坚\',\'男\',\'1968-11-22\',9000,\'13698545841\',\'广州\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(3,3,\'孙策\',\'男\',\'1988-1-22\',11000,\'13558745874\',\'深圳\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(3,2,\'孙权\',\'男\',\'1990-2-21\',12000,\'13698745214\',\'深圳\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(3,2,\'大乔\',\'女\',\'1995-2-21\',13000,\'13985478512\',\'上海\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(2,1,\'小乔\',\'女\',\'1996-2-21\',13500,\'13778787874\',\'北京\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(1,2,\'周瑜\',\'男\',\'1992-10-11\',8000,\'13987455214\',\'武汉\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(2,3,\'鲁肃\',\'男\',\'1984-9-10\',5500,\'13254785965\',\'成都\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(3,3,\'吕蒙\',\'男\',\'1987-5-19\',8500,\'13352197364\',\'成都\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(1,1,\'陆逊\',\'男\',\'1996-5-19\',7500,\'13025457392\',\'南京\')insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone,PeopleAddress)values(1,2,\'太史慈\',\'男\',\'1983-6-1\',7500,\'13077778888\',\'上海\')--完整写法向员工表插入多条数据
简略写法向部门表插入数据:
insert into Department values(\'硬件部\',\'......\')insert into Department values(\'总经办\',\'......\')--简略写法向部门表插入两条数据
1.2.一次插入多行
insert into 表名(列名,列名,......列名)--有自动增长或想使用默认值的列可以不用加
--加上了就需要手动赋值
select 值,值,......值 union
select 值,值,......值 union
select 值,值,......值--没有逗号
注:使用该语法插入时,插入的行顺序是随机的
如继续向部门表插入数据:
insert into Department(DepartmentName,DepartmentRemark)select \'测试部\',\'......\' unionselect \'实施部\',\'......\' unionselect \'产品部\',\'......\'--一次性插入多行数据
5.2.修改数据的基本语法
2.1.修改某一列的数据
修改整列:
update 表名 set 字段1=值1,字段2=值2,......字段n=值n--没有逗号
修改某一行某一列:
update 表名 set 字段1=值1,字段2=值2,......字段n=值n where 条件--没有逗号
若条件有多个,则需要使用and进行连接
如对工资进行调整,每个人加薪1000元:
update People set PeopleSalary = PeopleSalary + 1000--将工资字段全部加1000
对员工编号为7的人加薪500元:
update People set PeopleSalary = PeopleSalary + 500where PeopleID = 7--将员工编号为7的人加薪500
将软件部(部门编号为2) 人员工资低于15000的调整成15000:
update People set PeopleSalary = 15000where DepartmentID = 2 and PeopleSalary <15000--将部门编号为2的薪水低于15000的工资提高到15000
将刘备的工资改为以前的两倍,并且将其地址修改为北京:
update People set PeopleSalary = PeopleSalary*2, PeopleAddress = \'北京\'where PeopleName = \'刘备\'--将刘备的工资改为以前的两倍,并且将其地址修改为北京
2.2.删除某一行的数据
delete from 表名 where 条件--没有逗号
若不加where 条件,则会删除整个表的数据
如删除市场部(部门编号为1)中工资大于等于15000的人:
delete from People where DepartmentID = 1 and PeopleSalary >= 15000--删除市场部(部门编号为1)中工资大于等于15000的人
关于SQL Server中的几种删除语句的区别
- drop table 表名--删除表对象,相当于在对象资源管理器右键删除表,数据以及表本身都被删除了
- truncate table 表名--删除数据,清空表内数据,但是表结构依然存在
- delete from 表名--删除数据,清空表内数据,但是表结构依然存在
truncate清空所有数据,不能加条件,但 delete可以清空所有数据,也可以带条件删除指定数据
对于identity自动编号,truncate清空数据后,编号仍然从头开始自增,而delete删除数据后,对应的自动编号就永远消失了,编号从被删除的地方继续增加
6.基本查询
查询的结果可能是一张临时表,并且查询显示的列顺序是按照select中的列顺序来的,也可能为一个单独的值(单行单列表),并且可以像一个值一样进行运算等操作
6.1.查询所有列的基本语法
select * from 表名--*就代表所有列
如查询部门,职级和员工表的所有内容:
select * from Departmentselect * from [Rank]select * from People--查询部门,职级和员工表
6.2.查询指定列的基本语法
select 列名,列名,......列名 from 表名
如查询员工表的姓名,性别,生日,月薪和电话列内容:
select PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone from People--查询员工表的姓名,性别,生日,月薪和电话
6.3.查询指定列并在查询结果里为列增加别名的基本语法
select 列名 别名,列名 别名,......列名 别名 from 表名
如查询员工表的姓名,性别,生日,月薪和电话列内容并将每列列名显示为中文:
select PeopleName 姓名,PeopleSex 性别,PeopleBirth 生日,PeopleSalary 月薪,PeoplePhone 电话 from People--查询员工表的姓名,性别,生日,月薪和电话并将列名显示为中文
注:该操作并不会改变原表的结构,因为查询结果只是一张临时表,原表的每一列仍然是英文名,只是查询结果这张表内的每一列名称变味了中文
6.4.查询指定列并不显示查询结果内重复值的基本语法
select distinct 列名,列名,......列名 from 表名
如查询员工所在城市并不显示重复的值:
select distinct PeopleAddress from People--查询员工所在城市并去除重复的值
注:若distinct后面跟了多列,则判断某一行重复的逻辑是每一列值都相同,该操作同样不会改变原表的结构
6.5.查询指定列进行一定操作(但实际并未操作)的基本语法
select 列名,列名(操作如:*2),......列名 from 表名
上述语法对应的有一定操作的列在查询结果里可能显示无列名,此时可以加上别名
如假设准备加工资(上调20%),查询出加工资前后的员工姓名,性别,生日数据进行对比:
select PeopleName, PeopleSex, PeopleSalary 加薪前工资, PeopleSalary*1.2 加薪后工资 from People --准备加工资(上调20%),查询出加工资后的员工姓名,性别,生日
注:该操作同样不会改变原表的结构
7.条件查询与排序查询
7.1.SQL中常用的算数和逻辑运算符
- +,-,*,/,%:加,减,乘,除,取余
- =:等于,比较是否相等及赋值
- !=:比较不等于
- >:比较大于
- <:比较小于
- >=:比较大于等于
- <=:比较小于等于
- IS NULL:比较为空
- IS NOT NULL:比较不为空
- in:比较是否在其中 例如: peopleAddress in(’北京’,’上海’,’深圳’)
- like:模糊查询
- BETWEEN...AND...:比较是否在两者之间(包含端点)
- and:逻辑与(两个条件同时成立表达式成立)
- or:逻辑或(两个条件有一个成立表达式成立)
- not:逻辑非(条件成立,表达式则不成立;条件不成立,表达式则成立)
7.2.条件查询
条件查询的基本语法
select 列名,......列名 from 表名 where 条件
以下是一些有常用运算符使用的条件或多条件查询例子:
select * from People where PeopleSex = \'女\'--使用判等运算符=查询性别为女的员工信息select * from People where PeopleSalary >= 10000--使用判断大于等于运算符>=查询月薪大于等于10000元的员工信息select * from People where PeopleSex = \'女\' and PeopleSalary >= 10000--使用逻辑与运算符and连接多个条件查询月薪大于等于10000元的女员工信息select * from People where PeopleSalary >= 10000 or (PeopleSex = \'女\' and PeopleSalary >= 8000)--使用逻辑或运算符or连接多个条件查询月薪大于等于10000元的员工信息或月薪大于等于8000的女员工信息select * from People where PeopleBirth >= \'1980-1-1\' and (PeopleSex = \'女\' and PeopleSalary >= 10000)--使用逻辑与运算符and连接多个条件查询出生于1980-1-1后,月薪大于等于10000元的女员工信息select * from People where PeopleSalary BETWEEN 10000 AND 20000--或者select * from People where PeopleSalary >= 10000 and PeopleSalary =和= \'1980-1-1\' and PeopleBirth = 30 and (year(getdate())-year(PeopleBirth)) = 15000 and PeopleSalary = 24))or(month(PeopleBirth) = 11 and (day(PeopleBirth) <= 22))--查询星座为天蝎座的员工信息(10.24-11.22)select * from People where year(PeopleBirth) % 12 = 4--查询出生肖为鼠的人员信息
注:多个逻辑运算符同时使用时,加上括号可以提高运算优先级并且方便阅读
查询语句本身也可以作为一个结果再次进行操作
如查询工资比赵云高的人信息:
select * from People where PeopleSalary > (select PeopleSalary from People where PeopleName = \'赵云\')--查询工资比赵云高的人信息
查询和赵云在同一个城市的人的信息:
select * from People where PeopleAddress = (select PeopleAddress from People where PeopleName = \'赵云\')--查询和赵云在同一个城市的人的信息
case end语句基本语法
case
when 条件 then 值
when 条件 then 值
......
else 值 --若只剩下一种情况,可以使用else
end
若条件是判断等于某个值,并且每次都重复,可以省略为
case 条件
when 值 then 值
when 值 then 值
......
else 值 --若只剩下一种情况,可以使用else
end
如查询所有员工信息,添加一列显示生肖:
select PeopleName 姓名,PeopleSex 性别,PeopleSalary 工资,PeoplePhone 电话,PEOPLEBIRTH 生日,casewhen 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--或者select PeopleName 姓名,PeopleSex 性别,PeopleSalary 工资,PeoplePhone 电话,PEOPLEBIRTH 生日,case year(PeopleBirth) % 12when 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--查询所有员工信息,添加一列显示生肖
7.3.排序查询
排序查询就是根据某一字段或某一字段进行相应处理后的大小,对表中某些列的查询结果进行排序,排序查询的基本语法
select 列名,......列名 from 表名 order by 列名 PS:可加上asc/desc
注:若没有显式写上asc(升序)或desc(降序),则默认为asc(升序)
如查询所有员工信息,根据工资降序排列:
select * from People order by PeopleSalary desc--查询所有员工信息,根据工资降序排列
如查询所有员工信息,根据名字长度降序排列:
select * from People order by len(PeopleName) desc--查询所有员工信息,根据名字长度降序排列
若只需要排序查询后的某一部分行的结果,可以使用以下语法
select top 5 / top 10 percent 列名,......列名 from 表名 order by 列名 PS:可加上asc/desc
top 5 表示只取排序后前5行数据,top 10 percent表示只取排序后前10%的数据(会自动取整)
如查询工资最高的5个人的信息:
select top 5 * from People order by PeopleSalary desc--查询工资最高的5个人的信息
如查询工资最高的前10%员工的信息:
select top 10 percent * from People order by PeopleSalary desc--查询工资最高的前10%员工的信息
8.对于含有空值null的数据的操作
由于当前表内没有含有空值null的数据,首先插入一段包含null的数据
insert into People(DepartmentId,RankId,PeopleName,PeopleSex,PeopleBirth,PeopleSalary,PeoplePhone)values(1,1,\'皮卡丘\',\'男\',\'1999-7-9\',100000,\'13986598412\')
判断一个字段是否为NULL需要使用IS NULL或IS NOT NULL而不能使用=,否则查询不到信息
如查询出地址没有填写的员工信息:
select * from People where PeopleAddress IS NULL--查询出地址没有填写的员工信息
查询出地址已经填写的员工信息:
select * from People where PeopleAddress IS NOT NULL--查询出地址已经填写的员工信息
注:注意区分空值和空字符串,空值表示“无值”,空字符串表示一个长度为0的字符串,判断是否是空字符串用的是=运算符,判断是否为空值用的是IS NULL或IS NOT NULL,并且\'\',\' \',\' \'在SQL Server逻辑中是相等的(\'a\',\'a \'也相等,因为SQL Server逻辑判断时会自动忽略掉字符串后面的空格)
9.模糊查询
模糊查询一般使用like关键字和通配符结合来实现,通配符具体含义如下:
%:代表匹配0个字符、1个字符或多个字符。
_:代表匹配有且只有1个字符。
[]:代表匹配范围内,例:[2,3,4,5]或[2-5]
[^]:代表匹配不在范围内,例:[^2,3,4,5]或[^2-5]
模糊查询的语句形式如下:
select 列名,列名,......列名 from 表名 where 列名 like 值与通配符
如查询出姓马的员工信息:
select * from People where PeopleName like \'马%\'--查询出姓马的员工信息
查询出名字含有盖的员工信息:
select * from People where PeopleName like \'%盖%\'--查询出名字含有盖的员工信息
查询出名字含有盖或者史的员工信息:
select * from People where PeopleName like \'%盖%\' or PeopleName like\'%史%\'--查询出名字含有盖或者史的员工信息
查询出姓孙的员工信息,并且名字是两个字:
select * from People where PeopleName like \'孙_\'--或者select * from People where SUBSTRING(PeopleName,1,1)=\'孙\'and len(PeopleName)=2--查询出姓孙的员工信息,并且名字是两个字
查询名字最后一个字为慈的员工信息,并且名字是三个字:
select * from People where PeopleName like \'__慈\'--或者select * from People where SUBSTRING(PeopleName,3,1)=\'慈\'and len(PeopleName)=3--查询名字最后一个字为慈的员工信息,并且名字是三个字
查询出电话号码开头为138的员工信息:
select * from People where PeoplePhone like \'138%\'--查询出电话号码开头为138的员工信息
查询出电话号码开头为138,第四位是7或者8,最后一位是5的员工信息:
select * from People where PeoplePhone like \'138[7,8]%5\' --查询出电话号码开头为138,第四位是7或者8,最后一位是5的员工信息
查询出电话号码开头为138,第四位是2-5之间,最后一位不是2和3的员工信息:
select * from People where PeoplePhone like \'138[2-5]%[^2,3]\' --查询出电话号码开头为138,第四位是2-5之间,最后一位不是2和3的员工信息
10.聚合函数
SQL Server常见聚合函数:
count():求数量
max():求最大值
min():求最小值
sum():求和
avg():求平均值
where语句内需要聚合函数的返回值进行运算时,需要加上select关键字作为整体才可以使用
聚合函数的各种使用举例:
select count(*) 人数 from People--求员工总人数,count(*)返回当前表的行数select max(PeopleSalary) 最高工资 from People--求最高工资,max(PeopleSalary)返回PeopleSalary字段最大值select min(PeopleSalary) 最低工资 from People--求最高工资,min(PeopleSalary)返回PeopleSalary字段最小值select sum(PeopleSalary) 工资总和 from People--求所有员工的工资总和,sum(PeopleSalary)返回PeopleSalary字段的和 --求平均值,求所有员工的平均工资--方案一:select avg(PeopleSalary) 平均工资 from People--avg(PeopleSalary)返回PeopleSalary字段的平均值--方案二:精确到2位小数(使用round函数)select round(avg(PeopleSalary),2) 平均工资 from Peopleselect count(*) 人数, max(PeopleSalary) 最高工资, min(PeopleSalary) 最低工资, sum(PeopleSalary) 工资总和, avg(PeopleSalary) 平均工资 from People--求人数,最大值,最小值,工资总和,平均值并在一行显示
聚合函数与条件查询结合使用举例:
select \'武汉\' 地区, count(*) 人数, max(PeopleSalary) 最高工资, min(PeopleSalary) 最低工资, sum(PeopleSalary) 工资总和, avg(PeopleSalary) 平均工资 from Peoplewhere PeopleAddress = \'武汉\'--查询出武汉地区的员工人数,总工资,最高工资,最低工资和平均工资select * from People where PeopleSalary > (select avg(PeopleSalary) from People)--求出工资比平均工资高的人员信息--方案一select count(*) 人数, max(year(getdate())-year(PeopleBirth)) 最大年龄, min(year(getdate())-year(PeopleBirth)) 最小年龄, sum(year(getdate())-year(PeopleBirth)) 年龄总和, avg(year(getdate())-year(PeopleBirth)) 平均年龄 from People--方案二(使用DATEDIFF函数)select count(*) 数量, max(DATEDIFF(year, PeopleBirth, getDate())) 最高年龄, min(DATEDIFF(year, PeopleBirth, getDate())) 最低年龄, sum(DATEDIFF(year, PeopleBirth, getDate())) 年龄总和, avg(DATEDIFF(year, PeopleBirth, getDate())) 平均年龄 from People--求人数,年龄最大值,年龄最小值,年龄总和,年龄平均值,在一行显示--方案一select \'男\' 性别, \'月薪10000以上\' 月薪, max(year(getdate())-year(PeopleBirth)) 最大年龄, min(year(getdate())-year(PeopleBirth)) 最小年龄, avg(year(getdate())-year(PeopleBirth)) 平均年龄 from People where PeopleSex = \'男\' and PeopleSalary >= 10000--方案二(使用DATEDIFF函数)select \'男\' 性别, \'月薪10000以上\' 月薪, max(DATEDIFF(year, PeopleBirth, getDate())) 最大年龄, min(DATEDIFF(year, PeopleBirth, getDate())) 最小年龄, avg(DATEDIFF(year, PeopleBirth, getDate())) 平均年龄 from People--计算出月薪在10000 以上的男性员工的最大年龄,最小年龄和平均年龄--方案一select \'武汉或上海\' 地区, \'女\' 性别, max(year(getdate())-year(PeopleBirth)) 最大年龄, min(year(getdate())-year(PeopleBirth)) 最小年龄, avg(year(getdate())-year(PeopleBirth)) 平均年龄 from People where PeopleAddress IN (\'武汉\',\'上海\') and PeopleSex = \'女\'--方案二(使用DATEDIFF函数)select \'武汉或上海\' 地区, \'女\' 性别, max(DATEDIFF(year, PeopleBirth, getDate())) 最大年龄, min(DATEDIFF(year, PeopleBirth, getDate())) 最小年龄, avg(DATEDIFF(year, PeopleBirth, getDate())) 平均年龄 from People where PeopleAddress IN (\'武汉\',\'上海\') and PeopleSex = \'女\'--统计出所在地在“武汉或上海”的所有女员工数量以及最大年龄,最小年龄和平均年龄--方案一select * from People where (year(getdate())-year(PeopleBirth)) > (select avg(year(getdate())-year(PeopleBirth)) from People)--方案二(使用DATEDIFF函数)select * from People where (DATEDIFF(year, PeopleBirth, getDate())) > (select avg(DATEDIFF(year, PeopleBirth, getDate())) from People)--求出年龄比平均年龄高的人员信息
注:可以使用select关键字对查询结果进行自定义,如select \'你好\' 示例,查询结果中会显示,从而使查询结果更加友好
11.分组查询
分组查询一般与聚合函数结合使用,对查询的结果根据字段进行分组合并
分组查询的两种方式:
一:使用union进行多次查询合并(要求每一个union块的字段数量相同,对应数据类型相同才能使用,限制太多且代码冗余,不推荐使用)
二:使用group by进行分组查询
--方案一:使用union(此方案需要知道所有的地区,分别查询出所有地区的数据,然后使用union拼接起来。)select \'武汉\' 地区,count(*) 数量,max(PeopleSalary) 最高工资,min(PeopleSalary) 最低工资,sum(PeopleSalary) 工资总和,avg(PeopleSalary) 平均工资 from Peoplewhere PeopleAddress = \'武汉\' unionselect \'北京\' 地区,count(*) 数量,max(PeopleSalary) 最高工资,min(PeopleSalary) 最低工资,sum(PeopleSalary) 工资总和,avg(PeopleSalary) 平均工资 from Peoplewhere PeopleAddress = \'北京\'--方案二:使用Group by进行分组查询select PeopleAddress 地区,count(*) 数量,max(PeopleSalary) 最高工资,min(PeopleSalary) 最低工资,sum(PeopleSalary) 工资总和,avg(PeopleSalary) 平均工资 from People group by PeopleAddress--根据员工所在地区分组统计员工人数 ,员工工资总和 ,平均工资,最高工资和最低工资select PeopleAddress 地区,count(*) 数量,max(PeopleSalary) 最高工资,min(PeopleSalary) 最低工资,sum(PeopleSalary) 工资总和,avg(PeopleSalary) 平均工资 from People where year(PeopleBirth) < 1985 group by PeopleAddress--根据员工所在地区分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资,1985 年及以后出身的员工不参与统计。select PeopleAddress 地区,count(*) 数量,max(PeopleSalary) 最高工资,min(PeopleSalary) 最低工资,sum(PeopleSalary) 工资总和,avg(PeopleSalary) 平均工资 from People where year(PeopleBirth) = 2--根据员工所在地区分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资,要求筛选出员工人数至少在2人及以上的记录,并且1985年及以后出身的员工不参与统计。
注:
- 使用group by进行分组查询时,只能select聚合函数或者group by后面的字段,写其他字段从语法上或逻辑上来讲都是错误的
- group by 必须写在where语句后面,不然会报错
- 若查询的限定条件仍然带有聚合函数类型的,应当使用having替代where做限定条件,否则会报错,若限定条件不涉及聚合函数类型的,用where即可,并且having语句必须写在group by语句后面(可以理解为having语句是对分组查询后的结果再次进行条件处理)
- group by语句也可以多字段分组,此时必须满足多个字段值相同才会分到一个组内,如group by 字段1,字段2,....
12.多表查询
12.1.笛卡尔乘积
select * from 表1,表2.....
此查询结果会将每张表的所有行数据进行依次排列组合形成新的记录,例如select * from People, Department,若People表有10条记录,Department表有3条记录,则排列组合之后查询结果会有3*10=30条记录,这样的情况称之为笛卡尔乘积。通常这样的查询结果内有的记录并不是我们想要的,比如一个人并不属于软件部门,这样查询也会将这个人的信息和软件部门信息组合在一起,那么这条记录就是无意义的
12.2.简单多表查询
用where条件将主外键所属表连接成一张表(这里主外键必须表明所属表名),此种查询不符合主外键关系的数据不会被显示
如查询员工信息,同时显示部门名称:
select * from People,Department where People.DepartmentID = Department.DepartmentID --查询员工信息,同时显示部门名称
查询员工信息,同时显示职级名称:
select * from People,[Rank] where People.RankID = [Rank].RankID--查询员工信息,同时显示职级名称
查询员工信息,同时显示部门名称,职位名称:
select * from People,Department,[Rank]where People.DepartmentID = Department.DepartmentID and People.RankID = [Rank].RankID--查询员工信息,同时显示部门名称,职位名称
12.3.内连接查询
内连接查询只需要select from一张表即可,其余表用inner join连接,语句形式如下:
select * from 表1 inner join 表2 on 主外键关系(这里主外键必须表明所属表名)
若有多张需要连接的表继续inner join 即可
与简单多表查询的共同点:不符合主外键关系的数据不会显示
如查询员工信息,同时显示部门名称:
select * from People inner join Department on People.DepartmentID = Department.DepartmentID--查询员工信息,同时显示部门名称
查询员工信息,同时显示职级名称:
select * from People inner join [Rank] on People.RankID = [Rank].RankID--查询员工信息,同时显示职级名称
查询员工信息,同时显示部门名称,职位名称:
select * from People inner join Department on People.DepartmentID = Department.DepartmentIDinner join [Rank] on People.RankID = Rank.RankID--查询员工信息,同时显示部门名称,职位名称
12.4.外连接查询
- 左外连接(left join):以左表为主表,返回左表中的所有行和右表中的匹配行。如果在右表中找不到匹配的行,则使用 NULL 代替显示
select * from 表1 left join 表2 on 主外键关系(这里主外键必须表明所属表名)
若有多张需要连接的表继续left join 即可
- 右外连接(right join):右外连接和左外连接类似,A left join B == B right join A
select * from 表1 right join 表2 on 主外键关系(这里主外键必须表明所属表名)
若有多张需要连接的表继续right join 即可
- 全外连接(full join):返回左表与右表所有匹配的行。当某行在另一表中没有匹配行,则使用 NULL 代替显示
select * from 表1 full join 表2 on 主外键关系(这里主外键必须表明所属表名)
若有多张需要连接的表继续full join 即可
以下代码以左连接为例:
如查询员工信息,同时显示部门名称:
select * from People left join Department on People.DepartmentID = Department.DepartmentID --查询员工信息,同时显示部门名称
查询员工信息,同时显示职级名称:
select * from People left join [Rank] on People.RankID = [Rank].RankID--查询员工信息,同时显示职级名称
查询员工信息,同时显示部门名称,职位名称:
select * from People left join Department on People.DepartmentID = Department.DepartmentID left join [Rank] on People.RankID = Rank.RankID--查询员工信息,同时显示部门名称,职位名称
12.5.多表查询综合示例
以左外连接为例:
--查询出武汉地区所有的员工信息,要求显示部门名称以及员工的详细资料select PeopleName 姓名, People.DepartmentID 部门编号, DepartmentName 部门名称, PeopleSex 性别, PeopleBirth 生日, PeopleSalary 月薪, PeoplePhone 电话, PeopleAddress 地区from People left join Department on Department.DepartmentID = People.DepartmentID where PeopleAddress = \'武汉\' --查询出武汉地区所有的员工信息,要求显示部门名称,职级名称以及员工的详细资料select PeopleName 姓名, DepartmentName 部门名称, RankName 职位名称, PeopleSex 性别, PeopleBirth 生日, PeopleSalary 月薪, PeoplePhone 电话, PeopleAddress 地区from People left join Department on Department.DepartmentID = People.DepartmentIDleft join [Rank] on [Rank].RankID = People.RankID where PeopleAddress = \'武汉\' --根据部门分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资。--提示:在进行分组统计查询的时候添加二表联合查询。select DepartmentName 部门名称, count(*) 人数, sum(PeopleSalary) 工资总和, avg(PeopleSalary) 平均工资, max(PeopleSalary) 最高工资, min(PeopleSalary) 最低工资 from People left join DepartmentT on Department.DepartmentID = People.DepartmentIDgroup by Department.DepartmentID, DepartmentName --根据部门分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资,平均工资在10000 以下的不参与统计,并且根据平均工资降序排列。select DepartmentName 部门名称, count(*) 人数, sum(PeopleSalary) 工资总和, avg(PeopleSalary) 平均工资, max(PeopleSalary) 最高工资, min(PeopleSalary) 最低工资 from People left join DepartmentT on Department.DepartmentID = People.DepartmentIDgroup by Department.DepartmentID, DepartmentName having avg(PeopleSalary) >= 10000order by avg(PeopleSalary) desc --根据部门名称,然后根据职位名称,分组统计员工人数,员工工资总和,平均工资,最高工资和最低工资select DepartmentName 部门名称, RankName 职级名称, count(*) 人数, sum(PeopleSalary) 工资总和, avg(PeopleSalary) 平均工资, max(PeopleSalary) 最高工资, min(PeopleSalary) 最低工资from People left join DepartmentT on Department.DepartmentID = People.DepartmentIDleft join [Rank] on [Rank].RankID = People.RankID group by Department.DepartmentID, DepartmentName, [Rank].RankID,RankName
注:多表连接之后,若要对主外键字段进行查询,需要指明来自哪个表
12.6.自连接
自己连自己,例如有如下结构和数据:
create table Dept( DeptId int primary key, --部门编号 DeptName varchar(50) not null, --部门名称 ParentId int not null, --上级部门编号)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)--如果要查询出所有部门信息,并且查询出自己的上级部门,查询结果如下:--部门编号 部门名称 上级部门-- 3 软件研发部 软件部-- 4 软件测试部 软件部-- 5 软件实施部 软件部-- 6 硬件研发部 硬件部-- 7 硬件测试部 硬件部-- 8 硬件实施部 硬件部 select A.DeptId 部门编号,A.DeptName 部门名称,B.DeptName 上级名称 from Dept A inner join Dept B on A.ParentId = B.DeptId--A,B相当于创建了两张一模一样的临时表
五.数据库设计
1.数据库结构设计三范式
1.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:185699887;Tel:13885874587\')
上述设计则不满足第一范式,联系方式这一列并不是不可再分的最小单元,应修改为如下结构:
create table Student --学生表( StuId varchar(20) primary key,--学号 StuName varchar(20) not null,--学生姓名 Tel varchar(20) not null, --联系电话 QQ varchar(20) not null, --联系QQ)
第一范式的原子性是相对的,只需要满足业务需求即可,如上表无需钻牛角尖如把姓名拆成名和姓
1.2.第二范式
(前提满足第一范式)是对记录的惟一性,要求记录有惟一标识,即实体的惟一性,即不存在部分依赖,通俗讲非主属性必须完全依赖于主属性(主要是说在联合主键的情况下,非主键列不能只依赖于主键的一部分)。
如有如下表结构设计:
--选课成绩表create table StudentCourse( StuId varchar(20) not null,--学号 CourseId varchar(20) not null,--课程编号 StuName varchar(20) not null,--学生姓名 CourseName varchar(20) not null, --选课课程名称 CourseScore int not null, --考试成绩 primary key(StuId,CourseId)--联合主键学号与课程编号)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)
上述设计中有两个事物,一个学生信息,一个课程信息,很显然这两个事物都没有保证实体的唯一性,这里的姓名依赖学号,课程名称依赖课程编号,不是完全依赖主键,存在部分依赖,一张表包含太多事物,会产生很多数据冗余,所以不符合二范式,应修改为如下结构:
create table Course --课程表( CourseId int primary key identity(1,1),--课程编号 CourseName varchar(30) 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(50) not null, --学生名字 StuSex char(2) not null --学生性别)insert into Student(StuName,StuSex) values(\'刘备\',\'男\')insert into Student(StuName,StuSex) 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)
使用时将三张表连接即可,可以大大减少数据的冗余,方便维护同时减少问题
1.3.第三范式
(前提满足第二范式)要求任何字段不能由其他字段派生出来,它要求字段没有冗余,即不存在传递依赖,通俗讲就是非主属性不能与非主属性之间有依赖关系,非主属性必须直接依赖于主属性,不能间接依赖主属性。
如有如下表结构设计:
create table Student( StuId varchar(20) primary key,--学号 StuName varchar(20) not null,--学生姓名 ProfessionalId int not null,--专业编号 ProfessionalName varchar(50),--专业名称 ProfessionalRemark varchar(200), --专业介绍)insert into Student(StuId,StuName,ProfessionalId,ProfessionalName,ProfessionalRemark)values(\'001\',\'刘备\',1,\'计算机\',\'最牛的专业\')insert into Student(StuId,StuName,ProfessionalId,ProfessionalName,ProfessionalRemark)values(\'002\',\'关羽\',2,\'工商管理\',\'管理学的基础专业\')insert into Student(StuId,StuName,ProfessionalId,ProfessionalName,ProfessionalRemark)values(\'003\',\'张飞\',1,\'计算机\',\'最牛的专业\')select * from Student
上述设计中专业名称字段和专业介绍字段依赖于专业编号,专业编号依赖于主键学号,存在传递依赖,在数据库中会产生很多冗余数据,不满足第三范式,优化方案如下:
create table Professional--专业表( ProfessionalId int primary key identity(1,1),--专业编号 ProfessionalName varchar(50),--专业名称 ProfessionalRemark varchar(200), --专业介绍)create table Student--学生表( 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 Student(StuId,StuName,ProfessionalId) values(\'001\',\'刘备\',1)insert into Student(StuId,StuName,ProfessionalId) values(\'002\',\'关羽\',2)insert into Student(StuId,StuName,ProfessionalId) values(\'003\',\'张飞\',1)select * from Student
2.表关系
2.1.一对一
在实际的开发中应用不多,因为一对一可以创建成一张表。
如下学生基本信息表和学生详情表方案一,此方案要求两个表的主键相等关系确定一个学生,所以此设计必须保证主键是可以维护和编辑的,如果主键是自动增长,将很大程度增加了数据维护的难度:
create table StudentBasicInfo --学生基本信息( StuNo varchar(20) primary key not null, --学号 StuName varchar(20) not null, --姓名 StuSex nvarchar(1) not null --性别)create table StudentDetailInfo --学生详细信息( StuNo varchar(20) primary key not null, StuQQ varchar(20), --QQ stuPhone varchar(20), --电话 StuMail varchar(100), --邮箱 StuBirth date --生日)--插入数据的时候按照顺序先插入刘备的基本信息,在插入关羽的基本信息insert into StudentBasicInfo(StuNo,StuName,StuSex) values(\'QH001\',\'刘备\',\'男\')insert into StudentBasicInfo(StuNo,StuName,StuSex) values(\'QH002\',\'关羽\',\'男\')--插入数据的时候按照顺序先插入关羽的详细信息,在插入刘备的详细信息insert into StudentDetailInfo(StuNo,StuQQ,stuPhone,StuMail,StuBirth) values(\'QH002\',\'156545214\',\'13654525478\',\'guanyu@163.com\',\'1996-6-6\')insert into StudentDetailInfo(StuNo,StuQQ,stuPhone,StuMail,StuBirth)values(\'QH001\',\'186587854\',\'15326545214\',\'liubei@163.com\',\'1998-8-8\')
方案二,此方案中实际上我们是使用一对多的模式来表示一对一,保证多的表中只有一条对应数据即可,如下只需要保证一个学号对应一条学生详细信息即可:
create table StudentBasicInfo --学生基本信息( StuNo int primary key identity(1,1), --学号 StuName varchar(20) not null, --姓名 StuSex nvarchar(1) not null --性别)create table StudentDetailInfo --学生详细信息( StuDetailNo int primary key identity(1,1), --详细信息编号 StuNo int references StudentBasicInfo(StuNo) --学号,外键 StuQQ varchar(20), --QQ stuPhone varchar(20), --电话 StuMail varchar(100), --邮箱 StuBirth date --生日)
2.2.一对多(多对一)
需要在表(多方)创建一个字段,字段指向主表(一方)的主键,如下专业表和学生表:
create table Profession --专业( ProId int primary key identity(1,1), --专业编号 ProName varchar(50) not null --专业名称)create table Student --学生( StuId int primary key identity(1,1), --学生编号 ProId int not null,--专业编号,用于关联主表 StuName varchar(50) not null, --学生名字 StuSex char(2) not null --学生性别)insert into Profession(ProName) values(\'软件开发\')insert into Profession(ProName) values(\'企业信息化\')insert into Student(ProId,StuName,StuSex) values(1,\'刘备\',\'男\')insert into Student(ProId,StuName,StuSex) values(1,\'关羽\',\'男\')insert into Student(ProId,StuName,StuSex) values(2,\'张飞\',\'男\')insert into Student(ProId,StuName,StuSex) values(2,\'赵云\',\'男\')select * from Student left join Profession on Student.ProId = Profession.ProId
2.3.多对多
需要创建第三张表,中间表中至少两个字段,这两个字段分别指向各自一方的主键。如下课程表和学生表:
create table Course --课程( CourseId int primary key identity(1,1),--课程编号 CourseName varchar(30) 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(50) not null, --学生名字 StuSex char(2) not null --学生性别)insert into Student(StuName,StuSex) values(\'刘备\',\'男\')insert into Student(StuName,StuSex) 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)select * from Student inner join Exam on Student.StuId = Exam.StuIdinner join Course on Course.CourseId = Exam.CourseId
此方案中,一个学生可以有多门选课,一门课程也可以被多个学生选择,我们称之为多对多关系,在处理多对多关系的时候,我们需要建立一个中间关联表,该关联表中需要有另外两张表的主键字段。
3.数据库案例设计
业务需求说明:
模拟银行业务,设计简易版的银行数据库表结构,要求可以完成以下基本功能需求:
- 银行开户(注册个人信息)及开卡(办理银行卡)(一个人可以办理多张银行卡,但是最多只能办理3张)
- 存钱
- 查询余额
- 取钱
- 转账
- 查看交易记录
- 账户挂失
- 账户注销
表设计:
- 账户信息表:存储个人信息
- 银行卡表:存储银行卡信息
- 交易信息表(存储存钱和取钱的记录)
- 转账信息表(存储转账信息记录)
- 状态信息变化表(存储银行卡状态变化的记录,状态有1:正常,2:挂失,3:冻结,4:注销)
表结构设计:
--账户信息表:存储个人信息create table AccountInfo( AccountId int primary key identity(1,1), --账户编号 AccountCode varchar(20) not null, --身份证号码 AccountPhone varchar(20) not null, --电话号码 RealName varchar(20) not null, --真实姓名 OpenTime smalldatetime not null, --开户时间)--银行卡表:存储银行卡信息create table BankCard( CardNo varchar(30) primary key, --银行卡卡号 AccountId int not null, --账户编号(与账户信息表形成主外键关系) CardPwd varchar(30) not null, --银行卡密码 CardMoney money check(CardMoney >= 0), --银行卡余额 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) not null, --银行卡号(与银行卡表形成主外键关系) 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, --交易时间)--状态信息变化表(存储银行卡状态变化的记录,状态有1:正常,2:挂失,3:冻结,4:注销)create table CardStateChange( StateId int primary key identity(1,1),--状态信息自动编号 CardNo varchar(30) not null, --银行卡号(与银行卡表形成主外键关系) OldState int not null, --银行卡原始状态 NewState int not null, --银行卡新状态 StateWhy varchar(200) not null, --状态变化原因 StateTime smalldatetime not null, --记录产生时间)
添加测试数据:
--为刘备,关羽,张飞三个人进行开户开卡的操作--刘备身份证:420107198905064135--关羽身份证:420107199507104133--张飞身份证:420107199602034138insert into AccountInfo(AccountCode,AccountPhone,RealName,OpenTime)values(\'420107198905064135\',\'13554785425\',\'刘备\',GETDATE())insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState)values(\'6225125478544587\',1,\'123456\',0,1) insert into AccountInfo(AccountCode,AccountPhone,RealName,OpenTime)values(\'420107199507104133\',\'13454788854\',\'关羽\',GETDATE())insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState)values(\'6225547858741263\',2,\'123456\',0,1) insert into AccountInfo(AccountCode,AccountPhone,RealName,OpenTime)values(\'420107199602034138\',\'13456896321\',\'张飞\',GETDATE())insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState)values(\'6225547854125656\',3,\'123456\',0,1)select * from AccountInfoselect * from BankCard --进行存钱操作,刘备存钱2000元,关羽存钱:8000元,张飞存钱:500000元select * from AccountInfoupdate BankCard set CardMoney = CardMoney + 2000 where CardNo = \'6225125478544587\'insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)values(\'6225125478544587\',2000,0,GETDATE()) update BankCard set CardMoney = CardMoney + 8000 where CardNo = \'6225547858741263\'insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)values(\'6225547858741263\',8000,0,GETDATE()) update BankCard set CardMoney = CardMoney + 500000 where CardNo = \'6225547854125656\'insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)values(\'6225547854125656\',500000,0,GETDATE())--转账:刘备给张飞转账1000元update BankCard set CardMoney = CardMoney -1000 where CardNo = \'6225125478544587\'update BankCard set CardMoney = CardMoney + 1000 where CardNo = \'6225547854125656\'insert into CardTransfer(CardNoOut,CardNoIn,TransferMoney,TransferTime)values(\'6225125478544587\',\'6225547854125656\',1000,GETDATE())
六.使用T-SQL编程
1.信息打印
print:直接打印消息
select:在表格中打印消息,可以设置多列,以及每一列的名字
2.变量
T-SQL中变量分为局部变量和全局变量
2.1.局部变量
以@作为前缀,并且需要先声明再赋值,如下:
declare @str varchar(20)set @str = \'I like SQL\' --或者select @str = \'I like SQL\'print @str
关于set赋值和select赋值区别:
set:习惯上用于赋给变量指定的值(如上述代码)
select:习惯上用于从表中查询出的数据,由于变量只能存储一个值,若查询记录如果有多条,将最后一条记录的值赋给变量,例如:select @变量名 = 字段名 from 表名,会取出当前表该字段的最后一行值赋给变量
若一开始就知道变量的值,也可以简写为如下:
declare @num int = 100
2.2.全局变量
以@@作为前缀,并且由系统进行定义和维护,只读 ,下面列举一些常用全局变量:
- @@ERROR:返回执行的上一个语句的错误号,若没有错误,值为0
- @@IDENTITY:返回最后插入的标识值
- @@MAX_CONNECTIONS:返回允许同时进行的最大用户连接数
- @@ROWCOUNT:返回受上一语句影响的行数
- @@SERVERNAME:返回运行 SQL Server 的本地服务器的名称
- @@SERVICENAME:返回 SQL Server 正在其下运行的注册表项的名称
- @@TRANCOUNT:返回当前连接的活动事务数
- @@LOCK_TIMEOUT:返回当前会话的当前锁定超时设置(毫秒)
- @@FETCH_STATUS:表示游标提取状态,0表示提取成功,-1表示提取失败,-2表示不存在
变量使用的例子:
--为赵云此人进行开户开卡操作,赵云身份证:420107199904054233declare @AccountId intinsert into AccountInfo(AccountCode,AccountPhone,RealName,OpenTime)values(\'420107199904054233\',\'15878547898\',\'赵云\',GETDATE())set @AccountId = @@IDENTITY--返回最后插入的标识值insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState)values(\'6225123412357896\',@AccountId,\'123456\',0,1) --需要求出张飞的银行卡卡号和余额,张飞身份证:420107199602034138--方案一:连接查询select CardNo 卡号, CardMoney 余额 from BankCard left join AccountInfo on BankCard.AccountId = AccountInfo.AccountId where AccountCode = \'420107199602034138\'--方案二:使用变量declare @AccountId intselect @AccountId = (select AccountId from AccountInfo where AccountCode = \'420107199602034138\')select CardNo 卡号, CardMoney 余额 from BankCard where BankCard.AccountId = @AccountId
3.go语句
go语句的作用:
作为批处理的结束标志,并且只有go语句之前的代码执行完成之后才能执行后续代码
如下述代码会报错说DBTEST不存在:
create database DBTESTuse DBTEST
原因是SQL中create和use语句会在同一个批处理中执行,SQL Server的执行方式是一次性编译整个批处理然后按一定顺序执行,而上述代码在编译时DBTEST数据库尚未创建,因此会报错
解决办法可以讲两句代码分别单独执行,先执行create语句,再执行use语句;或使用go将两句代码分割为两个批处理,确保create语句执行完成之后才执行use语句,代码如下:
create database DBTESTgouse DBTESTgo
同时go也能限定变量的作用范围:
如下代码不会报错:
declare @num int--此处@num为全局变量(与前面的系统全局变量不是一个意思),指他的作用域为整个代码块set @num = 100set @num = 200
但下述代码会报错:
declare @num intset @num = 100go--@num的作用域到这为止set @num = 200--此时会报错@num没有定义
因为go语句将上述代码分割为了两个批处理,限定了变量@num的作用范围,此时@num是一个局部变量,作用范围只在go之前,声明之后
4.运算符
T-SQL中使用的运算符分为7种
- 算数运算符:加(+)、减(-)、乘(*)、除(/)、模(%)(没有+=类似的运算符)
- 逻辑运算符:AND、OR、LIKE、BETWEEN、IN、EXISTS是否存在、NOT、ALL() 所有、ANYI() 任一个(没有&&和||,SQL Server中是AND和OR)
- 赋值运算符:=
- 字符串运算符:+(字符串拼接,不能直接用于字符串和其它类型的数据拼接,只能先用convert或cast函数进行类型转换)
- 比较运算符:=、>、=、<=、(不等于)
- 位运算符:|、&、^
- 复合运算符:+=、-=、/=、%=、*=
运算符使用实例:
--已知长方形的长和宽,求长方形的周长和面积declare @c int = 5declare @k int = 10declare @zc intdeclare @mj intset @zc = (@c+@k)*2set @mj = @c * @kprint \'周长为:\' + Convert(varchar(20),@zc)--+不能用于字符串和其它类型的数据拼接,只能先进行类型转换print \'面积为:\' + Convert(varchar(20),@mj) --查询银行卡状态为冻结,并且余额超过1000000的银行卡信息select * from BankCard where CardState = 3 and CardMoney > 1000000--查询出银行卡状态为冻结或者余额等于0的银行卡信息select * from BankCard where CardState = 3 or CardMoney = 0--查询出姓名中含有\'刘\'的账户信息以及银行卡信息select * from AccountInfo left join BankCard on AccountInfo.AccountId = BankCard.AccountId where RealName like \'%刘%\'--查询出余额在2000-5000之间的银行卡信息select * from BankCard where CardMoney between 2000 and 5000--查询出银行卡状态为冻结或者注销的银行卡信息select * from BankCard where CardState in(3,4) --关羽身份证:420107199507104133,关羽到银行来开户,查询身份证在账户表是否存在,不存在则进行开户开卡,存在则不开户直接开卡。declare @AccountId intif exists(select * from AccountInfo where AccountCode = \'420107199507104133\') begin select @AccountId = (select AccountId from AccountInfo where AccountCode = \'420107199507104133\') insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState) values(\'6225456875357896\',@AccountId,\'123456\',0,1) endelse begin insert into AccountInfo(AccountCode,AccountPhone,RealName,OpenTime) values(\'420107199507104133\',\'13335645213\',\'关羽\',GETDATE()) set @AccountId = @@identity insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState) values(\'6225456875357896\',@AccountId,\'123456\',0,1) end --上述代码也可以使用not exists进行判断,表示不存在。--扩展:上面需求添加一个限制即一个人最多只能开3张银行卡。declare @AccountId intdeclare @count intif exists(select * from AccountInfo where AccountCode = \'420107199507104133\') begin select @AccountId = (select AccountId from AccountInfo where AccountCode = \'420107199507104133\') select @count = (select COUNT(*) from BankCard where AccountId = @AccountId) if @count <= 2 begin insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState) values(\'6225456875357898\',@AccountId,\'123456\',0,1) end else begin print \'一个人最多只能办理三张银行卡\' end endelse begin insert into AccountInfo(AccountCode,AccountPhone,RealName,OpenTime) values(\'420107199507104133\',\'13335645213\',\'关羽\',GETDATE()) set @AccountId = @@identity insert into BankCard(CardNo,AccountId,CardPwd,CardMoney,CardState) values(\'6225456875357898\',@AccountId,\'123456\',0,1) end --查询银行卡账户余额,是不是所有的账户余额都超过了3000。if 3000 < ALL(select CardMoney from BankCard) print \'所有账户余额都超过了3000\'else print \'存在有余额不超过3000的账户\' --查询银行卡账户余额,是否含有账户余额超过30000000的信息if 30000000 < ANY(select CardMoney from BankCard) print \'存在账户余额超过30000000的账户\'else print \'不存在账户余额超过30000000的账户\'
注:对于ALL和ANY的比较语句,ALL和ANY必须在比较符的右边,否则会报错
5.流程控制
5.1.选择分支结构
语法一:IF...ELSE...(SQL Server中if后面不用加(),并且语句块用begin和end代替{}),一般用于对数据库进行增删查改时的选择分支
语法形式如下:
if.....
begin
......
end
else
begin
......
end
--某用户银行卡号为“6225547854125656”,该用户执行取钱操作,取钱5000元,余额充足则进行取钱操作,并提示\"取钱成功\",否则提示“余额不足”。declare @balance moneyselect @balance = (select CardMoney from BankCard where CardNo=\'6225547854125656\')if @balance >= 5000 begin update BankCard set CardMoney = CardMoney - 5000 insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime) values(\'6225547854125656\',0,5000,GETDATE())--取钱后更改交易信息表 endelse begin print \'余额不足\' end
语法二: CASE......WHEN......(一般用于查询内的选择分支)
语法形式如下:
case case 条件=
when 条件 then 值 当条件均为判等且 when 值 then 值
when 条件 then 值 相同时也可以写为 when 值 then 值
when 条件 then 值 when 值 then 值
...... ......
else 值 else 值
end end
--查询银行卡信息,将银行卡状态1,2,3,4分别转换为汉字“正常,挂失,冻结,注销”,并且根据银行卡余额显示银行卡等级 30万以下为“普通用户”,30万及以上为\"VIP用户\",显示列分别为卡号,身份证,姓名,余额,用户等级,银行卡状态。select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,case when CardMoney < 300000 then \'普通用户\' else \'VIP用户\' end 用户等级,case when CardState = 1 then \'正常\' when CardState = 2 then \'挂失\' when CardState = 3 then \'冻结\' when CardState = 4 then \'注销\' else \'异常\'end 卡状态from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountId--或者以下写法select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,case when CardMoney < 300000 then \'普通用户\' else \'VIP用户\' 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
5.2.循环结构
while语句(SQL Server中while后面不用加(),并且语句块不需要{}而是用begin和end代替)
--循环打印1-10。declare @i int = 1while @i <= 10 begin print @i set @i = @i + 1 end --循环打印九九乘法表declare @i int = 1declare @str varchar(1000)while @i<=9 begin declare @j int = 1 set @str = \'\' while @j <= @i begin --方案一 CAST (expression AS data_type) --set @str = @str + cast(@i as varchar(2)) + \'*\' + cast(@j as varchar(2)) + \'=\' + cast(@i*@j as varchar(2)) + CHAR(9) --方案二 set @str = @str + Convert(varchar(2),@i) + \'*\' + Convert(varchar(2),@j) + \'=\' + Convert(varchar(2),@i*@j) + CHAR(9) set @j = @j + 1 end print @str set @i = @i + 1 end
SQL Server的特殊字符:char(9):制表符 char(10):换行 char(13):回车
注:SQL Server中print会自动换行,即每打印一次就会换行
6.子查询
将一个查询语句作为参数再次使用作为别的查询语句的一部分(查询语句查询出的临时表也可以像列一样直接在后面取别名然后作为一张表使用,但是该别名貌似在嵌套查询中无法使用,如下代码的倒数第二个代码段中where子查询无法直接使用别名Temp,会显示不存在)
--关羽的银行卡号为\"6225547858741263\",查询出余额比关羽多的银行卡信息,显示卡号,身份证,姓名,余额。--方案一:declare @gyBalance moneyselect @gyBalance = (select CardMoney from BankCard where CardNo=\'6225547858741263\')select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from BankCard left join AccountInfo on BankCard.AccountId = AccountInfo.AccountIdwhere CardMoney > @gyBalance--方案二:select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from BankCard left join AccountInfo on BankCard.AccountId = AccountInfo.AccountIdwhere CardMoney > (select CardMoney from BankCard where CardNo=\'6225547858741263\') --从所有账户信息中查询出余额最高的交易明细(存钱取钱信息)。--方案一:select * from CardExchange where CardNo in (select CardNo from BankCard where CardMoney = (select MAX(CardMoney) from BankCard))--方案二:--(如果有多个银行卡余额相等并且最高,此方案只能求出其中一个人的明细)select * from CardExchange where CardNo = (select top 1 CardNo from BankCard order by CardMoney desc) --查询有取款记录的银行卡及账户信息,显示卡号,身份证,姓名,余额。select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from BankCard left join AccountInfo on BankCard.AccountId = AccountInfo.AccountId where CardNo in(select CardNo from CardExchange where MoneyOutBank > 0) --查询出没有存款记录的银行卡及账户信息,显示卡号,身份证,姓名,余额。select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from BankCard left join AccountInfo on BankCard.AccountId = AccountInfo.AccountIdwhere CardNo not in(select CardNo from CardExchange where MoneyInBank > 0) --关羽的银行卡号为\"6225547858741263\",查询当天是否有收到转账。if exists(select * from CardTransfer where CardNoIn = \'6225547858741263\' and convert(varchar(22),TransferTime, 23) = convert(varchar(22),getdate(), 23)) print \'有转账记录\'else print \'没有转账记录\'--备注:上述例子也可以使用not exists来实现,表示不存在记录 --查询出交易次数(存款取款操作)最多的银行卡账户信息,显示:卡号,身份证,姓名,余额,交易次数。--方案一select top 1 BankCard.CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,exchangeCount 交易次数 from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountIdinner join(select CardNo,COUNT(*) exchangeCount from CardExchange group by CardNo) CarcExchageTempon BankCard.CardNo = CarcExchageTemp.CardNoorder by exchangeCount desc--方案二(如果有多个人交易次数相同,都是交易次数最多,则使用以下方案)select BankCard.CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,交易次数 from AccountInfoinner join BankCard on AccountInfo.AccountId = BankCard.AccountIdinner join (select CardNo,COUNT(*) 交易次数 from CardExchange group by CardNo) Temp on BankCard.CardNo = Temp.CardNo where 交易次数 = (select max(交易次数) from(select CardNo,COUNT(*) 交易次数 from CardExchange group by CardNo) Temp ) --查询出没有转账交易记录的银行卡账户信息,显示卡号,身份证,姓名,余额。select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额 from BankCard left join AccountInfo on BankCard.AccountId = AccountInfo.AccountIdwhere BankCard.CardNo not in (select CardNoIn from CardTransfer)and BankCard.CardNo not in (select CardNoOut from CardTransfer)
补充:一些SQL Server的时间函数
- GETDATE() 返回当前的日期和时间
- DATEPART() 返回日期/时间的单独部分
- DATEADD() 返回日期中添加或减去指定的时间间隔
- DATEDIFF() 返回两个日期之间的时间差
- DATENAME() 返回指定日期的指定日期部分的整数
- CONVERT(date_type(length),date_to_be_converted,style) 返回不同格式的时间 其中date_type(length)规定目标数据类型(带有可选长度),date_to_be_converted含有需要转换的值,style规定日期时间格式。
select DATEDIFF(day, \'2019-08-20\', getDate()); --获取指定时间单位的差值SELECT DATEADD(MINUTE,-5,GETDATE()) --加减时间,此处为获取五分钟前的时间,MINUTE 表示分钟,可为 YEAR,MONTH,DAY,HOURselect DATENAME(month, getDate()); --当前月份select DATENAME(WEEKDAY, getDate()); --当前星期几select DATEPART(month, getDate()); --当前月份select DAY(getDate()); --返回当前日期天数select MONTH(getDate()); --返回当前日期月数select YEAR(getDate()); --返回当前日期年数 SELECT CONVERT(VARCHAR(22),GETDATE(),20) --2020-01-09 14:46:46SELECT CONVERT(VARCHAR(24),GETDATE(),21) --2020-01-09 14:46:55.91SELECT CONVERT(VARCHAR(22),GETDATE(),23) --2020-01-09SELECT CONVERT(VARCHAR(22),GETDATE(),24) --15:04:07Select CONVERT(varchar(20),GETDATE(),14) --15:05:49:330
7.分页
使用top分页(当数据量很大时,查询效率很低)
select top (页面大小) from 表名 where 顺序编号 not in (select top (页面大小*(当前页码-1)) 顺序编号 from 表名)
使用row_number分页
select * from (select ROW_NUMBER() over(order by 顺序编号) RowId, * from 表名) Temp
where RowId between (当前页码-1)*页面大小+1 and 当前页码*@页面大小
下面以一张新建的学生表为例,假设每页五条数据:
--数据结构和数据如下:create table Student( StuId int primary key identity(1,2), --自动编号 StuName varchar(20), StuSex varchar(4))insert into Student(StuName,StuSex) values(\'刘备\',\'男\')insert into Student(StuName,StuSex) values(\'关羽\',\'男\')insert into Student(StuName,StuSex) values(\'张飞\',\'男\')insert into Student(StuName,StuSex) values(\'赵云\',\'男\')insert into Student(StuName,StuSex) values(\'马超\',\'男\')insert into Student(StuName,StuSex) values(\'黄忠\',\'男\')insert into Student(StuName,StuSex) values(\'魏延\',\'男\')insert into Student(StuName,StuSex) values(\'简雍\',\'男\')insert into Student(StuName,StuSex) values(\'诸葛亮\',\'男\')insert into Student(StuName,StuSex) values(\'徐庶\',\'男\')insert into Student(StuName,StuSex) values(\'周仓\',\'男\')insert into Student(StuName,StuSex) values(\'关平\',\'男\')insert into Student(StuName,StuSex) values(\'张苞\',\'男\')insert into Student(StuName,StuSex) values(\'曹操\',\'男\')insert into Student(StuName,StuSex) values(\'曹仁\',\'男\')insert into Student(StuName,StuSex) values(\'曹丕\',\'男\')insert into Student(StuName,StuSex) values(\'曹植\',\'男\')insert into Student(StuName,StuSex) values(\'曹彰\',\'男\')insert into Student(StuName,StuSex) values(\'典韦\',\'男\')insert into Student(StuName,StuSex) values(\'许褚\',\'男\')insert into Student(StuName,StuSex) values(\'夏侯敦\',\'男\')insert into Student(StuName,StuSex) values(\'郭嘉\',\'男\')insert into Student(StuName,StuSex) values(\'荀彧\',\'男\')insert into Student(StuName,StuSex) values(\'贾诩\',\'男\')insert into Student(StuName,StuSex) values(\'孙权\',\'男\')insert into Student(StuName,StuSex) values(\'孙坚\',\'男\')insert into Student(StuName,StuSex) values(\'孙策\',\'男\')insert into Student(StuName,StuSex) values(\'太史慈\',\'男\')insert into Student(StuName,StuSex) values(\'大乔\',\'女\')insert into Student(StuName,StuSex) values(\'小乔\',\'女\')--方案一:使用row_number分页declare @PageSize int = 5declare @PageIndex int = 1select * from (select ROW_NUMBER() over(order by StuId) RowId, * from Student) TempStuwhere RowId between (@PageIndex-1) * @PageSize+1 and @PageIndex * @PageSize --方案二:使用top分页declare @PageSize int = 5declare @PageIndex int = 1select top(@PageSize) * from Studentwhere StuId not in (select top((@PageIndex-1) * @PageSize) StuId from Student)
8.事务
将一堆操作捆绑在一起,要么均成功,要么均失败,防止数据库的数据出现逻辑不一致等错误
@@ERROR用于保存最近的一个语句错误代码值,事务的语句形式如下:
begin transaction
declare @myError interesting= 0
语句
set @myErroe = @myError + @@ERROR--每执行一步,累加myError
语句
set @myErroe = @myError + @@ERROR
......
if @myError = 0--根据myError判断上述语句段是否发生过错误
begin
commit transaction--没有错误,提交事务
end
else
begin
rollback transaction--发生错误,回滚事务
end
--人员信息如下:(第二列是身份证号,第三列是银行卡卡号)--刘备 420107198905064135 6225125478544587--关羽 420107199507104133 6225547858741263--张飞 420107199602034138 6225547854125656--假设刘备取款6000,(添加check约束,设置账户余额必须>=0),要求:使用事务实现,修改余额和添加取款记录两步操作使用事务begin transactiondeclare @MyError int = 0update BankCard set CardMoney = CardMoney-6000 where CardNo = \'6225125478544587\' set @MyError = @MyError + @@ERRORinsert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime)values(\'6225125478544587\',0,6000,GETDATE()) set @MyError = @MyError + @@ERRORif @MyError = 0 begin commit transaction print \'取款成功\' end else begin rollback transaction print \'余额不足\' end --刘备向张飞转账1000元,(添加check约束,设置账户余额必须>=0);分析步骤有三步(1)张飞添加1000元,(2)刘备扣除1000元,(3)生成转账记录;使用事务解决此问题。begin transactiondeclare @Error int = 0update BankCard set CardMoney = CardMoney -1000 where CardNo = \'6225125478544587\' set @Error = @@ERROR + @Errorupdate BankCard set CardMoney = CardMoney + 1000 where CardNo = \'6225547854125656\' set @Error = @@ERROR + @Errorinsert into CardTransfer(CardNoOut,CardNoIn,TransferMoney,TransferTime)values(\'6225125478544587\',\'6225547854125656\',1000,GETDATE())set @Error = @@ERROR + @Errorif @Error = 0 begin commit transaction print \'转账成功\' endelse begin rollback print \'转账失败\' end
9.索引
提高检索查询效率
SQL Server索引类型
按存储结构区分:聚集索引(又称聚类索引,簇集索引),非聚集索引(非聚类索引,非簇集索引)
聚集索引(clustered):根据数据行的键值在表或视图中的排序存储这些数据行,每个表只有一个聚集索引。聚集索引是一种对磁盘上实际数据重新组织以按指定的一列或多列值排序(类似字典中的拼音索引)(物理存储顺序)。聚集索引查询到的数据先后顺序就是实际先后顺序
非聚集索引(nonclustered):具有独立于数据行的结构,包含非聚集索引键值,且每个键值项都有指向包含该键值的数据行的指针。每个表可以有多个聚集索引。(类似字典中的偏旁部首索引)(逻辑存储顺序)。
SQL SERVER索引其他分类:按数据唯一性区分:“唯一索引”,“非唯一索引”;按键列个数区分:“单列索引”,“多列索引”。
创建索引的方式
1.通过显式的CREATE INDEX命令
语法形式为:创建索引:
create [UNIQUE] [CLUSTERED | NONCLUSTERED]
index ON( [ASC|DESC][,...n])
删除索引:
drop index on
如下代码示例创建索引和删除索引:
--exp:创建一个非聚集索引create nonclustered index index_Account on AccountInfo(AccountCode)--删除一个索引drop index index_Account on AccountInfo--按照显式指定索引进行查询select * from AccountInfo with(index = index_Account) where AccountCode=\'6225125478544587\'
2.在创建约束时作为隐含的对象
2.1主键约束(聚集索引)
2.2唯一约束(唯一索引)
注:关于索引的查看,当前表的索引可以直接在当前表的设计界面右键选择索引/键查看当前表所有索引属性,或在所属数据库的系统试图sys.indexes中查看
10.视图
在 SQL 中,视图是基于 SQL 语句的结果集的可视化的表。
视图包含行和列,就像一个真实的表。视图中的字段就是来自一个或多个数据库中的真实的表中的字段。(若要修改某些字段数据,不建议在视图中修改而是直接去原表修改,视图的功能很单一,就是为了方便查询和展示数据)
可以理解为虚拟表,类似于给查询结果这张临时表取名,此时这个视图会保存在当前数据库的视图栏中
语法形式如下:
创建视图:
create view 视图名 as select查询语句
删除视图:
drop view 视图名
--编写视图实现查询出所有银行卡账户信息,显示卡号,身份证,姓名,余额。create view CardAndAccount as select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额from BankCard left join AccountInfo on BankCard.AccountId = AccountInfo.AccountIdgo --如果要进行相应信息的查询,不需要编写复杂的SQL语句,直接使用视图,如下:select * from CardAndAccount
11.游标
游标:类似于指针,用于定位到结果集中某一行。
游标分类:
(1)静态游标(Static):在操作游标的时候,数据发生变化,游标中数据不变
(2)动态游标(Dynamic):在操作游标的时候,数据发生变化,游标中数据改变,默认值。
(3)键集驱动游标(KeySet):在操作游标的时候,被标识的列发生改变,游标中数据改变,其他列改变,游标中数据不变。
创建以下表结构和数据用于演示游标:
create table Member( MemberId int primary key identity(1,1), MemberAccount nvarchar(20) unique check(len(MemberAccount) between 6 and 12), MemberPwd nvarchar(20), MemberNickname nvarchar(20), MemberPhone nvarchar(20)) insert into Member(MemberAccount,MemberPwd,MemberNickname,MemberPhone)values(\'liubei\',\'123456\',\'刘备\',\'4659874564\')insert into Member(MemberAccount,MemberPwd,MemberNickname,MemberPhone)values(\'guanyu\',\'123456\',\'关羽\',\'42354234124\')insert into Member(MemberAccount,MemberPwd,MemberNickname,MemberPhone)values(\'zhangfei\',\'123456\',\'张飞\',\'41253445\')insert into Member(MemberAccount,MemberPwd,MemberNickname,MemberPhone)values(\'zhangyun\',\'123456\',\'赵云\',\'75675676547\')insert into Member(MemberAccount,MemberPwd,MemberNickname,MemberPhone)values(\'machao\',\'123456\',\'马超\',\'532523523\')
游标基本语法形式:
- 创建游标:declare 游标名 cursor scroll(可选选项scroll,scroll表示滚动游标,如没有scroll,则为只进游标,只能fetch next,即只能向下一行移动) for select 字段名 from 表名
declare mycur cursor scrollfor select MemberAccount from Member
- 打开游标:open 游标名
open mucur
- 关闭游标:close 游标名
close mycur
- 删除游标:deallocate 游标名
deallocate mycur
- 提取某行数据:
fetch first/last from 游标名--提取当前游标所指字段的第一行/最后一行
fetch absolute n from 游标名 --游标字段的第n行
fetch relative n from 游标名 --从游标当前位置数下移第n行
fetch next from 游标名 --游标当前位置的下一行
fetch prior from 游标名 --游标当前位置的上一行
fetch first from mycur --结果集的第一行fetch last from mycur --最后一行fetch absolute 1 from mycur --从游标的第一行开始数,第n行。fetch relative 3 from mycur --从当前位置数,第n行。fetch next from mycur --当前位置的下一行fetch prior from mycur --当前位置的上一行
- 提取数据给变量以供它用:
declare 变量名 变量类型
fetch absolute n from 游标名 into 变量名 --将游标下移第n行的字段数据取出给变量
--取出第3行用户名,查询该用户详细信息declare @acc varchar(30)fetch absolute 3 from mycur into @acc select * from Member where MemberAccount = @acc
- 遍历游标:利用全局变量@@fetch_status,若为0表示提取成功,-1表示提取失败,-2表示不存在
declare @acc varchar(20)fetch absolute 1 from mycur into @acc while @@FETCH_STATUS = 0 --@@FETCH_STATUS=0,提取成功,-1提取失败,-2不存在 begin print \'提取成功:\' + @acc fetch next from mycur into @acc end
利用游标进行数据的修改和删除(current of 游标名即可确认一行数据)
--更新数据fetch absolute 2 from mycurupdate Member set MemberPwd = \'1234567\' where current of mycur--删除游标所在行fetch absolute 3 from mycurdelete from Member where current of mycur
创建指向多个字段的游标,循环显示多列数据
declare mycur cursor scrollfor select MemberAccount,MemberPwd,MemberNickname from Memberopen mycurdeclare @acc varchar(20)declare @pwd varchar(20)declare @nickname varchar(20)fetch next from mycur into @acc,@pwd,@nicknamewhile @@FETCH_STATUS = 0 --@@FETCH_STATUS=0,提取成功,-1提取失败,-2行不存在 begin print \'用户名:\' + @acc+\',密码\'+@pwd+\',昵称\'+@nickname fetch next from mycur into @acc,@pwd,@nickname endclose mycur
注:fetch用于从游标所指位置提取数据,游标会在每次调用fetch之后自动移动到下一行
12.函数
函数分为系统函数和自定义函数,自定义函数又可以分为标量值函数(返回单个值),表值函数(返回查询结果),本节主要介绍自定义函数
声明并定义函数的语法形式如下:
create function 函数名(参数名 类型,参数名 类型......) returns 返回值类型
as
begin
函数体
return 值
end
注:若为无参函数,函数名后面直接写()即可;
对于函数调用,需要展示结果,使用print或select均可;
删除函数用drop function 函数名即可;
标量值函数的调用形式如下:select/print dbo.函数名(参数,参数...),表值函数的调用形式如下:select * from 函数名(参数,参数...),此时函数名前dbo.可加可不加;
编写并调用一个函数求该银行的金额总和,没有参数,返回标量值:
--函数声明create function GetSumMoney() returns moneyasbegindeclare @sum moneyselect @sum = (select sum(CardMoney) from BankCard)--此处需要括号括起来作为整体,不然会报错return @sumend--函数调用select dbo.GetSumMoney()
编写并调用一个函数,传入账户编号,返回账户真实姓名:
--声明函数create function GetRealNameByID(@accid int) returns varchar(30)asbegindeclare @realname varchar(30)select @realname = (select RealName from AccountInfo where AccountId = @accid)return @realnameend--调用函数select dbo.GetRealNameByID(1)
编写并调用一个函数,传递开始时间和结束时间,返回交易记录(存钱取钱),交易记录中包含 真实姓名,卡号,存钱金额,取钱金额,交易时间。
--方案一(逻辑复杂,函数体除了返回结果的sql语句还可以有其他逻辑代码,例如定义变量等)--声明函数create function GetExchangeByTime(@StartTime varchar(30),@EndTime varchar(30))returns @ExchangeTable table( RealName varchar(30), --真实姓名 CardNo varchar(30), --卡号 MoneyInBank money, --存钱金额 MoneyOutBank money, --取钱金额 ExchangeTime smalldatetime --交易时间)--这里查询结果表的字段名可以自定义asbegin insert into @ExchangeTable select AccountInfo.RealName,CardExchange.CardNo,CardExchange.MoneyInBank, CardExchange.MoneyOutBank,CardExchange.ExchangeTime from CardExchange left join BankCard on CardExchange.CardNo = BankCard.CardNo left join AccountInfo on BankCard.AccountId = AccountInfo.AccountId where CardExchange.ExchangeTime between @StartTime+\' 00:00:00\' and @EndTime+\' 23:59:59\' returnend--调用函数select * from GetExchangeByTime(\'2025-3-20\',\'2025-3-22\')--方案二(函数体内只能有return+sql查询结果语句)create function GetExchangeByTime(@StartTime varchar(30),@EndTime varchar(30))returns tableas return select AccountInfo.RealName,CardExchange.CardNo,CardExchange.MoneyInBank, CardExchange.MoneyOutBank,CardExchange.ExchangeTime from CardExchange left join BankCard on CardExchange.CardNo = BankCard.CardNo left join AccountInfo on BankCard.AccountId = AccountInfo.AccountId where CardExchange.ExchangeTime between @StartTime+\' 00:00:00\' and @EndTime+\' 23:59:59\'go--调用函数select * from GetExchangeByTime(\'2025-3-20\',\'2025-3-22\')
对于表值函数的声明,有两种方式,第一种复杂方式,返回值类型为table,且table前需要加上@返回表的名称,在函数体内也需要插入语句对返回表进行数据插入,这种方式逻辑复杂,函数体除了返回结果的sql语句还可以有其他逻辑代码,例如定义变量等;第二种简写方式不需要返回表的名称和结构,函数体内直接return+查询语句即可,这种方式函数体内只能有return+sql查询结果语句
编写并调用一个函数,查询银行卡信息,将银行卡状态1,2,3,4分别转换为汉字“正常,挂失,冻结,注销”,根据银行卡余额显示银行卡等级,30万以下为“普通用户”,30万及以上为\"VIP用户\",分别显示卡号,身份证,姓名,余额,用户等级,银行卡状态。
create function GetGradeByMoney(@myMoney int)returns varchar(10) asbegin declare @result varchar(10) if @myMoney < 3000 set @result = \'普通用户\' else set @result = \'VIP用户\' return @resultendgo--等级函数 create function GetStatusByNumber(@myNum int)returns varchar(10) asbegin declare @result varchar(10) if @myNum = 1 set @result = \'正常\' else if @myNum = 2 set @result = \'挂失\' else if @myNum = 3 set @result = \'冻结\' else if @myNum = 4 set @result = \'注销\' else set @result = \'异常\' return @resultendgo--状态函数--函数调用实现查询功能select CardNo 卡号,AccountCode 身份证,RealName 姓名,CardMoney 余额,dbo.GetGradeByMoney(CardMoney) 账户等级,dbo.GetStatusByNumber(CardState) 卡状态from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountId
注:若函数的参数来自于后面from的表的字段,直接在括号内写字段即可
编写并调用一个函数,根据出生日期求年龄,年龄求实岁,例如:
生日为2000-5-5,当前为2018-5-4,年龄为17岁 生日为2000-5-5,当前为2018-5-6,年龄为18岁create table Emp( Id int primary key identity(1,2), --自动编号 Name varchar(20), --姓名 Sex varchar(4), --性别 Birth smalldatetime --生日)insert into Emp(Name,Sex,Birth) values(\'刘备\',\'男\',\'2008-5-8\')insert into Emp(Name,Sex,Birth) values(\'关羽\',\'男\',\'1998-10-10\')insert into Emp(Name,Sex,Birth) values(\'张飞\',\'男\',\'1999-7-5\')insert into Emp(Name,Sex,Birth) values(\'赵云\',\'男\',\'2003-12-12\')insert into Emp(Name,Sex,Birth) values(\'马超\',\'男\',\'2003-1-5\')insert into Emp(Name,Sex,Birth) values(\'黄忠\',\'男\',\'1988-8-4\')insert into Emp(Name,Sex,Birth) values(\'魏延\',\'男\',\'1998-5-2\')insert into Emp(Name,Sex,Birth) values(\'简雍\',\'男\',\'1992-2-20\')insert into Emp(Name,Sex,Birth) values(\'诸葛亮\',\'男\',\'1993-3-1\')insert into Emp(Name,Sex,Birth) values(\'徐庶\',\'男\',\'1994-8-5\')gocreate function GetAgeByBirth(@birthday smalldatetime) returns intasbegin declare @Age int set @Age = Convert(int,year(getdate())-year(@birthday)) if month(@birthday) > month(getdate()) begin set @Age = @Age - 1 end if (month(@birthday) = month(getdate())) AND (day(@birthday) > day(getdate())) begin set @Age = @Age - 1 end return @Ageendgoselect Name 姓名, dbo.GetAgeByBirth(Birth) 年龄 from Emp
13.触发器
触发器分类:(1) “Instead of”触发器 (2)“After”触发器
“Instead of”触发器:在执行操作之前被执行
“After”触发器:在执行操作之后被执行
语法形式如下:
创建触发器:
create trigger 触发器名 on 触发器所属表名 触发器类型名 触发时机
as
触发内容
go
删除触发器:
drop trigger 触发器名 on 触发器所属表名
修改触发器:
alter trigger 触发器名 on 触发器所属表名 触发器类型名 触发时机
as
触发内容
go
注:不同的表可以有同名触发器,但同一张表不可以有同名从触发器,即使类型不同也不行
触发器中后面的案例中需要用到的表及测试数据如下:
--部门create table Department( DepartmentId varchar(10) primary key , --主键,自动增长 DepartmentName nvarchar(50), --部门名称)--人员信息create table People( PeopleId int primary key identity(1,1), --主键,自动增长 DepartmentId varchar(10), --部门编号,外键,与部门表关联 PeopleName nvarchar(20), --人员姓名 PeopleSex nvarchar(2), --人员性别 PeoplePhone nvarchar(20), --电话,联系方式)insert into Department(DepartmentId,DepartmentName)values(\'001\',\'总经办\')insert into Department(DepartmentId,DepartmentName)values(\'002\',\'市场部\')insert into Department(DepartmentId,DepartmentName)values(\'003\',\'人事部\')insert into Department(DepartmentId,DepartmentName)values(\'004\',\'财务部\')insert into Department(DepartmentId,DepartmentName)values(\'005\',\'软件部\')insert into People(DepartmentId,PeopleName,PeopleSex,PeoplePhone)values(\'001\',\'刘备\',\'男\',\'13558785478\')insert into People(DepartmentId,PeopleName,PeopleSex,PeoplePhone)values(\'001\',\'关羽\',\'男\',\'13558788785\')insert into People(DepartmentId,PeopleName,PeopleSex,PeoplePhone)values(\'002\',\'张飞\',\'男\',\'13698547125\')
假设有部门表和员工表,在添加员工的时候,该员工的部门编号如果在部门表中找不到,则自动添加部门信息,部门名称为\"新部门\"。
--编写触发器:create trigger tri_InsertPeople on Peopleafter insertasif not exists(select * from Department where DepartmentId = (select DepartmentId from inserted)) insert into Department(DepartmentId,DepartmentName) values((select DepartmentId from inserted),\'新部门\')go --测试触发器:insert People(DepartmentId,PeopleName,PeopleSex,PeoplePhone)values(\'009\',\'赵云\',\'男\',\'13854587456\')
注:inserted是一张系统临时表,用于暂时存储插入或插入后的数据,deleted同理,没有updated表,系统进行update是背后逻辑是先进行删除再添加一条数据,inserted表和deleted表用于存放对表中数据行的修改信息,他们是触发器执行时自动创建的,放在内存中,是临时表。当触发器工作完成,它们也被删除。它们是只读表,不能向它们写入内容。
删除一个部门的时候将部门下所有员工全部删除。
--编写触发器:create trigger tri_DeleteDept on Departmentafter deleteasdelete from People where People.DepartmentId = (select DepartmentId from deleted)go --测试触发器:delete Department where DepartmentId = \'001\'
删除一个部门的时候判断该部门下是否有员工,有则不删除,没有则删除。
--编写触发器create trigger tri_DeleteDeptNew on Department instead of deleteasif not exists(select * from PeoPle where DepartmentId = (select DepartmentId from deleted))begindelete from Department where DepartmentId = (select DepartmentId from deleted)endgo --测试触发器:delete Department where DepartmentId = \'001\'delete Department where DepartmentId = \'002\'delete Department where DepartmentId = \'003\'
修改一个部门编号之后,将该部门下所有员工的部门编号同步进行修改
--编写触发器:create trigger tri_UpdateDept on Departmentafter updateas update People set DepartmentId = (select DepartmentId from inserted) where DepartmentId = (select DepartmentId from deleted)go --测试触发器:update Department set DepartmentId = \'zjb001\' where DepartmentId=\'001\'
实际工作中,触发器要谨慎使用,滥用可能会造成意想不到的连锁反应
14.存储过程
存储过程(Procedure)是SQL语句和流程控制语句的预编译集合。
14.1. 没有输入参数,没有输出参数的存储过程
创建语法形式如下:
create proc 存储过程名
as
存储过程内容
go
执行存储过程:
exec 存储过程名
定义存储过程实现查询出账户余额最低的银行卡账户信息,显示银行卡号,姓名,账户余额
--方案一create proc proc_MinMoneyCardas select top 1 CardNo 银行卡号,RealName 姓名,CardMoney 余额 from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountId order by CardMoney ascgo --方案二:(余额最低,有多个人则显示结果是多个)create proc proc_MinMoneyCardas select CardNo 银行卡号,RealName 姓名,CardMoney 余额 from BankCard inner join AccountInfo on BankCard.AccountId = AccountInfo.AccountId where CardMoney=(select MIN(CardMoney) from BankCard)go --执行存储过程:exec proc_MinMoneyCard
14.2.有输入参数,没有输出参数的存储过程
创建语法形式如下:
create proc 存储过程名
输入参数名 输入参数类型,
......
输入参数名 输入参数类型
as
存储过程内容
go
执行存储过程:
exec 存储过程名 输入参数,....输入参数
模拟银行卡存钱操作,传入银行卡号,存钱金额,实现存钱操作
create proc proc_CunQian@CardNo varchar(30),@MoneyInBank moneyas update BankCard set CardMoney = CardMoney + @MoneyInBank where CardNo = @CardNo insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime) values(@CardNo,@MoneyInBank,0,GETDATE())--go --执行存储过程:exec proc_CunQian \'6225125478544587\',3000
14.3.有输入参数,没有输出参数,但是有返回值的存储过程(返回值必须整数)
创建语法形式如下:
create proc 存储过程名
输入参数名 输入参数类型,
......
输入参数名 输入参数类型
as
存储过程内容(内部包含return语句)
go
执行存储过程:
exec 存储过程名 输入参数,....输入参数
注:若需要接住存储过程的返回值,可以先声明一个变量用于存储,然后
exec 变量名 = 存储过程名 输入参数,....输入参数即可
模拟银行卡取钱操作,传入银行卡号,取钱金额,实现取钱操作,取钱成功,返回1,取钱失败返回-1
create proc proc_QuQian@CardNo varchar(30),@MoneyOutBank moneyas update BankCard set CardMoney = CardMoney - @MoneyOutBank where CardNo = @CardNo if @@ERROR 0 return -1 insert into CardExchange(CardNo,MoneyInBank,MoneyOutBank,ExchangeTime) values(@CardNo,0,@MoneyOutBank,GETDATE()) return 1go --执行存储过程:declare @returnValue intexec @returnValue = proc_QuQian \'662018092100000002\',1000000print @returnValue
14.4.有输入参数,有输出参数的存储过程
创建语法形式如下:
create proc 存储过程名
输入参数名 输入参数类型,
......
输入参数名 输入参数类型,
输出参数名 输出参数名类型 output,
......
输出参数名 输出参数名类型 output
as
存储过程内容
go
执行存储过程:
exec 存储过程名 输入参数,....输入参数,输出参数名 ouput,......输出参数名 ouput
查询出某时间段的银行存取款信息以及存款总金额,取款总金额,传入开始时间,结束时间,显示存取款交易信息的同时,返回存款总金额,取款总金额。
create proc proc_SelectExchange @startTime varchar(20), --开始时间 @endTime varchar(20), --结束时间 @SumIn money output, --存款总金额 @SumOut money output --取款总金额asselect @SumIn = (select SUM(MoneyInBank) from CardExchange where ExchangeTime between @startTime+\' 00:00:00\' and @endTime+\' 23:59:59\')select @SumOut = (select SUM(MoneyOutBank) from CardExchange where ExchangeTime between @startTime+\' 00:00:00\' and @endTime+\' 23:59:59\')select * from CardExchange where ExchangeTime between @startTime+\' 00:00:00\' and @endTime+\' 23:59:59\'go --执行存储过程:declare @SumIn money --存款总金额declare @SumOut money --取款总金额exec proc_SelectExchange \'2018-1-1\',\'2018-12-31\',@SumIn output,@SumOut outputselect @SumInselect @SumOut
14.5.具有既能输入又能输出的参数的存储过程
创建语法形式如下:
create proc 存储过程名
输入输出参数名 输入输出参数名类型 output,
......
输入输出参数名 输入输出参数名类型 output
as
存储过程内容
go
执行存储过程:
exec 存储过程名 输入输出参数名 ouput,......输入输出参数名 ouput
注:区分纯输出参数和输入输出参数就是看在声明参数是是否赋了初值带到存储过程中
密码升级,传入用户名和密码,如果用户名密码正确,并且密码长度<8,自动升级成8位密码
--有输入输出参数(密码作为输入参数也作为输出参数)--密码升级,传入用户名和密码,如果用户名密码正确,并且密码长度<8,自动升级成8位密码select FLOOR(RAND()*10) --0-9之间随机数create proc procPwdUpgrade@cardno nvarchar(20),@pwd nvarchar(20) outputas if not exists(select * from BankCard where CardNo=@cardno and CardPwd=@pwd) set @pwd = \'\' else begin if len(@pwd) < 8 begin declare @len int = 8- len(@pwd) declare @i int = 1 while @i <= @len begin set @pwd = @pwd + cast(FLOOR(RAND()*10) as varchar(1)) set @i = @i+1 end update BankCard set CardPwd = @pwd where CardNo=@cardno end endgodeclare @pwd nvarchar(20) = \'123456\'exec procPwdUpgrade \'6225547854125656\',@pwd outputselect @pwd
所有类型的存储过程删除语法形式如下:
删除存储过程:
drop 存储过程名
注:存储过程内容内部声明的变量是局部变量,作用域只在存储过程内,因此会需要返回值或者输出参数
存储过程和函数的区别:
1、存储过程用户在数据库中完成特定操作或者任务(如插入,删除等),函数用于返回特定的数据。
2、存储过程声明用procedure,函数用function。
3、存储过程不一定有返回值,或通过out返回多个输出参数,而函数必须通过return语句返回一个确定类型的标量值或表
4、SQL Server不允许函数中包含事务,但存储过程中可以包含事务
5、SQL Server不允许函数中调用存储过程,但存储过程中可以调用函数
6、SQL Server中函数可以直接嵌入到语句中(如SELECT),但存储过程必须使用专用调用语句