第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程序对数据库里的数据进行复杂的操作。