第7章 数据(记录)操作
本章包括
◆ 查看数据表中的记录
◆ 在数据表中插入记录
◆ 更新数据表中的记录
◆ 删除数据表中的记录
◆ 复杂的SELECT查询语句
创建完数据库和数据表之后,就可以向数据表里添加、修改和删除记录了。本章介绍如何对数据表里的记录进行添加、修改和删除操作。在本章中,如果没有特别声明,都以Northwind数据库为例进行操作。
7.1 在SQL Server Management Studio中查看记录
在SQL Server Management Studio中可以很直观地查看记录、精确地定位到某一条记录上,也可以返回头几条记录,下面介绍具体操作方法。
7.1.1 选择前1000行
下面以打开Northwind数据库中的“产品”表为例,介绍如何在SQL Server Management Studio中打开表。
step 1 启动SQL Server Management Studio,在【对象资源管理器】窗格中展开树形目录,定位到【产品】选项。
step 2 右击【产品】选项,在弹出的快捷菜单中选择【选择前1000行】选项。
step 3 在图7.1所示的界面中,程序显示了“产品”表的前1000条数据,右边显示的是“产品”表里的记录内容,该表格为【结果】窗格。
图7.1 显示前1000行数据
7.1.2 返回前几条记录
在SQL Server Management Studio中查询记录,直接打开数据表时,会返回该数据表中所有的记录。在数据量很大的情况下,只返回前几条记录会大大减少SQL Server的负担,查询的速度也会很快。下面介绍返回前几条记录的方法:
step 1 在打开表之后,调出【属性】窗格。调出方法为:单击菜单栏中的【视图】→【属性对话框】选项。如图7.2所示,在【属性】窗格里展开【Top规范】选项,在【(最前面)】下拉列表框里选择【是】选项,在【Percent】下拉列表框里选择【否】选项,在【表达式】文本框里输入数字“10”。
图7.2 返回前几行记录
step 2 选择【结果】窗格,然后单击【运行SQL】按钮,程序返回图7.2所示的结果,在【结果】窗格里只显示前10条记录。
注意 在此操作过程中,如果不在【结果】窗格里单击一下,【运行SQL】按钮是灰色的,不能单击。
如果在【Percent】下拉列表框中选择的是【是】选项,那么在【表达式】框中设置的就是返回数量占总记录数的百分比。在本例中,“产品”表里一共有77条记录,那么返回的记录数就为8条。
7.2 在SQL Server Management Studio中插入记录
打开表之后,在最后一条记录下面会有一条所有字段都为Null的记录,如图7.2所示。在此可以输入新记录的内容。在输入新记录内容时,有几点需要注意。
主键并且是标识列的字段不能输入字段内容,因为它是由SQL Server自动维护的字段,该字段是只读的。
计算列也不能输入字段内容,它也是由SQL Server自动维护的字段,该字段也是只读的。
输入字段内容的数据类型要和字段定义的数据类型一致,包括数据类型、长度、精度等,否则会出现图7.3所示的警告提示“单元格的值无效”。在此警告框里会显示第几行第几列的数据不正确。
图7.3 警告框
不能为Null的字段一定要输入字段内容,否则会出现图7.4所示的“未更新任何行”提示信息,在该警告框里可以看到表的哪个字段不允许有空值。如果是可以为Null的字段,则可以不输入字段内容。
图7.4 提示未更新任何行
如果字段是外键,一定要符合外键要求。在本例中,“供应商ID”和“类别ID”都是外键,如果输入一个“供应商表”里不存在的“供应商ID”,将会出现图7.5所示的“未更新任何行”提示信息,在该警告框里会显示表的哪个字段违反了什么外键的约束。
图7.5 提示未更新任何行
如果有其他约束,例如Check约束和Unique约束,也必须满足这些约束。
如果要让字段输入默认值,则不用在字段内输入任何数据,在保存记录时,SQL Server会自动填入默认值,但前提是该字段设置有默认值。
在输入完记录的各字段之后,只要将光标定位到其他记录上,或关闭【结果】窗格,新记录就会自动保存。
技巧 如果在保存新插入的记录之前,发现插入的数据有误,可以直接修改。按一下【Esc】键可以取消当前字段的输入。如果想取消插入记录,可连按两下【Esc】键。
在SQL Server 2008中,允许通过复制和粘贴来插入新的记录,类似在Word表格里的操作,其操作方法如下:
step 1 打开数据表,右击要复制的记录,在弹出的快捷菜单里选择【复制】选项。
step 2 右击【结果】窗格的最后一行,也就是插入记录的那一行,在弹出的快捷菜单里选择【粘贴】选项。
step 3 将光标定位到其他记录上,就完成了插入操作。
此时,SQL Server会自动插入一条与复制记录完全相同的记录,但如果数据表中有标识列,标识列的内容不会被复制。
7.3 用lnsert语句插入记录
在T-SQL中,可以使用Insert语句插入记录。T-SQL语句可以在查询编辑器中执行。有关查询编辑器的使用介绍,请参照本书3.3.3节。下面是对Insert语句的介绍。
7.3.1 基本语法
Insert语句的其本语法如下:
INSERT [ TOP ( expression ) [ PERCENT ] ] --插入记录数或百分比数 [ INTO] --可选参数 { <object> --数据表或视图 | rowset_function_limited --OPENQUERY或OPENROWSET函数 [ WITH ( <Table_Hint_Limited> [ ...n ] ) ] --指定目标表所允许的一个或多个表提示 } { [ ( column_list ) ] --要插入数据的一列或多列的列表 [ <OUTPUT Clause> ] --将插入行作为插入操作的一部分返回 { VALUES ( { DEFAULT | NULL | expression } [ ,...n ] ) --引入要插入的数据值的列表 | derived_table --SELECT语句 | execute_statement --EXECUTE语句 } } | DEFAULT VALUES --强制新行包含为每个列定义的默认值 [; ]
7.3.2 参数说明
Insert语句的参数不是太多,下面介绍一些比较常用的。
◆ TOP ( expression ) [ PERCENT ]:该参数一般与Select语句组合使用,用于指定将插入的行数或百分比。expression可以是行数或行的百分比。
◆ INTO:可选参数,可以将它用在Insert和目标表之间。
◆ <object>语法块:该语法块用于指明要插入数据的表或视图,其代码如下所示。
<object> ::= { [ server_name . --服务器名 database_name . --数据库名 schema_name . --架构名 | database_name .[ schema_name ] . | schema_name . ] table_or_view_name --表或视图名 }
参数说明如下:
◆ server_name:表或视图所在服务器的名称。如果指定了server_name,则需要指定database_name和schema_name。
◆ database_name:数据库的名称。
◆ schema_name:该表或视图所属架构的名称。
◆ table_or_view_name:要接收数据的数据表或视图的名称。
◆ rowset_function_limited:OPENQUERY或OPENROWSET函数。
◆ WITH ( <Table_Hint_Limited> [ ...n ] ):指定目标表所允许的一个或多个表提示。需要有WITH关键字和括号。
◆ column_list:要在其中插入数据的一列或多列的列表。必须用括号将column_list括起来,并且用逗号进行分隔。
◆ OUTPUT子句:将插入行作为插入操作的一部分返回。
◆ VALUES:引入要插入的数据值的列表。对于column_list(如果已指定)或表中的每个列,都必须有一个数据值。必须用圆括号将值列表括起来。
◆ DEFAULT:插入为列定义的默认值。
◆ NULL:插入Null值。
◆ Expression:插入一个常量、变量或表达式。
◆ derived_table:任何有效的Select语句,它返回将加载到表中的数据行。
◆ execute_statement:任何有效的EXECUTE语句,它使用SELECT或READTEXT语句返回数据。
◆ DEFAULT VALUES:强制新行包含为每个列定义的默认值。
7.3.3 简单的插入语句
例一:插入一条记录。
在“订单明细”表里插入一条记录。其中,“订单ID”字段的值为10248,“产品ID”字段的值为1,“单价”字段的值为10,“数量”字段的值为2,“折扣”字段的值为0.8。其代码如下:
INSERT订单明细VALUES (10248,1,10,2,0.8)
在本例中,为数据表中的每一个字段都插入了字段内容,由于提供了所有字段的值并按表中各字段的顺序列出这些值,因此不必再指定字段名。
7.3.4 按表中不同字段顺序插入记录
例二:按不同的字段顺序插入记录。
在“订单明细”表里插入一条记录。其中,“订单ID”字段的值为10248,“产品ID”字段的值为2,“单价”字段的值为26,“数量”字段的值为3,“折扣”字段的值为0.8。其代码如下:
INSERT INTO订单明细 (折扣, 数量, 单价, 产品ID, 订单ID) VALUES (0.8, 3, 26, 2, 10248)
在本例中,并不是按表中各字段的顺序列出所有的字段内容,所以要指定字段列表。
7.3.5 插入值少于字段数的记录
例三:插入值少于字段数的记录。
在“类别”表中输入一条记录。其中,“类别名称”字段的值为“图书”,“说明”字段的值为“所有类型的图书”。其代码如下:
INSERT INTO类别 (类别名称, 说明) VALUES (N'图书',N'所有类型的图书')
在本例中,没有插入“类别ID”和“图片”两个字段的数据。因为“类别ID”字段是自动增长的标识列字段,SQL Server会自动加入字段内容,。“图片”字段是可以为Null的,如果不指定其字段内容,则它的字段内容为Null。
7.3.6 在标识列字段里插入字段内容
例四:插入带标识字段列的记录。
在“类别”表中输入一条记录。其中,“类别ID”字段的值为100,“类别”字段的值为“电器”。其代码如下:
SET IDENTITY_INSERT类别ON; GO INSERT INTO类别 (类别ID,类别名称) VALUES (100,N'电器') GO SELECT * FROM类别 GO
由于类别ID是标识列,所以在一般情况下,是只读列,不允许插入数据。如果要插入数据,必须先用“SET IDENTITY_INSERT类别ON;”,然后才可以在该字段内插入数据。插入语句运行完后,用Select语句查询一下,看看“类别ID”是不是已经插入了数据。此时,“类别ID”的标识已经到了100,再插入下一条记录时,“类别ID”会自动变成101。
7.3.7 从数据表中查询记录并插入到另一个表中
例五:将查询结果插入到另外一个表中。
从“雇员”表里查出“雇员ID”、“姓氏”、“名字”、“邮政编码”、“城市”、“地址”、“家庭电话”七个字段,组成一个“雇员通讯录”表。其代码如下:
CREATE TABLE雇员通讯录( 雇员ID int PRIMARY KEY, 姓氏nvarchar(20) NOT NULL, 名字nvarchar(10) NOT NULL, 邮政编码nvarchar(10) NULL, 城市nvarchar(15) NULL, 地址nvarchar(60) NULL, 家庭电话nvarchar(24) NULL ) GO INSERT INTO雇员通讯录 SELECT 雇员ID, 姓氏, 名字, 邮政编码, 城市, 地址, 家庭电话 FROM 雇员 GO SELECT * FROM雇员通讯录 GO
在本例中,新建了一个“雇员通讯录”表,然后从“雇员”表里查出所有雇员记录中的有效字段,并插入到“雇员通讯录”表中,最后用Select语句查看插入的结果。如果只插入前5条记录,可以用以下代码:
DELETE雇员通讯录 GO INSERT top (5) INTO雇员通讯录 SELECT 雇员ID, 姓氏, 名字, 邮政编码, 城市, 地址, 家庭电话 FROM 雇员 GO
由于“雇员通讯录”表中已经包含与要插入的记录相同的“雇员ID”,所以先将其删除。
7.3.8 从数据表中查询部分字段记录并插入到另一个表中
例六:将查询出来的部分记录插入另一个表中。
从“雇员”表里查出“雇员ID”、“姓氏”、“名字”、“邮政编码”、“地址”5个字段,插入到“雇员通讯录”表。其代码如下:
DELETE雇员通讯录 GO INSERT雇员通讯录(雇员ID, 姓氏, 名字, 邮政编码, 地址) SELECT 雇员ID, 姓氏, 名字, 邮政编码, 地址 FROM 雇员 GO
在本例中,并没有将“雇员通讯录”表中所有的字段都填入数据,未填入数据的字段会自动设为Null。
7.3.9 从数据表中查询记录插入其他表并为字段添加固定内容
例七:复杂的查询和插入。
从“雇员”表里查出“雇员ID”、“姓氏”、“名字”、“邮政编码”、“地址”五个字段,插入到“雇员通讯录”表,并在插入的所有记录的“城市”字段都填入“北京”字样。其代码如下:
DELETE雇员通讯录 GO INSERT雇员通讯录(雇员ID, 姓氏, 名字, 邮政编码, 地址, 城市) SELECT 雇员ID, 姓氏, 名字, 邮政编码, 地址, '北京' FROM 雇员 GO
7.3.10 在lnsert语句中使用exec子句
例八:使用exec子句。
查看数据库实例中当前用户和进程的信息,插入到一个表中。其代码如下:
CREATE TABLE用户与进程信息( 编号int PRIMARY KEY IDENTITY, 进程ID smallint, 定线程上下文ID smallint, 进程状 态nchar(30), 登录名nchar(128), 主机名nchar(128), 阻塞进程的系统进程ID nchar(5), 数据库名nchar(128), 运行命令nchar(16), 请求ID int, 查询时间smalldatetime DEFAULT getdate() ) GO INSERT用户与进程信息(进程ID,定线程上下文ID,进程状态,登录名,主机名, 阻塞进程的系统进程ID,数据库名,运行命令,请求ID) EXEC sp_who SELECT * FROM用户与进程信息
7.3.11 在数据中只插入默认值
例九:插入带默认值的记录。
在“用户与进程信息”表中插入一条记录,该记录只插入了有默认值的字段内容。其代码如下:
INSERT INTO用户与进程信息 DEFAULT VALUES
7.4 插入记录的注意事项
在SQL Server Management Studio中,无论是插入记录,还是更新记录,都必须先打开数据表。打开数据表后,找到要修改的记录,然后可以在记录上直接修改字段内容。修改完毕之后,只需将光标从该记录上移开,定位到其他记录上,SQL Server就会将修改的记录保存。在修改记录内容时,除了要注意插入记录时的注意事项之外,还要注意以下几点。
◆ 在修改char和nchar这类长度固定的字段时,要注意其后的空格,因为在输入数据时,如果数据长度比字段定义的长度短,SQL Server会自动将不足的部分补上空格。如果没有注意后面的空格,很容易出现超过长度限制的警告。
◆ 在可以为Null的字段中,如果要设置为Null,可以按下【Crtl+0】组合键。
◆ 如果将记录字段内容修改过后,又想恢复修改前的值,可以将光标定位到该字段,然后按下【Esc】键。
◆ 如果想放弃整条记录的修改,可以连按两次【Esc】键。
7.5 用Update语句更新记录
在T-SQL语言中,可以使用Update语句更新记录内容。下面是对Update语句的介绍。
7.5.1 基本语法
Update语句的基本语法如下:
UPDATE [ TOP ( expression ) [ PERCENT ] ] --更新记录数或百分比数 { <object> --要更改数据的表或视图的名称 | rowset_function_limited --OPENQUERY或OPENROWSET函数 [ WITH ( <Table_Hint_Limited> [ ...n ] ) ]--指定目标表允许的一个或多个表提示 } SET --指定要更新的列或变量名称的列表 { column_name={ expression | DEFAULT | NULL } --指定更改的数据的字段 | { udt_column_name.{ { property_name=expression --更改用户定义类型字段 | field_name=expression } | method_name ( argument [ , ...n ] ) } } | column_name { .WRITE ( expression , @Offset , @Length ) } --指定更改的 数据的字段 | @variable=expression --已声明的变量 | @variable=column=expression [ , ...n ] } [ , ...n ] [ <OUTPUT Clause> ] --返回更新后的数据或基于更新后的数据的表达式 [ FROM{ <table_source> } [ , ...n ] ] --指定将表、视图或派生表源用于为更新操作提供条件 [ WHERE { <search_condition> --指定条件来限定所更新的行 | { [ CURRENT OF --指定更新在指定游标的当前位置进行 { { [ GLOBAL ] cursor_name } --指定cursor_name涉及到全局游标 | cursor_variable_name --要从中进行提取的开放游标的名称 } ] } } ] [ OPTION ( <query_hint> [ , ...n ] ) ] --指定优化器提示用于自定义数据库引擎处理语句 的方式 [ ; ]
7.5.2 参数说明
Update语句的参数不是太多,下面介绍一些比较常用的参数。
◆ TOP ( expression ) [ PERCENT ]:用于指定将更新的行数或百分比。expression可以是行数或行的百分比。
◆ <object>语法块:该语法块主要用于指明要更新数据的表或视图,其代码如下所示。
<object> ::= { [ server_name . database_name . schema_name . | database_name .[ schema_name ] . | schema_name . ] table_or_view_name}
其参数包括:
● server_name:表或视图所在服务器的名称。如果指定了server_name,则需要指定database_name和schema_name。
● database_name:数据库的名称。
● schema_name:该表或视图所属架构的名称。
● table_or_view_name:要接收数据的表或视图的名称。
◆ rowset_function_limited:OPENQUERY或OPENROWSET函数,视提供程序的功能而定。
◆ WITH ( <Table_Hint_Limited>:指定目标表允许的一个或多个表提示,需要有WITH关键字和括号。
◆ SET:指定要更新的字段或变量名称的列表。
◆ column_name:指定更改的数据的字段。column_name必须已存在于table_or view_name中的字段。
◆ expression:用于设置column_name的值的变量、文字值、表达式或嵌套select语句(加括号)。
◆ DEFAULT:用于指定将字段定义的默认值替换该字段的现有值。如果该字段没有默认值并且定义为允许空值,则该参数也可用于将字段更改为Null。
◆ udt_column_name:用户定义类型字段。
◆ property_name或field_name:用户定义类型的公共属性或公共数据成员。
◆ method_name ( argument [ , ...n ] ):带一个或多个参数的udt_column_name的非静态公共赋值函数方法。
◆ WRITE ( expression , @Offset , @Length ):指定修改字段值的一部分。用expression替换@Length单位(从column_name的@Offset开始)。只有varchar(max), nvarchar(max)或varbinary(max)字段才能使用此子句来指定。字段名不能为Null,也不能由表名或表别名限定。
◆ @variable:已声明的变量,该变量将设置为expression所返回的值。SET @variable=column=expression将变量设置为与字段相同的值。这与SET @variable=column,column=expression不同,后者将变量设置为字段更新前的值。
◆ <OUTPUT Clause>:在UPDATE操作中,返回更新后的数据或基于更新后的数据的表达式。
◆ FROM:指定将表、视图或派生表源用于为更新操作提供条件。
◆ WHERE:指定条件来限定所更新的行。
◆ <search_condition>:为要更新的行指定需满足的条件。
◆ CURRENT OF:指定更新在指定游标的当前位置进行。
◆ GLOBAL:指定cursor_name涉及到全局游标。
◆ cursor_name:要从中进行提取的开放游标的名称。如果同时存在名为cursor_name的全局游标和局部游标,那么,在指定了GLOBAL时,该参数指全局游标,否则指局部游标。游标必须允许更新。
◆ cursor_variable_name:游标变量的名称,必须引用允许更新的游标。
◆ OPTION:指定优化器提示用于自定义数据库引擎处理语句的方式。
7.5.3 简单的更新语句
例十:更新某字段。
在“类别”表里,将“类别名称”为“电器”的记录的“说明”字段的内容改为“家用电器”。其代码如下:
UPDATE 类别 SET 说明=N’家用电器’ WHERE (类别名称=N’电器’)
注意 由于UPDATE语句一次可以更新多条记录,所以在使用WHERE子句时,一定要小心。如果省略了WHERE子句,则会更新所有记录。
7.5.4 使用计算值更新记录
例十一:通过计算值更新记录。
在“产品”表中,牛奶涨价50%。其代码如下:
UPDATE产品 SET单价=单价*1.5 WHERE产品名称=N’牛奶’
7.5.5 引用其他表里的字段值更新记录
例十二:通过其他表的字段值更新记录。
将“订单明细”表中订单产品为“牛奶”的单价改为与“产品”表中“牛奶”的单价一致。其代码如下:
UPDATE订单明细 SET单价=产品.单价 FROM产品 WHERE (订单明细.产品ID=产品.产品ID) AND (产品.产品名称=N’牛奶’)
7.5.6 使用top子句更新记录
例十三:使用top子句更新记录。
在上例中,只更新排在前面的前10%条记录。其代码如下:
UPDATE top (10) PERCENT订单明细 SET单价=产品.单价 FROM产品 WHERE (订单明细.产品ID=产品.产品ID) AND (产品.产品名称=N’牛奶’)
7.6 在SQL Server Management Studio中删除记录
在SQL Server Management Studio中删除记录,必须先打开数据表。打开数据表后,选中要删除的记录,右击该记录,在弹出的快捷菜单里选择【删除】选项,然后在弹出的警告对话框里单击【是】按钮,完成删除操作。在删除记录时,需要注意以下几点:
◆ 记录删除之后不能进行撤销,所以在删除之前,一定要确认无误。
◆ 一次可以删除多条记录,按住【Shift】或【Ctrl】键,可以选择多条记录。
◆ 在选择记录后,按下【Delete】键也可以进行删除操作。
◆ 如果要删除的记录是其他表的外键指向,删除操作可能会影响外键表。例如删除“类别”表里的记录,有可能会将“产品”表里的数据删除,具体情况要看外键是如何定义的。
7.7 用Delete语句删除记录
在T-SQL语言中,可以使用Delete语句删除记录,下面介绍有关内容。
7.7.1 基本语法
Delete语句的其本语法如下:
DELETE [ TOP ( expression ) [ PERCENT ] ] --要删除的行数 [ FROM ] { <object> | rowset_function_limited --openquery或openowset函数 [ WITH ( <table_hint_limited> [ ...n ] ) ] --指定一个或多个表提示 } [ <OUTPUT Clause> ] --将已删除的行或行表达式返回 [ FROM <table_source> [ , ...n ] ] [ WHERE { <search_condition> --删除行的条件 | { [ CURRENT OF --删除游标的当前行 { { [ GLOBAL ] cursor_name } --游标名 | cursor_variable_name --游标变量名 } ] } } ] [ OPTION ( <Query Hint> [ , ...n ] ) ] --指定优化器提示 [; ]
7.7.2 参数说明
Delete语句的参数不是太多,下面介绍一些比较常用的参数。
◆ TOP ( expression ) [ PERCENT ]:用于指定将要删除的行数或百分比。expression可以是行数或行的百分比。
◆ FROM:可选的关键字,可用在Delete关键字与目标table_or_view_name或rowset_function_limited之间。
◆ <object>语法块:该语法块主要用于指明要删除数据的表或视图,其代码如下所示。
<object> ::= { [ server_name . database_name . schema_name . | database_name .[ schema_name ] . | schema_name . ] table_or_view_name}
其参数包括:
● server_name:表或视图所在服务器的名称。如果指定了server_name,则需要指定database_name和schema_name。
● database_name:数据库的名称。
● schema_name:该表或视图所属架构的名称。
● table_or_view_name:要接收数据的表或视图的名称。
◆ rowset_function_limited:OPENQUERY或OPENROWSET函数,视提供程序的功能而定。
◆ WITH ( <Table_Hint_Limited>:指定目标表允许的一个或多个表提示,需要有WITH关键字和括号。
◆ <OUTPUT Clause>:将已删除行或基于这些行的表达式作为DELETE操作的一部分返回。
◆ FROM:指定附加的From子句。这个对Delete的T-SQL扩展允许从<table_source>指定数据,并从第一个From子句内的表中删除相应的行。
◆ WHERE:指定用于限制删除行数的条件。如果没有提供Where子句,则删除表中的所有行。
◆ <search_condition>:指定删除行的限定条件。
◆ CURRENT OF:指定删除在指定游标的当前位置进行。
◆ GLOBAL:指定cursor_name涉及全局游标。
◆ cursor_name:要从中进行提取的开放游标的名称。如果同时存在名为cursor_name的全局游标和局部游标,那么在指定了GLOBAL时,该参数指全局游标,否则指局部游标。游标必须允许更新。
◆ cursor_variable_name:游标变量的名称,必须引用允许更新的游标。
◆ OPTION:指定优化器提示,用于自定义数据库引擎处理语句的方式。
7.7.3 简单的删除语句
例十四:删除某记录。
在“类别”表里将“类别名称”为“电器”的记录删除。其代码如下:
DELETE类别 WHERE类别名称=N’电器’
注意 由于Delete语句一次可以删除多条记录,所以在使用WHERE子句时,一定要小心。如果省略了WHERE子句,则会删除所有记录。
7.7.4 删除多条记录
例十五:删除多条记录。
在“订单”表中,删除订购时间在1999年8月1日以前的订单。其代码如下:
DELETE订单 WHERE订购日期 < '1996.8.1'
7.7.5 引用其他表里的字段值来删除记录
例十六:通过其他表字段来删除记录。
删除雇员“王伟”的所有订单。其代码如下:
DELETE订单 FROM雇员 WHERE (雇员.雇员ID=订单.雇员ID) AND (雇员.姓氏=N’王’) AND (雇员.名字=N’伟’)
7.8 用Truncate table语句删除记录
在T-SQL语言中,还提供了一个Truncate table语句删除记录,它相当于“DELETE表名”,用于删除数据表中所有的记录。其语法代码为:
TRUNCATE TABLE [ { database_name.[ schema_name ]. | schema_name . } ] table_name [ ; ]
例如要删除“订单明细”表里的所有记录,可以使用以下代码:
TRUNCATE TABLE订单明细
Truncate table与Delete相比有以下几个不同点:
◆ Delete每删除一条记录,都会将操作过程记录在事务日志文件中,而Truncate table语句不会将删除记录的操作过程记录在事务日志文件中。所以用Truncate table删除所有记录的速度快,但删除之后不能用事务日志文件恢复。
◆ Delete语句在删除记录时,要先将表中的各行锁定,才能删除记录,而Truncate table不会锁定各行,只锁定表和页。
◆ 用Truncate table删除完记录后,自动增长的字段(标识列)会重新开始编号,而用Delete语句删除之后,会以上次最后记录为开始点继续编号。
◆ 如果要删除记录的表是其他表外键指向的表,那么不能用Truncate table语句来删除,只能用Delete语句删除。
◆ Truncate table语句不能删除参与索引视图的表,而Delete语句可以。
7.9 用Select语句进行查询
T-SQL语言中的Select语句用于从数据库表或视图中查询数据,并且可以从一个或多个表/视图中选择一个或多个行/列。Select语句的完整语法比较复杂,下面会分为一个个子句进行介绍,其主要子句包括:Select子句、From子句、Where子句、Group by子句、Having子句、Order by子句。在查询之间还可以使用union,except和intersect运算符,将各个查询的结果合并或比较到一个结果集中。Select语句的语法基本结构如下:
SELECT [ ALL | DISTINCT ] [TOP expression [PERCENT] [ WITH TIES ] ] < select_list > [ INTO new_table ] [ FROM { <table_source> } [ , ...n ] ] [ WHERE <search_condition> ] [ GROUP BY [ ALL ] group_by_expression [ , ...n ] [ WITH { CUBE | ROLLUP } ] ] [ HAVING < search_condition > ]
由于Select语句比较复杂,后面将逐个介绍其子句。
7.10 用Select子句设定查询内容
Select子句的作用是指定查询返回的列。下面是对Select子句的介绍。
7.10.1 基本语法
Select子句的语法代码如下:
SELECT [ ALL --所有行 | DISTINCT ] --唯一行 [ TOP expression [ PERCENT ] [ WITH TIES ] ] --返回结果集的头几行 <select_list> <select_list> ::= { * --所有列 | { table_name | view_name | table_alias }.* --指定列及列所在的表或视图 | { column_name --返回的列名 | [ ] expression --返回表达式列 | $IDENTITY --返回标识列 | $ROWGUID } --返回GUID列 | udt_column_name --返回CLR列名 [ { . | :: } --指定CLR的方法、属性或字段 { { property_name --公共属性 | field_name } --公共数据成员 | method_name(argument [, ...n] ) } ] --公共方法 [ [ AS ] column_alias ] | column_alias=expression --替换列名 } [ , ...n ]
7.10.2 参数说明
Select子句包含以下参数:
◆ ALL:指定在结果集中可以包含所有行,此参数为默认值。
◆ DISTINCT:指定在结果集中只能包含唯一行,在此关键字中Null值是相等的。
◆ TOP expression [ PERCENT ] [ WITH TIES ]:指定只返回查询结果集中的前几行或结果集中百分比数的行。expression可以是指定数目或百分比数目的行。
◆ <select_list>:指定要显示的列。选择列表是以逗号分隔的一系列表达式。可在选择列表中指定的表达式的最大数目是4096列。
◆ *:指定返回From子句中的所有表和视图中的所有列。这些列按From子句中指定的表或视图顺序返回,并对应于它们在表或视图中的顺序。
◆ { table_name | view_name | table_alias }.*:指定要显示的列及其所在的表或视图。
◆ column_name:指定要返回的列名。
◆ expression:代表列名、常量、函数以及由一个或多个运算符连接的列名、常量和函数的任意组合,或者是子查询。
◆ $IDENTITY:用于返回标识列。
◆ $ROWGUID:用于返回GUID列。
◆ udt_column_name:要返回的CLR(公共语言运行时)用户定义类型列的名称。
◆ { . | :: }:用于指定CLR用户自定义类型的方法、属性或字段。其中,“.”用于实例(非静态)方法、属性或字段,“::”用于静态方法、属性或字段。
◆ property_name:用于指定udt_column_name的公共属性。
◆ field_name:用于指定udt_column_name的公共数据成员。
◆ method_name:用于采用一个或多个参数的udt_column_name的公共方法。
◆ column_alias:查询结果集内替换列名的可选名。
7.10.3 查询表中所有列
例十七:查询所有列。
查看“类别”表中的所有记录,其代码如下:
SELECT * FROM类别
查询结果如图7.6所示。
图7.6 查询表中的所有列
7.10.4 查询表中某几列
例十八:查询表中的某几列。
查看“类别”表中的“类别名称”和“说明”字段,其代码如下:
SELECT类别名称,说明 FROM类别 以上代码还可以写为: SELECT类别.类别名称,类别.说明 FROM类别
查询结果如图7.7所示。
图7.7 查询表中某几列
7.10.5 为查询添加计算列
例十九:为查询结果添加计算列。
查看“订单明细”表中每个订单的总价,其代码如下:
SELECT 订单ID, 产品ID, 单价,数量,折扣,单价 * (1 . 折扣) * 数量AS总价 FROM 订单明细
在以上代码中,除了“总价”列是通过计算得出来的虚拟列之外,其他各列都是“订单明细”表中的列。由于本例中将“订单明细”表中所有的列都显示出来,所以代码还可以简化成:
SELECT *, 单价 * (1 . 折扣) * 数量AS总价 FROM 订单明细
查询结果如图7.8所示。
图7.8 为查询添加计算列
由于计算列不是数据表中的列,所以使用“as”关键字来指定列的别名。即使要查询的列是数据表中真实存在的列,也可以用“as”来指定别名。例如:
SELECT 订单ID as订单编号,产品ID as产品编号, 单价,数量,折扣,单价 * (1 . 折扣) * 数量AS总价 FROM 订单明细
其查询结果如图7.9所示,“订单ID”的列名已经变成“订单编号”。
图7.9 指定别名
7.10.6 查看最前记录
例二十:查看前几条记录。
查看订购数量最多的10条订单记录,其代码如下:
SELECT TOP 10 * FROM 订单明细 ORDER BY数量DESC
查询结果如图7.10所示,“select top 10”代表前10条记录。在本例中,将所有记录按“数量”字段内容倒序排序,再取前10条记录,就可以显示订购数量最多的10条订单记录。如果要显示所有记录中的前10%条记录,可以用以下代码来查询。
图7.10 查询表中前10条记录
SELECT top 10 PERCENT * FROM 订单明细 ORDER BY数量DESC
上例中,用“select top 10”可以显示前10条记录,但是如果第11条记录的订购数量也是110,那么用“select top 10”就不能显示出来了。这种情况在Select语句里,可以用“With Ties”关键字来显示,tie是“与……打成平局”的意思,也就是显示排名并列的记录,代码如下:
SELECT TOP 10 WITH TIES * FROM 订单明细 ORDER BY数量DESC
其查询结果如图7.11所示。
图7.11 使用with ties的结果
在图7.11中可以看出,虽然使用了“select top 10”的语句,但是显示的实际记录数为12条。因为最后5条记录的订购数量是一样的。
7.10.7 查看不重复记录
例二十一:查看不带重复记录的结果。
查看货物在哪些城市里有过销售记录。在“订单”表里有一个字段是“货主城市”,通过查询这个字段,就可以了解货物曾经销过哪些城市。其代码如下:
SELECT 货主城市 FROM 订单
或者:
SELECT ALL 货主城市 FROM 订单
查询结果如图7.12所示。
图7.12 查看货物销往城市
从图7.12中可以看出,有很多记录都是重复的,如果要显示不重复的记录,可以使用以下代码:
SELECT DISTINCT货主城市 FROM 订单
查询结果如图7.13所示。
图7.13 查看不重复的记录
Distinct关键字可以同时指定多个字段,例如同时指定两个字段,那么查询的结果是两个字段同时不重复的记录,例如以下代码:
SELECT DISTINCT货主名称,货主城市 FROM 订单
其查询结果如图7.14所示。
图7.14 查询多个字段同时不重复的记录
从图7.14中可以看出,有“货主名称”相同的记录,但是没有“货主名称”与“货主城市”同时相同的记录。
说明 ALL表示显示所有记录,不管是否重复。系统默认值为ALL,所以此关键字可以省略。DISTINCT表示重复的记录只显示其中一条,如果有多条记录的要显示列的内容为Null,那么也只显示其中一条记录。
7.10.8 查询表中的标识列或GUlD列
例二十二:查询表的标识列。
查看“类别”表中的标识列,其代码如下:
SELECT $IDENTITY FROM 类别
查询结果如图7.15所示。
图7.15 查询表中的标识列
从图7.15中可以看出,虽然没有在Select语句里指定要查询的字段名,但还是可以返回“类别ID”列,因为一个表中只能有一个标识列。同样,一个表中也只能有一个GUID列,所以用以下代码也可以返回GUID列的内容。
SELECT $ROWGUID FROM 类别
在本例中,“类别”表并没有GUID列,所以运行上述代码会报错,希望读者可以举一反三地运用。
说明 查询表中的标识列或GUID列,常用于T-SQL程序中。
7.11 用From子句指定查询哪些表
From子句的作用是指定要查询数据的来源,下面是对From子句的介绍。
7.11.1 基本语法
From子句的语法代码如下:
[ FROM { <table_source> } [ , ...n ] ] <table_source> ::= { table_or_view_name --表或视图名 [ [ AS ] table_alias ] --表或视图别名 [ WITH ( < table_hint > [ [ , ]...n ] ) ] --指定查询优化器 | rowset_function [ [ AS ] table_alias ] --指定行集函数 [ ( bulk_column_alias [ , ...n ] ) ] --替代结果集内的列名 | user_defined_function [ [ AS ] table_alias ] --指定表值函数 | OPENXML <openxml_clause> --通过XML查询 | derived_table [ AS ] table_alias [ ( column_alias [ , ...n ] ) ] --子查询 | <joined_table> } <joined_table> ::= --多表联合查询 { <table_source> <join_type> --联合类型 <table_source> ON <search_condition> --联合条件 | <table_source> CROSS JOIN <table_source> | left_table_source { CROSS | OUTER } APPLY right_table_source | [ ( ] <joined_table> [ ) ] } <join_type> ::= [ { INNER | { { LEFT | RIGHT | FULL } [ OUTER ] } } [ <join_hint> ] ] JOIN
7.11.2 参数说明
From子句包含以下参数:
◆ table_or_view_name:指定表或视图名。
◆ [AS]table_alias:指定表的别名。
◆ WITH(<table_hint>[[, ]...n]):指定查询优化器对此表和此语句使用优化或锁定策略。
◆ rowset_function:指定其中一个行集函数(例如OPENROWSET),该函数返回可用于替代表引用的对象。
◆ bulk_column_alias:替代结果集内列名的可选别名。只允许在OPENROWSET函数和BULK选项的Select语句中使用列别名。使用该参数时,必须为每个表列指定别名,顺序与这些列在文件中的顺序相同。
◆ user_defined_function:用于指定表值函数。
◆ OPENXML <openxml_clause>:通过XML文档提供行集视图。
◆ derived_table:从数据库中检索行的子查询。该参数用于外部查询的输入。
◆ column_alias:替代派生表的结果集内列名的可选别名。在选择列表中的每个列包括一个列别名,并将整个列别名列表用圆括号括起来。
● <joined_table>语法块:指定由两个或更多表中的查询所构成的结果集。
● <join_type>语法块:指定联接操作的类型。设置为From子句。
7.11.3 简单的From子句
例二十三:查询所有记录。
查看“雇员”表中的所有记录,其代码如下:
SELECT * FROM 雇员
7.11.4 从两个表中联合查询记录
例二十四:两个表联合查询。
查看所有产品名称和类别。
如果单独查询“类别”表,只能查询到“类别ID”和“类别名称”,例如:
SELECT类别ID,类别名称 FROM类别
其查询结果如图7.16所示。
图7.16 【类别】表中的记录
如果单独查询“产品”表,只能查询到“产品ID”、“产品名称”和“类别ID”,例如:
SELECT产品ID,产品名称,类别ID FROM产品
其查询结果如图7.17所示。
图7.17 【产品】表中的记录
在“类别”表和“产品”表里,同时存在着“类别ID”字段。在“产品”表中获得“类别ID”的内容,然后从“类别”表中查询相同的“类别ID”的记录,就可以得知“产品”表中该产品的类别名称。在Select语句中,可以通过Join将两个表中的记录联接起来进行查询,其代码如下:
SELECT产品ID,产品名称,类别名称 FROM产品JOIN类别 ON产品.类别ID=类别.类别ID
查询结果如图7.18所示。
图7.18 联合查询结果
Join的作用是将两个或两个以上表的记录横向联接起来,On的作用是设置查询条件,将无用的记录过滤掉。下面列出两个表,以方便读者进行理解。其中,表7.1是“产品”表里的内容,表7.2是“类别”表里内容。
表7.1 “产品”表中的记录
表7.2 “类别”表中的记录
使用Join联接后的结果如表7.3所示。
表7.3 Join后的记录
此时,查询的结果为两个表中所有记录的联接。在本例中,要查询的是“产品”表中的“类别编号”字段内容与“类别”表中的“类别编号”字段内容相同的记录,这就要用到ON关键字来指定过滤条件。例如“ON产品.类别ID=类别.类别ID”,其查询结果如表7.4所示。
表7.4 Join…On…后的记录
在本例中,如果还要多查看一个“类别编号”,则不能使用以下代码:
SELECT产品ID,产品名称,类别名称,类别ID FROM产品JOIN类别 ON产品.类别ID=类别.类别ID
如果运行上面的语句,SQL Server 2008将会出现“列名 '类别ID' 不明确”的错误提示。由于在“产品”表和“类别”表里同时有的字段,SQL Server不知道应该显示哪个表中的字段。此时,要用“表名.字段名”来明确要显示的是哪个表中的字段,例如以下代码:
SELECT产品ID,产品名称,类别名称,类别.类别ID FROM产品JOIN类别 ON产品.类别ID=类别.类别ID
Select的Where子句也可以设置过滤条件,其代码如下:
SELECT产品ID,产品名称,类别名称,类别.类别ID FROM产品,类别 WHERE产品.类别ID=类别.类别ID
虽然以上代码也可以实现相同的功能,但是SQL Server提倡使用Join…On…语句来进行联合查询。Join…On…语句的可读性比较强,并且还有Left Join,Full Join和Right Join等不同的显示方式可以运用,这是Where子句所不能比拟的。
7.11.5 从多个表中联合查询记录
例二十五:多个表联合查询。
查看订单的产品名称、单价、数量、折扣和订购日期。由于以上字段分别在“产品”表、“订单明细”表和“订单”表中,所以必须将三个表联合起来进行查询,其代码如下:
SELECT 产品.产品名称,订单明细.单价,订单明细.数量, 订单明细.折扣,订单.订购日期 FROM 订单明细JOIN 订单ON订单明细.订单ID=订单.订单ID JOIN 产品ON订单明细.产品ID=产品.产品ID
在多个表中进行Join查询时,也可以用小括号来指定Join的顺序。在本例中,代码还可以写为:
SELECT 产品.产品名称,订单明细.单价,订单明细.数量, 订单明细.折扣,订单.订购日期 FROM (订单明细JOIN订单ON订单明细.订单ID=订单.订单ID ) JOIN产品ON订单明细.产品ID=产品.产品ID
本代码中,可以看成先将“订单明细”表与“订单”表Join后,形成一个新表,然后和“产品”表Join。
7.11.6 Join的其他类型
Join分为[Inner] Join,Left [Outer] Join,Right [Outer] Join,Full [Outer] Join和Cross Join几种类型,使用不同的类型可以得到不同的查询结果。
◆ [Inner] Join:只显示符合条件的记录,默认为Join方式,Inner可以省略。
◆ Left [Outer] Join:显示左边表中所有的记录,以及右边表中符合条件的记录。
◆ Right [Outer] Join:显示右边表中所有的记录,以及左边表中符合条件的记录。
◆ Full [Outer] Join:显示所有表中的记录,包括符合条件的记录和不符合条件的记录。
◆ Cross Join:将一个表的每一条记录和另一个表中的每一条记录搭配成新的记录,不需要用On来设置条件。
下面举例介绍Join各类型的用法。先建两个表,一个是“库存信息”表,如表7.5所示,该表显示产品的库存情况;另一个是“订单信息”表,如表7.6所示,该表显示产品的订单信息。
表7.5 “库存信息”表中的记录
表7.6 “订单信息”表中的记录
例二十六:使用Inner Join查询。
查看可以出货的产品。当某个产品既有订单,又有库存时,就可以发货给买家。其代码如下:
SELECT * FROM库存信息INNER JOIN 订单信息ON库存信息.产品名称=订单信息.产品名称
以上代码中的Inner可以省略,查询结果如图7.19所示。
图7.19 Join的查询结果
例二十七:使用Left Outer Join查询。
查看所有的库存产品,以及哪些库存产品有了订单。其代码如下:
SELECT * FROM库存信息LEFT OUTER JOIN 订单信息ON库存信息.产品名称=订单信息.产品名称
以上代码中的Outer可以省略,查询结果如图7.20所示。从该图中可以看出,“库存信息”表里的“酸奶酪”后面的三个字段的内容都为Null,表示在“订单信息”表里该产品没有订单记录。
图7.20 Left Join的查询结果
例二十八:使用Right Outer Join查询。
查看所有订单信息,并显示没有库存的产品。其代码如下:
SELECT * FROM库存信息RIGHT OUTER JOIN 订单信息ON库存信息.产品名称=订单信息.产品名称
以上代码中的Outer可以省略,查询结果如图7.21所示。从该图可以看出,“订单信息”表中的“柳橙汁”前三个字段的内容都为Null,这表示在“库存信息”表中没有该产品的存库。
图7.21 Right Join的查询结果
例二十九:使用Full Outer Join查询。
查看所有库存和订单,并显示哪些库存产品没订单、哪些订单产品没存库。其代码如下:
SELECT * FROM库存信息FULL OUTER JOIN 订单信息ON库存信息.产品名称=订单信息.产品名称
以上代码中的Outer可以省略,查询结果如图7.22所示。在该图中可以看出,酸奶酪没有订单,柳橙汁没有库存。
图7.22 Full Join的查询结果
例三十:使用Cross Join查询。
将库存产品和订单产品进行比较,其代码如下:
SELECT * FROM库存信息CROSS JOIN订单信息
查询结果如图7.23所示。在本例中比较的意义不大,但在某些情况下该比较方法是必不可少的。
图7.23 Cross Join的查询结果
7.11.7 使用表别名
例三十一:使用表别名。
查看订单的订单价格和订购时间,其代码如下:
SELECT细.单价,细.数量,细.折扣, 细.单价* (1 . 细.折扣) * 细.数量AS总价, 订.订购日期 FROM订单AS订INNER JOIN 订单明细AS细ON订.订单ID=细.订单ID
查询结果如图7.24所示,从该图中可以看出查询结果和没有用表别名的查询结果没有不同。
图7.24 使用表别名的查询结果
技巧 为比较长的表名取个别名,可以方便记忆和使用。
7.11.8 表Join自身
例三十二:使用表Join自身的查询。
查看雇员及其主管领导。在“雇员”表里,有一个“上级”字段,该字段里存放的是其上级主管的雇员ID。其代码如下:
SELECT 雇员.雇员ID, 雇员.姓氏,雇员.名字,雇员.职务, 主管.姓氏AS主管姓氏,主管.名字AS主管名字, 主管.职务AS主管职务 FROM 雇员LEFT OUTER JOIN 雇员AS主管ON雇员.上级=主管.雇员ID
查询结果如图7.25所示,“主管姓氏”等字段为Null的记录表示该雇员没有上级主管。
图7.25 Join自己的结果
7.12 用Where子句设定查询条件
Where子句的作用是设置查询条件,下面是Where子句的介绍。
7.12.1 基本语法
Wherer子句的语法代码如下:
[ WHERE <search_condition> ] < search_condition > ::= { [ NOT ] <predicate> | ( <search_condition> ) } [ { AND | OR } [ NOT ] { <predicate> | ( <search_condition> ) } ] [ , ...n ] <predicate> ::= { expression {=| < > | !=| > | >=| ! > | < | <=| ! < } expression | string_expression [ NOT ] LIKE string_expression [ ESCAPE ' escape_character' ] | expression [ NOT ] BETWEEN expression AND expression | expression IS [ NOT ] NULL | CONTAINS ( { column | * } , ' < contains_search_condition >' ) | FREETEXT ( { column | * } , ' freetext_string' ) | expression [ NOT ] IN ( subquery | expression [ , ...n ] ) | expression {=| < > | !=| > | >=| ! > | < | <=| ! < } { ALL | SOME | ANY} ( subquery ) | EXISTS ( subquery ) }
7.12.2 参数说明
Where子句包含以下参数:
◆ <search_condition>语法块:用于指定要在Select语句、查询表达式或子查询的结果集中返回的行的条件。
◆ NOT:对指定的布尔表达式求反。
◆ AND:组合两个条件,并在两个条件都为TRUE时取值为TRUE。
◆ OR:组合两个条件,并在任何一个条件为TRUE时取值为TRUE。
◆ <predicate>语法块:返回TRUE, FALSE或UNKNOWN的表达式,其参数如下所示。
● expression:用于指定列名、常量、函数、变量、标量子查询,或者是通过运算符或子查询连接的列名、常量和函数的任意组合。
● =| < > | !=| > | >=| ! > | < | <=| ! <:分别为等于、不等于、不等于、大于、大于或等于、不大于、小于、小于或等于、不小于运算符。
● string_expression:用于指明字符串和通配符。
● [NOT] LIKE:用于指示后续的字符串使用模糊查询。
● ESCAPE ' escape_character' :使用该参数则允许在字符串中搜索通配符,而不是将其作为通配符使用。
● [NOT] BETWEEN:用于指定值的包含范围,并使用AND分隔开始值和结束值。
● IS [NOT] NULL:根据使用的关键字,指定是否搜索空值或非空值。如果有任何一个操作数为Null,则包含位运算符或算术运算符的表达式的计算结果为Null。
● CONTAINS:在包含字符数据的列中,搜索单个词和短语的精确或不精确(“模糊”)的匹配项、在一定范围内相同的近似词以及加权匹配项。
● FREETEXT:在包含字符数据的列中,搜索与搜索条件中的词的含义相符而非精确匹配的值,提供一种形式简单的自然语言查询。
● [NOT] IN:根据是在列表中包含还是排除某表达式,指定对该表达式的搜索。搜索表达式可以是常量或列名,而列表可以是一组常量,更常用的是子查询。将一组值用圆括号括起来。
● subquery:可以看成是受限的Select语句,与Select语句中的<query_expresssion>相似。不允许使用Order by子句、Compute子句和INTO关键字。
● ALL:与比较运算符和子查询一起使用。如果子查询检索的所有值都满足比较运算,则为<predicate>返回TRUE;如果并非所有值都满足比较运算或子查询未向外部语句返回行,则返回FALSE。
● SOME | ANY:与比较运算符和子查询一起使用。如果子查询检索的任何值都满足比较运算,则返回TRUE;如果子查询内没有值满足比较运算或子查询未向外部语句返回行,则返回FALSE;其他情况下,表达式为UNKNOWN。
● EXISTS:与子查询一起使用,用于测试是否存在子查询返回的行。
7.12.3 单个查询条件
例三十三:一个查询条件。
查看“产品”表中库存量为零的产品,其代码如下:
SELECT * FROM产品 WHERE库存量=0
也可以查询库存量不为零的产品,其代码如下:
SELECT * FROM产品 WHERE库存量 <> 0
或者:
SELECT * FROM产品 WHERE NOT库存量=0
利用=(等于)、<>(不等于)、!=(不等于)、>(大于)、>=(大于或等于)、!>(不大于)、<(小于)、<=(小于或等于)、!<(不小于),可以灵活地设置查询条件。NOT是将查询条件取反值。
7.12.4 设置多个查询条件
例三十四:多个查询条件。
查看“产品”表中类别为“调味品”、库存量为零的产品,其代码如下:
SELECT * FROM产品 WHERE库存量=0 AND类别ID=2
以上代码中,已知调味品的类别ID为2,如果不知道调味品的类别ID,也可以通过联合查询来获得记录,其代码如下:
SELECT产品.* FROM产品JOIN类别 ON产品.类别ID=类别.类别ID WHERE产品.库存量=0 AND类别.类别名称=N’调味品’
两段代码的运行结果是一样的。
如果查询的条件比较多,可以用AND(和)、OR(或)来连接不同的查询条件。如果查询条件比较复杂,还可以用小括号来指明AND或OR的顺序。
例三十五:更复杂的查询。
查看“产品”表中类别为“调味品”和“日用品”、库存量为0的产品,其代码如下:
SELECT产品.* FROM产品JOIN类别 ON产品.类别ID=类别.类别ID WHERE产品.库存量=0 AND (类别.类别名称=N’调味品’ OR类别.类别名称=N’日用品’)
如果将小括号省略,则查询的结果为类别是“调味品”且库存量为零的产品和所有类别为“日用品”的记录。
7.12.5 在查询条件里使用函数
例三十六:带函数的查询条件。
查看在公司工作时间超过13年的雇员。假设今年是2008年,要查看在公司工作时间超过13年的雇员,只需查询雇用日期在1993年以前的员工记录。其代码如下:
SELECT * FROM雇员 WHERE雇用日期< CONVERT(DATETIME, '1993.1.1' , 102)
在本例中,CONVERT函数的作用是将字符串“1993.1.1”转换成日期型数据,只有数据类型相同或兼容才能比较。以上代码还可以写为:
SELECT * FROM雇员 WHERE雇用日期< '1993.1.1'
虽然SQL Server会隐式将字符串“1993.1.1”转换成日期型数据,但为了养成良好的编程习惯,最好还是用CONVERT函数来转换类型。
以上代码虽然可以查到距离2008年来说在公司工作超过13年的雇员,但是如果到了2009年,再次查询在公司工作超过13年的雇员记录,则需要修改SQL代码了。如果使用以下代码,则无论何时查询都不用修改代码:
SELECT * FROM雇员 WHERE Year(Getdate()).Year(雇用日期) >13
Getdate( )函数获取系统的当前时间,Year( )函数获取时间类型数据的年份。
7.12.6 查询两个条件之间的记录
例三十七:查询条件为区间。
查看在1993年和1994年之间聘用的雇员,其代码如下:
SELECT * FROM雇员 WHERE雇用日期BETWEEN CONVERT(DATETIME, '1993.01.01' , 102) AND CONVERT(DATETIME, '1994.12.31' , 102)
7.12.7 查询字段内容为Null的记录
例三十八:查询Null值。
查看公司中最高负责人信息。在“雇员”表里有一个“上级”字段,该字段内容为Null的雇员则为公司中最高负责人。其代码如下:
SELECT * FROM雇员 WHERE 上级IS NULL
如果查询条件是判断字段内容是否为空,必须要用“Is Null”或“Is Not Null”来设置查询条件,如果用“上级=Null”,查询到的结果为0条记录。因为Null是不能比较的。
7.12.8 将结果集作为查询条件
例三十九:查询结果集。
查看雇员ID为1,3,4,7和9的雇员记录,其代码如下:
SELECT * FROM雇员 WHERE 雇员ID=1 OR雇员ID=3 OR雇员ID=4 OR雇员ID=7 OR雇员ID=9
以上代码虽然可以查询到雇员ID为1,3,4,7和9的雇员记录,但是如果查询条件特别多,用OR就会显得混乱。如果用IN关键字,代码的可读性会明显增强。例如以下代码:
SELECT * FROM雇员 WHERE雇员ID IN (1,3,4,7,9)
如果要查询雇员ID不为1,3,4,7和9的雇员记录,其代码如下:
SELECT * FROM雇员 WHERE雇员ID NOT IN (1,3,4,7,9)
例四十:查询复杂的结果集。
查看所有日用品的订单。要查看所有日用品的订单,首先要知道类别为“日用品”的产品有哪些,它们的产品ID是多少,然后再从“订单明细”中找到所有与这些“产品ID”集有关的记录。其代码如下:
SELECT * FROM订单明细 WHERE产品ID IN (SELECT产品ID FROM产品JOIN类别 ON产品.类别ID =类别.类别ID WHERE类别.类别名称=N’日用品’)
7.12.9 模糊查询
例四十一:在“产品”表中查询所有与奶相关的产品。其代码如下:
SELECT * FROM产品 WHERE产品名称LIKE ' %奶%'
其查询结果如图7.26所示,在该图中可以看出,“产品名称”中含有“奶”字的所有记录都被查询出来了。
图7.26 模糊查询结果
LIKE关键字可以使用的通配符有%、_、[]和[^]4种,其代表意义如表7.7所示。
表7.7 LIKE的通配符
在本例中,使用了“LIKE ‘%奶%’”,也就是指明只要“产品名称”字段中有一个字是“奶”,就是符合查询条件的记录。如果要查询所有奶酪产品,则代码如下:
SELECT * FROM产品 WHERE产品名称LIKE ' %奶酪’
该代码中只指定了一个“%”,在“产品名称”字段内容里,最后两个字为“奶酪”的记录都是符合条件的记录。如果将代码改为:
SELECT * FROM产品 WHERE产品名称LIKE ' _奶酪’
那么查询结果就只能是“产品名称”字段内容里最后两个字为“奶酪”,并且该字段内容一共只有三个字的记录。因为“_”代表一个字符。如果用“LIKE ‘_ _奶酪’”作为查询条件,则查询结果就只能是“产品名称”字段内容里最后两个字为“奶酪”,并且该字段一共只有4个字的记录。
再看看以下代码:
SELECT * FROM产品 WHERE产品名称LIKE ' %油’ GO SELECT * FROM产品 WHERE产品名称LIKE ' [麻酱]油’ GO SELECT * FROM产品 WHERE产品名称LIKE ' [^麻酱]油’ GO
其查询结果如图7.27所示。在第一个查询里,查出的是“产品名称”字段里最后一个字为“油”的记录。在第二个查询里,查出的是“产品名称”字段里最后一个字为“油”且“油”字前为“麻”或“酱”的记录。在第三个查询里,查出的是“产品名称”字段里最后一个字为“油”且“油”字前不为“麻”或“酱”的记录。
图7.27 使用不同通配符的查询结果
7.12.10 在模糊查询中查询含有通配符的文本
例四十二:模糊查询。
查看“类别”表中“说明”字段里含有“_”字符的记录,其代码如下:
SELECT * FROM类别 WHERE说明LIKE ' %[_]%'
以上代码可以查询出字符串中含有下画线的记录。在LIKE语句中,可以将通配符作为文字字符使用。若要将通配符作为文字字符使用,必须要将通配符放在方括号中。在表7.8中举了几个例子以方便读者理解。
表7.8 将通配符作为文字字符使用
7.12.11 使用EXlSTS关键字
EXISTS关键字的作用是用来检查在子查询中是否有结果返回,如果有结果返回则为真,如果无结果返回则为假。
例四十三:使用EXISTS的查询。
查看在公司工作时间少于13年的雇员的所有订单,其代码如下:
SELECT * FROM订单 WHERE EXISTS ( SELECT * FROM雇员 WHERE Year(Getdate()).Year(雇用日期) <13 AND订单.雇员ID=雇员.雇员ID )
在本例中是演示EXISTS的使用方法,如果不用EXISTS关键字也可以得到查询结果,其代码如下:
SELECT * FROM订单JOIN雇员 ON订单.雇员ID =雇员.雇员ID WHERE Year(Getdate()).Year(雇员.雇用日期) <13
7.12.12 使用ALL, ANY和SOME关键字
ANY和SOME的意思相同,都是指在进行比较运算符时只要子查询中有一行能使结果为真,则结果为真;而ALL则要求子查询的所有行都使结果为真,结果才为真。
例四十四:使用ANY关键字的查询。
查看类别为日用品和点心的产品,其代码如下:
SELECT * FROM产品 WHERE类别ID=ANY ( SELECT类别ID FROM类别 WHERE 类别名称=N’日用品’ OR 类别名称=N’点心’ )
在本例中,只要“类别ID”与子查询中的“类别ID”中的其中一个相等就可以返回真,相当于以下代码:
SELECT * FROM产品 WHERE类别ID in ( SELECT类别ID FROM类别 WHERE 类别名称=N’日用品’ OR 类别名称=N’点心’ )
或者:
SELECT * FROM产品JOIN类别 ON产品.类别ID=类别.类别ID WHERE类别.类别名称=N’日用品’ OR 类别.类别名称=N’点心’
如果将本例中的ANY换成ALL,返回的记录数为零,因为类别ID不能既为“日用品”的类别ID又为“点心”的类别ID。
例四十五:使用ALL关键字的查询。
查询“产品”表中单价大于所有类别为“日用品”的产品单价的产品,其代码如下:
SELECT * FROM产品 WHERE单价> ALL ( SELECT单价 FROM产品JOIN类别 ON产品.类别ID=类别.类别ID WHERE 类别名称=N’日用品’ )
在本例中主要是演示关键字ALL的用法,该代码相当于以下代码:
SELECT * FROM产品 WHERE单价> ( SELECT max(单价) FROM产品JOIN类别 ON产品.类别ID=类别.类别ID WHERE 类别名称=N’日用品’ )
7.13 使用Order by子句排序
Order by子句的作用是设置排序顺序,下面是对Order by子句的介绍。
7.13.1 基本语法
Order by子句的语法代码如下:
[ ORDER BY { order_by_expression --要排序的列 [ COLLATE collation_name ] --排序规则 [ ASC | DESC ] --升序或降序 } [ , ...n ] ]
7.13.2 参数说明
Order by子句包含参数比较少,主要有以下几个:
◆ order_by_expression:指定要排序的列。
◆ COLLATE collation_name:指定根据collation_name中指定的排序规则,而不是表或视图中所定义的列的排序规则,应运行的ORDER BY操作。collation_name可以是Windows排序规则名称或SQL排序规则名称。
◆ ASC:指定排序方式为升序,对指定列中的值进行从最低值到最高值排序。
◆ DESC:指定排序方式为降序,对指定列中的值进行从最高值到最低值排序。
7.13.3 按一个字段排序
例四十六:按字段排序。
查看所有产品记录,并按照产品名排序,其代码如下:
SELECT * FROM产品 ORDER BY产品名称
本例中省略了ASC关键字,默认为ASC。如果要按产品名称倒序排序,则用以下代码:
SELECT * FROM产品 ORDER BY产品名称DESC
7.13.4 按多个字段排序
例四十七:按多个字段排序。
查看所有产品记录,并按照供应商编号和产品名排序,其代码如下:
SELECT * FROM产品 ORDER BY供应商ID,产品名称
在本例中,先是按照供应商编号以升序方式排序,在供应商编号相同的记录中,按照产品名升序方式排序。在多条件排序方式下,也可以设置升序或倒序,其代码如下:
SELECT * FROM产品 ORDER BY供应商ID ASC,产品名称DESC
7.14 使用Group by子句分组
Group by子句的作用是将数据依据设置的条件分成各个群组,同时在Select子句中使用汇总函数进行数据汇总。下面是对Group by子句的介绍。
7.14.1 基本语法
Group by子句的语法代码如下:
[ GROUP BY [ ALL ] group_by_expression [ , ...n ] [ WITH { CUBE | ROLLUP } ] ]
7.14.2 参数说明
Group by子句包含参数比较少,主要有以下几个:
◆ ALL:用于指定包含所有组和结果集,甚至包含那些其中任何行都不满足Where子句指定的搜索条件的组和结果集。
◆ group_by_expression:用于指定进行分组所依据的表达式,也称为组合列。group_by expression既可以是列,也可以是引用由From子句返回的列的非聚合表达式。
◆ CUBE:指定在结果集内不仅包含由Group by提供的行,还包含汇总行。Group by汇总行针对每个可能的组和子组组合在结果集内返回。Group by汇总行在结果中显示为Null,但用来表示所有值。使用GROUPING函数可确定结果集内的空值是否为Group by汇总值。
◆ ROLLUP:指定在结果集内不仅包含由Group by提供的行,还包含汇总行。按层次结构顺序,从组内的最低级别到最高级别汇总组。组的层次结构取决于列分组时指定使用的顺序。更改列分组的顺序会影响在结果集内生成的行数。
7.14.3 Group by的基本用法
例四十八:分类统计。
统计每个城市的订单总数,其代码如下:
SELECT货主城市,count(订单ID) AS订单总数 FROM订单 GROUP BY货主城市
其运行结果如图7.28所示。在本例中按货主城市将“订单”表里的所有记录分成了若干个组,然后用count( )函数统计每个组里的记录数。
图7.28 分组查询结果
7.14.4 在Group by中使用表达式
例四十九:对分类统计结果进行排序。
按年份统计每个城市的订单总数,其代码如下:
SELECT货主城市,count(订单ID) AS订单总数,YEAR(订购日期) as订购年份 FROM订单 GROUP BY货主城市,YEAR(订购日期) ORDER BY货主城市,YEAR(订购日期)
其运行结果如图7.29所示。在本例中,YEAR( )函数用于取出“订购日期”字段里的年份数。
图7.29 在分组中使用表达式的查询结果
注意 Group by子句里可以是字段名,也可以是包含字段值的表达式,但不能是汇总函数。例如,本例中Group by子句若改为“GROUP BY货主城市,count(订单ID)”,则会出错。在Select子句里,除了汇总函数之外,其他所有出现的字段一定要在Group by子句里曾经出现过才行。在Select子句里不一定要出现汇总函数,但至少要用到Group by分组依据里的一项。text, ntext, image和xml数据类型的字段不能作为Group by的分组依据。
7.14.5 使用with cube对所有字段进行汇总
使用with cube会对Group by所列出的所有分组字段进行汇总运算。
例五十:统计并汇总。
统计每个城市的订单总数,并进行汇总,其代码如下:
SELECT货主城市,count(订单ID) AS订单总数 FROM订单 GROUP BY货主城市 WITH CUBE
其运行结果如图7.30所示。在记录集的最后一条记录里,“货主城市”字段内容为Null,而“订单总数”字段内容为713,这是对上面所有“货主城市”字段的汇总,其内容为“订单总数”的和。
图7.30 使用with cube分组查询结果
例五十一:按年份统计并汇总。
按年份统计每个城市的订单总数,并进行汇总,其代码如下:
SELECT货主城市,YEAR(订购日期) as订购年份,count(订单ID) AS订单总数 FROM订单 GROUP BY货主城市,YEAR(订购日期) WITH CUBE
其运行结果如图7.31所示。在图7.31中可以看到:第70条记录统计的是厦门所有年份的订单数,第80条记录统计的是重庆所有年份的订单数,第81条记录统计的是所有地区所有年份的订单数,第82条记录统计的是1996年所有地区的订单数。
图7.31 使用with cube分组查询结果
7.14.6 使用with rollup对第一个字段进行汇总
使用with rollup会对group by所列出的第一个分组字段进行汇总运算。
例五十二:按城市统计并按城市汇总。
按年份统计每个城市的订单总数,并按城市进行汇总,其代码如下:
SELECT货主城市,YEAR(订购日期) as订购年份,count(订单ID) AS订单总数 FROM订单 GROUP BY货主城市,YEAR(订购日期) WITH ROLLUP
其运行结果如图7.32所示。在该图中可以看到:第80条记录统计的是重庆所有年份的订单数,第81条记录统计的是所有地区所有年份的订单数,并没有按年份进行汇总的记录。
图7.32 使用with rollup分组查询结果
7.14.7 使用Group by all对所有数据分组
Group by all可以对数据表中所有的数据进行分组,下面举两个例子比较一下。
例五十三:对所有数据分组。
统计1998月5月1日以来每个城市的订单总数及运费金额,其代码如下:
SELECT货主城市,count(订单ID) AS订单总数,sum(运货费) AS运货费总数 FROM订单 WHERE订购日期> '1998.5.1' GROUP BY货主城市
其运行结果如图7.33所示。
图7.33 Group by的查询结果
如果使用Group by all,得出的结果将会不一样,其代码如下:
SELECT货主城市,count(订单ID) AS订单总数,sum(运货费) AS运货费总数 FROM订单 WHERE订购日期> '1998.5.1' GROUP BY ALL货主城市
运行结果如图7.34所示,在1998年5月1日之后北京地区没有订单记录,所以在“订单总数”字段内容中显示为0,在“运货费总数”字段内容中显示为Null。与图7.33对比可以发现,在图7.33所示的结果中,1998年5月1日之后没有订单记录的城市不会在结果集里出现,而在图7.34所示的结果中,即使1998年5月1日之后没有订单记录的城市,也会在结果集里出现。
图7.34 Group by all的查询结果
注意 GROUP BY ALL参数不能与CUBE和ROLLUP同时使用。
7.15 使用Having子句在分组中设置查询条件
Having子句用于指定组或聚合的搜索条件,通常在Group by子句中使用。如果不使用Group by子句,则Having的行为与Where子句一样。Having子句与Where子句的区别是:汇总函数只能在Having子句中使用。
7.15.1 基本语法
Having子句的语法代码如下:
[ HAVING <search condition> ] < search_condition > ::= { [ NOT ] <predicate> | ( <search_condition> ) } [ { AND | OR } [ NOT ] { <predicate> | ( <search_condition> ) } ] [ , ...n ] <predicate> ::= { expression {=| < > | !=| > | >=| ! > | < | <=| ! < } expression | string_expression [ NOT ] LIKE string_expression [ ESCAPE ' escape_character' ] | expression [ NOT ] BETWEEN expression AND expression | expression IS [ NOT ] NULL | CONTAINS ( { column | * } , ' < contains_search_condition >' ) | FREETEXT ( { column | * } , ' freetext_string' ) | expression [ NOT ] IN ( subquery | expression [ , ...n ] ) | expression {=| < > | !=| > | >=| ! > | < | <=| ! < } { ALL | SOME | ANY} ( subquery ) | EXISTS ( subquery ) }
其参数与Where子句的参数几乎一样,在此不再赘述,有兴趣的读者可以参考本章7.12.2节的内容。
7.15.2 使用Having对分组设置查询条件
例五十四:分组查询。
查看订单数超过20的城市,以及这些城市的总订单数和总运费。如果使用以下代码将会报错:
SELECT货主城市,count(订单ID) AS订单总数,sum(运货费) AS运货费总数 FROM订单 WHERE count(订单ID) > 20 GROUP BY货主城市
因为在Where子句里不能使用汇总函数,如果要使用汇总函数作为查询条件,就必须将其放在Having子句里。正确的代码如下:
SELECT货主城市,count(订单ID) AS订单总数,sum(运货费) AS运货费总数 FROM订单 GROUP BY货主城市 HAVING count(订单ID) > 20
注意 如果Having子句与Group by all子句一起使用,all的功能将会被取消。
7.16 使用Compute子句归类
Compute用于分组统计,生成的统计作为附加的汇总列出现在结果集的最后。当与by一起使用时,Compute子句在结果集内生成控制中断和小计。可以在同一查询内指定Compute by和Compute子句。
7.16.1 基本语法
Compute子句的语法代码如下:
[ COMPUTE { { AVG | COUNT | MAX | MIN | STDEV | STDEVP | VAR | VARP | SUM } ( expression ) } [ , ...n ] [ BY expression [ , ...n ] ] ]
7.16.2 参数说明
Compute子句包含的参数比较少,主要有以下几个:
◆ AVG:求平均值。
◆ COUNT:统计行数。
◆ MAX:最高值。
◆ MIN:最低值。
◆ STDEV:标准偏差。
◆ STDEVP:总体标准偏差。
◆ VAR:方差。
◆ VARP:总体方差。
◆ SUM:求和。
◆ expression:指明要用来汇总函数处理的字段或表达式,在此不能用字段别名,并且必须是Select子句列表中的一项。
◆ BY expression:指明要进行分类的字段(别名)或表达式,此字段名称必须是出现在Order by子句中的一项。
7.16.3 使用Compute归类
例五十五:汇总归类。
查看还没有发货的订单,以及运费的总数。其代码如下:
SELECT订单ID,货主城市,运货费 FROM订单 WHERE 发货日期is null COMPUTE SUM(运货费)
运行结果如图7.35所示。在图中显示了两个结果集,一个是订单结果集,另一个是运费总数的结果集。
图7.35 使用compute的查询结果
7.16.4 使用Compute by归类
例五十六:使用Compute by归类。
按城市查看还没有发货的订单,以及运费的总数。其代码如下:
SELECT订单ID,货主城市,运货费 FROM订单 WHERE 发货日期is null ORDER BY货主城市desc COMPUTE SUM(运货费) by货主城市
运行结果如图7.36所示,系统为每个城市建立了两个结果集,一个显示该城市的所有订单情况,一个显示该城市的运费合计。
图7.36 使用compute by的查询结果
7.17 使用Union子句合并多个查询结果
Union的作用是将两个或更多查询的结果合并为一个结果集,该结果集包含联合查询中的所有查询的全部行。Union运算不同于Join运算,Join运算是将两个或多个数据表的字段进行左右水平合并。一般来说,合并后字段数会增加,而Union是将多个查询结果上下叠加,合并后字段数不会增多,但记录总数会增加。例如,表7.9是查询结果集一,表7.10是查询结果集二,表7.11是Union之后的结果。
表7.9 查询结果集一
表7.10 查询结果集二
表7.11 Union之后的结果
要使用Union合并两个查询结果集,必须满足以下几个条件:
◆ 所有查询中的列数和列的顺序必须相同。
◆ 要合并的数据类型必须兼容,即数据类型可以不同,但必须可以转换。
◆ 合并的查询结果集的字段名称以第一个查询结果的字段名称为名,其他查询结果集的字段名称将会被忽略。
7.17.1 基本语法
Union的语法代码如下:
{ <query specification> | ( <query expression> ) } UNION [ ALL ] <query specification | ( <query expression> ) [ UNION [ ALL ] <query specification> | ( <query expression> ) [ ...n ] ]
7.17.2 参数说明
Union子句包含的参数主要有以下几个:
◆ <query specification>:查询规范或查询表达式,用于返回与另一个查询规范或查询表达式所返回的数据合并的数据。
◆ Union:指定合并多个结果集并将其作为单个结果集返回。
◆ ALL:将全部行并入结果中,其中包括重复行。如果未指定该参数,则删除重复行。
7.17.3 使用Union All合并两个查询结果集
例五十七:使用Union All合并两个查询结果。
公司要组织供应商和客户开交流会,需要一份通知名单,分别从“供应商”表和“客户”表里查出所有的联系人,并将其合为一个表。其代码如下:
SELECT联系人姓名,地址,电话 FROM供应商 UNION ALL SELECT联系人姓名,地址,电话 FROM客户
7.17.4 使用Union合并两个查询结果集
例五十八:使用Union合并两个查询结果。
在例五十七中,如果有人既是供应商也是客户,那么就会在表中出现两条相同的记录。如果要去掉重复记录,只需使用Union合并结果集。其代码如下:
SELECT联系人姓名,地址,电话 FROM供应商 UNION SELECT联系人姓名,地址,电话 FROM客户
7.17.5 使用Union加入临时数据
例五十九:使用Union加入临时数据。
在例五十八的查询结果集中,再加上一个临时通知的人。其代码如下:
SELECT联系人姓名,地址,电话 FROM供应商 UNION SELECT联系人姓名,地址,电话 FROM客户 UNION SELECT ’张三’, ’北京中医药大学’, '010.12345678'
注意 在合并多个结果集时,合并的顺序一般是从上至下,先合并第一个与第二个查询结果集,再合并第三个查询结果集。如果要设置合并的顺序,可以用小括号来改变合并顺序。
7.17.6 在Union的结果集里排序
例六十:在Union的结果集里排序。
在例五十八的查询结果集中,按联系人的姓名排序。其代码如下:
SELECT联系人姓名,地址,电话 FROM供应商 UNION SELECT联系人姓名,地址,电话 FROM客户 ORDER BY联系人姓名
注意 Order by与Compute子句只能用在整个UNION语句的最后,是针对UNION之后的结果集进行排序,不能用在单个查询语句里。
7.17.7 在Union的结果集里分组
例六十一:在Union的结果集里分组。
在例五十八的查询结果集中,按地址分组,查看相同的地址对应多少个联系人。其代码如下:
SELECT地址,COUNT(地址) AS联系人数 FROM ( SELECT联系人姓名,地址,电话 FROM供应商 UNION SELECT联系人姓名,地址,电话 FROM客户 ) AS临时表 GROUP BY地址
注意 Group by和Having子句只能用在单个查询结果集里,不能用在UNION之后的查询结果集中。如果要在UNION之后的查询结果集中分组或过滤记录,则只能将UNION之后的查询结果集视为一个数据表,然后在这个数据表中进行分组或过滤记录操作。
7.18 使用Select into子句为查询结果建立新表
Select into可以将查询的结果集放置在一个新建表中。
7.18.1 基本语法
Select into其实就是Select子句里的一个into参数,其语法代码包含在Select语法代码中,可以查阅本章7.9节。为了方便学习,这里将Select into的语法代码单独列出来。
SELECT < select_list > [ INTO new_table ] [ FROM { <table_source> } [ , ...n ] ] [ WHERE <search_condition> ]
7.18.2 使用Select into创建一个新表
例六十二:使用Select into创建新表。
从“产品”表里查询库存量为零的记录,并生成一个新的“缺货记录”表。其代码如下:
SELECT产品ID,产品名称 INTO缺货记录 FROM dbo.产品 WHERE库存量=0
运行完毕之后,在数据库中会增加一个名为“缺货记录”的数据表。表里的记录为“产品”表中库存量为零的产品ID和产品名称。
7.18.3 在Select into中设置复杂的查询条件
例六十三:查询并生成新表。
查看雇员及其订单情况,并生成一个新表。其代码如下:
SELECT雇员.雇员ID, 雇员.姓氏,雇员.名字, 产品.产品名称as售出产品,订单明细.单价,订单明细.数量, 订单明细.折扣,订单明细.单价*订单明细.数量*(1.订单明细.折扣) as总价, 客户.公司名称,客户.联系人姓名,客户.地址,客户.邮政编码, 客户.电话 INTO雇员订单信息 FROM 订单INNER JOIN 订单明细ON订单.订单ID=订单明细.订单ID INNER JOIN 雇员ON订单.雇员ID=雇员.雇员ID INNER JOIN 产品ON订单明细.产品ID=产品.产品ID INNER JOIN 客户ON订单.客户ID=客户.客户ID
技巧 无论查询语句有多复杂,只要加上into关键字,就可以将查询结果放在一个新数据表中。
7.18.4 使用Select into复制表结构
例六十四:复制表结构。
在订单越来越多的时候,可能会出现查询时间越来越长的情况,这个时候可以建立一个与“订单”表结构完全相同的表,将很少查询的历史订单(例如十年前的订单),移至该数据表中,以加快对“订单”表的查询速度。
使用Select into可以创建一个新数据表,并且数据表的字段类型与查询的字段类型相同。例如例六十二中新建的数据表,其字段结构分别为int和nvarchar(40)。如果要建立一个新的数据表并使其结构与“订单”表结构完全相同,只要让Where子句返回FALSE,查询出来的结果集为空,就可以不在新建的表里插入数据,只建立一个新表。其代码如下:
SELECT * INTO订单历史记录 FROM 订单 WHERE 0=1
注意 用以上办法创建的新表结构与旧表结构完全一样,但不包括约束和标识等,仅字段类型与长度和旧表一致。
7.19 在不同的数据库中查询记录
SQL Server 2008支持在不同的数据库中进行查询,其查询语句与上面所介绍的查询语句一样,只要精确地指定数据表名就行。一个完整的数据表名应该包括三个部分:数据库名、所属架构名和数据表名。
例六十五:在不同数据库中查询。
在Northwind数据库与test数据库中查询数据:
SELECT * FROM Northwind.dbo.雇员 JOIN test.dbo.订单 ON Northwind.dbo.雇员.雇员ID=test.dbo.订单.雇员ID
从以上代码可以看出,只要写明了完整的数据表名,就可以跨越数据库进行数据查询。由于在Select子句里已经标明了数据表所在位置,所以在ON子句里,可以省略数据库与所属架构名。
SELECT * FROM Northwind.dbo.雇员 JOIN test.dbo.订单 ON雇员.雇员ID=订单.雇员ID 可以使用别名来让代码进一步简化。 SELECT * FROM Northwind.dbo.雇员as N雇员 JOIN test.dbo.订单as例订单 ON N雇员.雇员ID=例订单.雇员ID
在用use语句指定数据库后,当前数据库的数据库名也可以省略,例如以下代码:
use Northwind SELECT * FROM雇员 JOIN test.dbo.订单 ON雇员.雇员ID=test.dbo.订单.雇员ID
7.20 Null字段的处理方法
在不知道具体有什么数据的时候,可以使用Null来表示。Null表示空,含有Null的字段长度为零。
7.20.1 使用Null时要注意的事项
由于Null比较特殊,所以在使用时必须注意几点。
◆ Null与空字符串、零、空格不同。Null代表的是一个“未知”的值,等价于没有任何值。空字符串是一个字符串,是一个长度为零、内容为空的字符串,而Null是不属于任何类型的。零是数字型的数据。空格也是字符串,其内容为空格,长度为1。
◆ 对Null进行加、减、乘、除等运算操作,结果仍为Null。
◆ 在默认情况下,对Null进行比较(无论是等于、小于还是大于),返回的结果都是FALSE。
◆ 如果将系统选项ANSI_NULLS设置为OFF,可以对Null进行相等比较,其他比较运算返回值都为FALSE。
◆ 在对含有Null的字段排序时,Null字段永远是最小的值。例如进行升序排序,Null排在最前面。
◆ 汇总函数(例如SUM和AVG等)会自动忽略Null值。
7.20.2 检查字段内容是否为Null
用Is Null与is Not Null可以判断字段内容是否为空。
例六十六:查询为Null的数据。
查看“类别”表里没有图片的类别,代码如下:
SELECT * FROM类别 WHERE图片IS NULL
例六十七:查询不为Null的数据。
查看“类别”表里有图片的类别,代码如下:
SELECT * FROM类别 WHERE图片IS NOT NULL
7.20.3 使用lSNULL函数替换Null值
ISNULL函数可以用来替换Null的值,其语法代码如下:
ISNULL ( check_expression, replacement_value )
其中,check_expression是要接受检查的字段(或表达式),如果该字段不为空,则返回它的值,如果为空,则返回replacement_value的值。
注意 replacement_value的类型必须与check_expression兼容。
例六十八:替换Null值。
查看“类别”表中的所有类别及其说明,代码如下:
SELECT类别ID,类别名称,isnull(说明,’暂无说明’) as说明 FROM类别
其运行结果如7.37所示,在第9条记录中,“说明”字段原本为Null,现在已经被替换为“暂无说明”字样。
图7.37 lSNULL的运行结果
7.21 SQL Server 2008语句新增功能
在SQL Server 2008中,对于Select,Insert,Update和Delete等语句都新增了一些值得关注的功能。
7.21.1 Select语句新增功能
SQL Server 2008中的Select语句提供了以下功能。
◆ WITH公用表表达式。
公用表表达式就是指定临时命名的结果集。公用表表达式来源于简单的查询,并在单个Select,Insert,Update或Delete语句的执行范围里定义。公用表表达式可以作为Select语句的一部分,也可以出现在创建视图的语句中。公用表表达式支持递归引用。
例六十九:创建临时命名的结果集。
查看所有雇员手下的员工的订单数,其代码如下:
WITH临时表(雇员ID,上级ID,订单数) AS ( SELECT雇员.雇员ID,雇员.上级,count(订单.订单ID) FROM订单 JOIN雇员ON订单.雇员ID=雇员.雇员ID GROUP BY雇员.雇员ID,雇员.上级 ) SELECT雇员.姓氏,雇员.名字,sum(订单数) as订单数FROM临时表 JOIN雇员ON临时表.上级ID=雇员.雇员ID GROUP BY雇员.姓氏,雇员.名字
◆ 用于选择用户定义的类型值的用户定义的类型列名。
7.21.2 lnsert语句新增功能
SQL Server 2008中的Insert语句提供了以下功能。
◆ WITH公用表表达式。
在Insert语句里,WITH公用表表达式通常是用在插入的内容中。
例七十:Insert中WITH表达式的应用。
在“雇员通讯录”表里插入订单数超过100的员工信息。由于在前面例子中已经向“雇员通讯录”表中插入了一些信息,因此在本例中先将这些记录删除。其代码如下:
DELETE雇员通讯录 GO WITH临时表(雇员ID,姓氏,名字,邮政编码,城市,地址,家庭电话) AS ( SELECT 雇员ID, 姓氏,名字,邮政编码,城市,地址,家庭电话 FROM 雇员 WHERE 雇员ID IN ( SELECT雇员ID FROM订单 GROUP BY雇员ID HAVING COUNT(订单ID) >100 ) ) INSERT INTO 雇员通讯录 SELECT雇员ID,姓氏,名字,邮政编码,城市,地址,家庭电话 FROM临时表
◆ TOP表达式。
在Insert子句里使用TOP表达式,通常用于插入选择的记录集里的前几条记录。
例七十一:插入前几条记录。
将“雇员”表中前五位雇员的信息插入到“雇员通讯录”表中,其代码如下所示:
DELETE雇员通讯录 INSERT top (5) INTO 雇员通讯录 SELECT 雇员ID, 姓氏,名字,邮政编码,城市,地址,家庭电话 FROM 雇员
◆ OUTPUT子句。
使用OUTPUT子句可以返回插入到数据表里的记录。
例七十二:OUTPUT子句示例。
将“雇员”表中前五位雇员的信息插入到“雇员通讯录”表中,其代码如下所示:
DELETE雇员通讯录 GO INSERT top (5) INTO雇员通讯录 OUTPUT INSERTED.雇员ID, INSERTED.姓氏,INSERTED.名字,INSERTED.邮政编码, INSERTED.城市,INSERTED.地址,INSERTED.家庭电话 SELECT 雇员ID, 姓氏,名字,邮政编码,城市,地址,家庭电话 FROM 雇员
◆ 允许插入用户定义的类型值。
7.21.3 Update语句新增功能
SQL Server 2008中的Update语句提供了以下功能:
◆ 在Update子句里,WITH公用表表达式通常是作为更新条件出现。
◆ TOP表达式。
◆ OUTPUT子句。
◆ 用于选择用户定义的类型值的用户定义的类型列名。
◆ WRITE子句。
WRITE子句的语法代码为:
.WRITE ( expression, @Offset , @Length )
使用WRITE子句可以修改指定列中的值的一部分,但必须是varchar(max),nvarchar(max)或varbinary(max)类型的列才能使用WRITE子句。替换方式是将指定例中的第@Offset字符开始到长度为@Length为止的字符以expression来替换。
例七十三:替换部分内容。
替换“雇员通讯录”表“地址”列中的部分内容。由于WRITE子句只能替换varchar(max),nvarchar(max)或varbinary(max)类型的列内容,因此必须先将“地址”字段类型转换成varchar(max)类型,再进行替换操作。其代码如下:
ALTER TABLE雇员通讯录 ALTER COLUMN地址nvarchar(max) GO SELECT * FROM雇员通讯录 WHERE雇员ID=4 UPDATE雇员通讯录 SET地址.WRITE (N’试一下’,1,1) WHERE雇员ID=4 SELECT * FROM雇员通讯录 WHERE雇员ID=4
其运行结果如图7.38所示。
图7.38 例七十三运行结果
7.21.4 Delete语句新增功能
SQL Server 2008中的Delete语句提供了以下功能:
◆ WITH公用表表达式。
◆ TOP表达式。
◆ OUTPUT子句。
7.22 小结
本章介绍了如何在SQL Server Management Studio中查看、修改、更新和删除记录,并且详细介绍了怎么使用T-SQL语句查看、修改、更新和删除记录。
使用Insert语句可以插入记录,应了解如何按表中不同字段顺序插入字段内容、如何从数据表中查询出记录并插入到另一个表中、如何插入字段的默认值和如何只插入字段的默认值、如何在标识列字段里插入字段内容。使用Update语句可以更新记录内容,应了解如何使用计算值来更新记录、如何引用其他表里的字段值更新记录、如何使用top子句来更新记录。使用Delete语句可以删除记录,应了解如何设置删除记录的条件、如何引用其他表里的字段值来删除记录。还应了解使用Delete删除记录和使用Truncate table删除记录的区别。
本章的重点是如何使用Select语句来查询记录。应了解如何为查询添加计算列,如何查看最新记录,如何用Distinct查询不重复记录,如何查询表中的标识列或GUID列,如何使用Join从多个表中联合查询记录,Join的几个类型;如何使用表别名,如何设置查询条件,如何在查询条件里使用函数,如何将结果集作为查询条件,如何进行模糊查询;如何用Order by来对查询结果排序,如何用Group by来对查询结果分组和汇总,如何用Having对分组设置查询条件,如何用Compute来归类,如何用Union来合并多个查询结果等。第8章将会重点介绍如何使用T-SQL程序对数据库里的数据进行复杂的操作。