检查 SELECT 语句
Transact-SQL(或 T-SQL)是 Microsoft SQL 产品和服务使用的 ANSI 标准 SQL 语言的方言。 它与标准 SQL 类似。 我们的重中之重是 SELECT 语句,该语句到目前具有的任意 DML 语句选项和变体最多。
首先,让我们整体来看一下 SELECT 语句的处理方式。 SELECT 语句的写入顺序并非 SQL Server 数据库引擎计算和处理该语句的顺序。
请考虑下列查询:
SELECT OrderDate, COUNT(OrderID) AS Orders
FROM Sales.SalesOrder
WHERE Status = 'Shipped'
GROUP BY OrderDate
HAVING COUNT(OrderID) > 1
ORDER BY OrderDate DESC;
该查询包含一个 SELECT 语句,其中包含多个子句,每个子句定义一个对检索的数据必须应用的特定操作。 在检查操作的运行时顺序之前,让我们先简单了解一下此查询的目的,但此模块中不会详细介绍各子句的详细信息。
SELECT 子句将返回 OrderDate 列以及被分配名称(或别名)Orders 的 OrderID 值计数:
SELECT OrderDate, COUNT(OrderID) AS Orders
FROM 子句标识哪个表是查询的行源;在本例中是指 Sales.SalesOrder 表:
FROM Sales.SalesOrder
WHERE 子句将对结果中的行进行筛选,只保留满足指定条件的行,在本例中则只保留“已发货”状态的订单:
WHERE Status = 'Shipped'
GROUP BY 子句将按 OrderDate 对满足筛选条件的行进行分组,从而将具有相同 OrderDate 的所有行视为单个组,并对每组返回一行:
GROUP BY OrderDate
形成组后,HAVING 子句将根据自己的谓词来筛选组。 结果中将仅包含有多个订单的日期:
HAVING COUNT(OrderID) > 1
要预览此查询,最后还要使用子句 ORDER BY,按 OrderDate 降序顺序对输出排序:
ORDER BY OrderDate DESC;
现在,你已了解每个子句的作用,接着我们来看 SQL Server 对它们的实际计算顺序:
- 首先计算 FROM 子句,为其余语句提供源行。 这里将创建一个虚拟表并将其传递到下一步。
- 接着,计算 WHERE 子句后,从源表中筛选出与谓词匹配的行。 将筛选后的虚拟表传递到下一步。
- 然后计算 GROUP BY,根据 GROUP BY 列表中的唯一值来组织虚拟表中的行。 这时将创建一个包含组列表的新虚拟表,并将其传递到下一步。 从操作流的此刻开始,其他元素只能引用 GROUP BY 列表中的列或聚合函数。
- 接下来,计算 HAVING 子句,根据谓词筛选出整个组。 筛选在步骤 3 中创建的虚拟表,并将其传递到下一步。
- 最后执行 SELECT 子句,确定查询结果中显示哪些列。 由于 SELECT 子句在其他步骤之后计算,因此这时创建的任何列别名(在本例中为 Orders)都无法用于 GROUP BY 或 HAVING 子句。
- 最后一步执行 ORDER BY,按列列表对确定的行排序。
要将这种理解应用于示例查询,下面是上述 SELECT 语句运行时的逻辑顺序:
FROM Sales.SalesOrder
WHERE Status = 'Shipped'
GROUP BY OrderDate
HAVING COUNT(OrderID) > 1
SELECT OrderDate, COUNT(OrderID) AS Orders
ORDER BY OrderDate DESC;
每个写入的 SELECT 语句并非都需要所有可能的子句。 唯一必需的子句是 SELECT 子句,它们在某些情况下可以单独使用。 通常还会包括 FROM 子句,用于标识要查询的表。 此外,Transact-SQL 还包含其他可添加的子句。
如你所见,写入 T-SQL 查询的顺序与其计算的逻辑顺序并不相同。 计算的运行时顺序决定哪些数据可用于哪些子句,因为子句仅有权访问已处理子句提供的信息。 因此,在写入查询时了解真正的逻辑处理顺序非常重要。
选择所有列
SELECT 子句通常称为 SELECT 列表,因为它会列出要在查询结果中返回的值。
最简单的 SELECT 子句形式是使用星号字符 (*) 返回所有列。 在 T-SQL 查询中使用时,它被称为“星型”。 尽管 SELECT * 适合用于快速测试,但应避免在生产工作中使用它们,原因如下:
- 对表进行的添加列或重新排列列等更改会反映在查询结果中,进而可能导致使用该查询的应用程序或报表出现输出意外。
- 如果源表包含大量行,则返回不需要的数据可能会减慢查询速度并导致性能问题。
例如,以下示例将从 Production.Product 表中检索(假设)所有列。
SELECT * FROM Production.Product;
此查询的结果将是一个行集,其中包含表中所有行的所有列,可能如下所示:
ProductID
名称
ProductNum
颜色
StandardCost
ListPrice
大小
重量
ProductCatID
680
HL Road Frame - 黑色,58
FR-R92B-58
黑色
1059.31
1431.5
58
1016.04
18
706
HL Road Frame - 红色,58
FR-R92R-58
Red
1059.31
1431.5
58
1016.04
18
707
Sport-100 Helmet,红色
HL-U509-R
Red
13.0863
34.99
35
708
Sport-100 Helmet,黑色
HL-U509
黑色
13.0863
34.99
35
...
...
...
...
...
...
...
...
...
选择特定列
使用显式列列表,可以控制究竟以哪种顺序返回哪些列。 结果中的每一列都将使用列名称作为标题。
例如,请考虑以下查询,我们仍然假设使用 Production.Product 表。
SELECT ProductID, Name, ListPrice, StandardCost
FROM Production.Product;
这次,结果仅包含指定的列:
ProductID
名称
ListPrice
StandardCost
680
HL Road Frame - 黑色,58
1431.5
1059.31
706
HL Road Frame - 红色,58
1431.5
1059.31
707
Sport-100 Helmet,红色
34.99
13.0863
708
Sport-100 Helmet,黑色
34.99
13.0863
...
...
...
...
选择表达式
除检索指定表中存储的列之外,SELECT 子句还可以执行计算和操作,即使用运算符合并列和值或合并多个列。 计算或操作的结果必须是单值(标量)结果,在结果中作为单独一列显示。
例如,以下查询包含两个表达式:
SELECT ProductID,
Name + '(' + ProductNumber + ')',
ListPrice - StandardCost
FROM Production.Product;
该查询的结果可能如下所示:
ProductID
680
HL 公路车架 - 黑色,58(FR-R92B-58)
372.19
706
HL 公路车架 - 红色,58(FR-R92R-58)
372.19
707
Sport-100 头盔,红色(HL-U509-R)
21.9037
708
Sport-100 头盔,黑色(HL-U509)
21.9037
...
...
...
关于这些结果,需要注意的几点如下:
- 两个表达式返回的列没有列名称。 根据提交查询所用的工具,缺失列名称可能以空白列标题、文本“无列名称”指示器或“column1”等默认名称指示。 在本部分后面,我们将介绍如何为查询中的列名称指定别名。
- 第一个表达式使用 + 运算符来连接字符串(基于字符)值,而第二个表达式使用 - 运算符从一个数值中减去另一个数值。 与数值一起使用时,+ 运算符执行加法。 显然,了解表达式中所包含列的数据类型非常重要。 我们将在下一部分讨论数据类型。
指定列别名
可以为 SELECT 查询返回的每列指定一个别名,用作源列名称的替代项,或者用于为表达式的输出指定名称。
例如,下面的查询与前面相同,但为每列指定了别名:
SELECT ProductID AS ID,
Name + '(' + ProductNumber + ')' AS ProductName,
ListPrice - StandardCost AS Markup
FROM Production.Product;
此查询的结果包括指定的列名称:
ID
ProductName
标记
680
HL 公路车架 - 黑色,58(FR-R92B-58)
372.19
706
HL 公路车架 - 红色,58(FR-R92R-58)
372.19
707
Sport-100 头盔,红色(HL-U509-R)
21.9037
708
Sport-100 头盔,黑色(HL-U509)
21.9037
...
...
...
注意
指定别名时,AS 关键字是可选项,但最好是包含该项以便澄清。
格式化查询
通过此部分的示例,你可能会注意到查询代码的格式可以灵活设置。 例如,可以每行写入一个子句(或整个查询),也可以将其拆分成多行写入。 在大多数数据库系统中,代码不区分大小写,并且有些 T-SQL 语言元素是可选项(包括前面提到的 AS 关键字,甚至是语句末尾的分号)。
请考虑以下准则,让 T-SQL 代码容易阅读(从而更便于理解和调试!):
- 将 T-SQL 关键字(如 SELECT、FROM、AS 等)大写。 大写关键字是一种常用约定,这样查找复杂语句的每个子句会变得更加轻松。
- 另起新的一行写入语句的每个主子句。
- 如果 SELECT 列表不止有几列、几个表达式或别名,请考虑在每行列出其各列。
- 缩进包含子子句或列的行,以明确每个代码属于哪个主子句。