SQL 必知必会
检索相关注意点
检索去重数据关键字——DISTINCT
DISTINCT 关键字指示数据库只返回不同值,若使用 DISTINCT 关键字,必须放在列名前面。DISTINCT 关键字作用于所有列。
SELECT DISTINCT * FROM TABLE;
检索从第 N 行开始检索前 M 行数据
SQL SERVER:
SELECT TOP 5 column FROM table;
ORACLE:
SELECT cloumn FROM table WHERE ROWNUM <= 5;
MySQL,MariaDB,PostgreSQL 或者 SQLite:
SELECT prod_name
FROM Products
LIMIT 5 OFFSET 5;
LIMIT 5 指示 MySQL 等 DBMS 返回不超过 5 行的数据,OFFSET 5 指示 MySQL 等 DBMS 返回从第 5 行起的 5 行数据。
数据库检索从第 0 行开始,因此 LIMIT 1 OFFSET 1 会检索第 2 行。
三种注释方式
-- 此后一行为注释
# 此后一行为注释
/* 此后
多行为注释,
直至 */
排序相关注意点
关系数据库设计理论认为,如果不明确规定排序顺序,则不应该假定检索出的数据的顺序有任何意义。
为了明确地为数据进行排序,可以使用 ORDER BY 子句。ORDER BY 子句取一个或多个列的名字。
SELECT cloumn_1 FROM table ORDER BY cloumn_1;
ORDER BY 需要保证为 SELECT 语句的最后一条子句,即放在最后。
通常,ORDER BY 子句使用的列可以为显示选择的列,也可以是非检索的列数据。
ORDER BY 后可以跟多个列,排序顺序为第一个列,当第一个列中有多行相同时,采用第二列进行排序,以此类推。
SELECT * FROM table ORDER BY column_1,column_2;
ORDER BY 可以按相对列的位置进行排序。
SELECT column_1,column_2,column_3 FROM table ORDER BY 2,3;
这样表示好处在于不用重新输入列名,但缺点也很明显:1.不明确给出列名有可能造成错用列名排序;2.若修改索引列的数量和顺序,则 ORDER BY 也需要更改,若遗漏将可能导致数据顺序出现异常;3.若进行排序的列不再索引列中,无法使用此种写法。
可以混合使用列名排序和位置排序。
排序方向
默认排序顺序为升序(从 A 到 Z),还可以使用降序排列,指定 DESC 关键字。
SELECT prod_id, prod_price, prod_name
FROM Products
ORDER BY prod_price DESC;
DESC 关键字只应用到直接位于其前面的列名,如果想在多个列上进行降序排序,必须对每一列指定 DESC 关键字。
默认按字典序排序,若需要改变,需要 DBMS Mananger 进行修改。
过滤数据
只检索满足特定条件的数据,需要指定搜索条件,搜索条件也称过滤条件,在 SELECT 语句中们加入 WHERE 关键字加搜索条件进行过滤。
SELECT prod_name, prod_price
FROM Products
WHERE prod_price = 3.49;
WHERE 子句操作符
操作符 | 说明 |
---|---|
= | 等于 |
<> | 不等于 |
!= | 不等于 |
< | 小于 |
<= | 小于等于 |
!< | 不小于 |
> | 大于 |
>= | 大于等于 |
!> | 不大于 |
BETWEEN | 在指定的两个值之间 |
IS NULL | 为 NULL 值 |
单引号用来限定字符串。如果将值与字符串类型
的列进行比较,就需要限定引号。用来与数值列进行比较的值不用引号。
在使用 BETWEEN 时,必须指定两个值——所需范围的低端值和高端值。这两个值必须用 AND 关键字分隔。BETWEEN 匹配范围中所有的值,包括指定的开始值和结束值。
SELECT 语句有一个特殊的 WHERE 子句,可用来检查具有 NULL 值的列。这个 WHERE 子句就是 IS
NULL 子句。
组合 WHERE 子句
AND、OR 逻辑操作符
SQL 允许给出多个 WHERE 子句。这些子句有两种使用方式,即以 AND 子句或 OR 子句等逻辑操作符的方式使用。
SELECT prod_id, prod_price, prod_name
FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;
AND 指示 DBMS 只返回满足所有给定条件的行。
可以增加多个过滤条件,每个条件间都要使用 AND 关键字。
SELECT prod_name, prod_price
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = ‘BRS01’;
OR 操作符指示 DBMS 检索匹配任一条件的行
SELECT prod_name, prod_price
FROM Products WHERE vend_id = 'DLL01' OR vend_id = ‘BRS01’
AND prod_price >= 10;
AND 在求值过程中优先级比 OR 更高,使用圆括号进行分组可以避免查询条件混乱,消除歧义。
SELECT prod_name, prod_price
FROM Products
WHERE (vend_id = 'DLL01' OR vend_id = ‘BRS01’)
AND prod_price >= 10;
IN 操作符
IN 操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN 取一组由逗号分隔、括在圆括号中的合法值。
优点:
- 在有很多合法选项时(一般超过三个),IN 操作符的语法更清楚,更直观。
- 在与其他 AND 和 OR 操作符组合使用 IN 时,求值顺序更容易管理。
- IN 操作符一般比一组 OR 操作符执行得更快。
- IN 的最大优点是可以包含其他 SELECT 语句,能够更动态地建立 WHERE 子句。
NOT 操作符
WHERE 子句中的 NOT 操作符有且只有一个功能,那就是否定其后所跟的任何条件。NOT 关键字可以用在要过滤的列前,而不仅是在其后。
SELECT prod_name
FROM Products
WHERE NOT vend_id = 'DLL01'
ORDER BY prod_name;
也可以使用 <> 或!=操作符来完成。
SELECT prod_name
FROM Products
WHERE vend_id <> 'DLL01'
ORDER BY prod_name;
在更复杂的子句中,NOT 是非常有用的。例如,在与 IN 操作符联合使用时,NOT 可以非常简单地找出与条件列表不匹配的行。
用通配符进行过滤
前面的过滤条件都是使用已知的值。利用通配符,可以创建比较特定数据的搜索模式。
通配符(wildcard)
用来匹配值的一部分的特殊字符。
搜索模式(search pattern)
由字面值、通配符或两者组合构成的搜索条件。
为在搜索子句中使用通配符,必须使用 LIKE 操作符。LIKE 指示 DBMS,后跟的搜索模式利用通配符匹配而不是简单的相等匹配进行比较。
通配符搜索只能用于文本字段(串),非文本数据类型字段不能使用通配符搜索。
百分号通配符(%)
最常用的通配符是百分号(%)。在搜索串中,% 表示任何字符出现任意次数。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE 'Fish%';
% 告诉 DBMS 接受 Fish 之后的任意字符,不管它有多少字符。
通配符可在搜索模式中的任意位置使用,并且可以使用多个通配符。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '%bean bag%';
搜索模式'%bean bag%'表示匹配任何位置上包含文本 bean bag 的值,不论它之前或之后出现什么字符。
需要特别注意,% 代表搜索模式中给定位置的 0 个、1 个或多个字符。通配符 % 看起来像是可以匹配任何东西,但有个例外,这就是 NULL。子句 WHERE prod_name LIKE '%'不会匹配产品名称为 NULL 的行。
下划线(_)通配符
下划线的用途与 % 一样,但它只匹配单个字符,而不是多个字符。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '__ inch teddy bear';
与 % 能匹配 0 个字符不同,_总是刚好匹配一个字符,不能多也不能少。
方括号([ ])通配符
方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。
-- 找出所有名字以J或M起头的联系人
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[JM]%'
ORDER BY cust_contact;
[JM]匹配任何以方括号中字母开头的联系人名,它也只能匹配单个字符。因此,任何多于一个字符的名字都不匹配。[JM]之后的 % 通配符匹配第一个字符之后的任意数目的字符,返回所需结果。此通配符可以用前缀字符 ^(脱字号)来否定。
-- 查询匹配不以J或M起头的任意联系人名
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[^JM]%'
ORDER BY cust_contact;
当然,也可以使用 NOT 操作符得出相同的结果。^ 的唯一优点是在使用多个 WHERE 子句时可以简化语法。
- 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
- 在确实需要使用通配符时,也尽量不要把它们用在搜索模式的开始处。把通配符置于开始处,搜索起来是最慢的。
- 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
计算字段
计算字段并不实际存在于数据库表中。计算字段是运行时在 SELECT 语句内创建的。
需要特别注意,只有数据库知道 SELECT 语句中哪些列是实际的表列,哪些列是计算字段。从客户端(如应用程序)来看,计算字段的数据与其
他列的数据的返回方式相同。
拼接字段
Access 和 SQL Server 使用 + 号。DB2、Oracle、PostgreSQL、SQLite 和 Open Office Base 使用 ||。在 MySQL 和 MariaDB 中,必须使用特殊的函数。
SELECT vend_name + ' (' + vend_country + ')'
FROM Vendors
ORDER BY vend_name;
"||"
SELECT vend_name || ' (' || vend_country || ')'
FROM Vendors
ORDER BY vend_name;
为正确返回格式化的数据,必须去掉空格。这可以使用 SQL 的 RTRIM()函数来完成。
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
FROM Vendors
ORDER BY vend_name;
MySQL ↓
SELECT Concat(vend_name, ' (', vend_country, ')')
AS vend_title
FROM Vendors
ORDER BY vend_name;
SELECT 语句本身与以前使用的相同,只不过这里的计算字段之后跟了文本 AS vend_title。它指示 SQL 创建一个包含指定计算结果的名为 vend_title 的计算字段。任何客户端应用都可以按名称引用这个列,就像它是一个实际的表列一样。
说明:AS 通常可选
在很多 DBMS 中,AS 关键字是可选的,不过最好使用它,这被视为一条最佳实践。
提示:别名的其他用途
别名还有其他用途。常见的用途包括在实际的表列名包含不合法的字符(如空格)时重新命名它,在原来的名字含混或容易误解时扩充它。
警告:别名
别名既可以是一个单词也可以是一个字符串。如果是后者,字符串应该括在引号中。虽然这种做法是合法的,但不建议这么去做。多单词的名字可读性高,不过会给客户端应用带来各种问题。因此,别名最常见的使用是将多个单词的列名重命名为一个单词的名字。
算术运算
计算字段的另一常见用途是对检索出的数据进行算术计算。
SELECT prod_id,
quantity,
item_price,
quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
操作符 | 说明 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
SELECT 语句为测试、检验函数和计算提供了很好的方法。虽然 SELECT 通常用于从表中检索数据,但是省略了 FROM 子句后就是简单地访问和处理表达式,例如 SELECT 3 * 2;将返回 6,SELECT Trim(' abc ');将返回 abc,SELECT Now();使用 Now()函数返回当前日期和时间。
函数名 | 语法 |
---|---|
提取字符串的组成部分 | MYSQL:SUBSTRING() |
数据类型转换 | MYSQL: CONVERT() |
取当前日期 | MYSQL:CURDATE() |
使用函数
大多数 SQL 支持以下类型函数:
- 用于处理文本字符串(如删除或填充值,转换值为大写或小写)的文本函数。
- 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
- 用于处理日期和时间值并从这些值中提取特定成分(如返回两个日期之差,检查日期有效性)的日期和时间函数。
- 返回 DBMS 正使用的特殊信息(如返回用户登录信息)的系统函数。
函数可以作为 SELECT 语句的 WHERE 子句中使用,在其他 SQL 语句中使用等。
文本处理函数
函数 | 说明 |
---|---|
LEFT() | 返回字符串左边的字符 |
LENGTH()(也使用 DATALENGTH()或者 LEN()) | 返回字符串的长度 |
LOWER() | 将字符串转换为小写 |
LTRIM | 去除字符串左边的空格 |
RIGHT()(或使用子字符串函数) | 返回字符串右边的字符 |
RTRIM | 去掉字符串右边的空格 |
SOUNDEX() | 返回字符串的 SOUNDEX 值 |
UPPER() | 将字符串转为大写 |
SOUNDEX 是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。SOUNDEX 考虑了类似的发音字符和音节,使得能对字符串进行发音比较而不是字母比较。虽然 SOUNDEX 不是 SQL 概念,但多数 DBMS 都提供对 SOUNDEX 的支持。 |
SELECT cust_name, cust_contact
FROM Customers
WHERE SOUNDEX(cust_contact) = SOUNDEX('Michael Green');
MySQL 可以使用 YEAR()的函数中提取年份。
数值处理函数
数值处理函数:
函数 | 说明 |
---|---|
ABS() | 返回一个数的绝对值 |
COS() | 返回一个角度的余弦 |
EXP() | 返回一个数的指数值 |
PI() | 返回圆周率 |
SIN() | 返回一个角度的正弦 |
SQRT() | 返回一个数的平方根 |
TAN() | 返回一个角度的正切 |
聚集函数
- 确定表中行数(或者满足某个条件或包含某个特定值的行数);
- 获得表中某些行的和;
- 找出表列(或所有行或某些特定的行)的最大值、最小值、平均值。
函数 | 说明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
SELECT AVG(prod_price) AS avg_price
FROM Products
WHERE vend_id = 'DLL01';
VG()只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个 AVG()函数。
AVG()函数忽略列值为 NULL 的行。
COUNT()
利用 COUNT()函数确定表中行数或符合特定条件的行数。
- 使用 COUNT(*)对表中行的数目进行技术,不管表列中包含的是空值(NULL)还是非空值
- 使用 COUNT(column)对特定列中具有值的行进行计数,忽略 NULL 值。
MAX()
MAX()返回指定列中的最大值。
虽然 MAX()一般用来找出最大的数值或日期值,但许多(并非所有)DBMS 允许将它用来返回任意列中的最大值,包括返回文本列中的最大值。在用于文本数据时,MAX()返回按该列排序后的最后一行。MAX()函数忽略列值为 NULL 的行。
MIN()
和 MAX()使用方法相反。
SUM()
SUM()用来返回指定列值得和,也可以用来合计计算值。
SELECT SUM(item_price*quantity) AS total_price
FROM OrderItems
WHERE order_num = 20005;
利用标准的算术操作符,所有聚集函数都可用来执行多个列上的计算。SUM()函数忽略列值为 NULL 的行。
聚集不同值
以上五个聚集函数都可以如下使用:
- 对所有行执行计算,指定 ALL 参数或不指定参数(因为 ALL 是默认行为)。
- 只包含不同的值,指定 DISTINCT 参数。
- ALL 参数不需要指定,因为它是默认行为。如果不指定 DISTINCT,则假定为 ALL。
- DISTINCT 不能用于 COUNT()
如果指定列名,则 DISTINCT 只能用于 COUNT()。DISTINCT 不能用于 COUNT()。类似地,DISTINCT 必须使用列名,不能用于计算或表达式。 - 将 DISTINCT 用于 MIN()和 MAX()
虽然 DISTINCT 从技术上可用于 MIN()和 MAX(),但这样做实际上没有价值。一个列中的最小值和最大值不管是否只考虑不同值,结果都是相同的。 - 除了这里介绍的 DISTINCT 和 ALL 参数,支持对查询结果的子集进行计算的 TOP 和 TOP PERCENT。
组合聚集函数
SELECT 语句可根据需要包含多个聚集函数。
SELECT COUNT(*) AS num_items,
MIN(prod_price) AS price_min,
MAX(prod_price) AS price_max,
AVG(prod_price) AS price_avg
FROM Products;
在指定别名以包含某个聚集函数的结果时,不应该使用表中实际的列名。
数据分组
使用分组可以将数据分为多个逻辑组,对每个组进行聚集计算。分组是使用 SELECT 语句的 GROUP BY 子句建立的。
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;
GROUP BY 子句指示 DBMS 按 vend_id 排序并分组数据。这就会对每个 vend_id 而不是整个表计算 num_prods 一次。
- GROUP BY 子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。
- 如果在 GROUP BY 子句中嵌套了分组,数据将在最后指定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。
- GROUP BY 子句中列出的每一列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在 SELECT 中使用表达式,则必须在 GROUP BY 子句中指定相同的表达式。不能使用别名。
- 大多数 SQL 实现不允许 GROUP BY 列带有长度可变的数据类型(如文本或备注型字段)。
- 除聚集计算语句外,SELECT 语句中的每一列都必须在 GROUP BY 子句中给出。
- 如果分组列中包含具有 NULL 值的行,则 NULL 将作为一个分组返回。如果列中有多行 NULL 值,它们将分为一组。
- GROUP BY 子句必须出现在 WHERE 子句之后,ORDER BY 子句之前。
过滤分组
HERE 不能完成任务,因为 WHERE 过滤指定的是行而不是分组。事实上,WHERE 没有分组的概念。
SQL 为此提供了另一个子句,就是 HAVING 子句。HAVING 非常类似于 WHERE。事实上,目前为止所学过的所有类型的 WHERE 子句都可以用 HAVING 来替代。唯一的差别是,WHERE 过滤行,而 HAVING 过滤分组。WHERE 和 HAVING 可以一起使用。
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
WHERE prod_price >= 4
GROUP BY vend_id
HAVING COUNT(*) >= 2;
分组和排序
ORDER BY | GROUP BY |
---|---|
对产生的输出排序 | 对行分组,但输出可能不是分组的顺序 |
任意列都可以使用(甚至非选择的列也可以使用) | 只可能使用选择列或表达式列,而且必须使用每个选择列表达式 |
不一定需要 | 如果与聚集函数一起使用列(或表达式),则必须使用 |
一般在使用 GROUP BY 子句时,应该也给出 ORDER BY 子句。这是保证数据正确排序的唯一方法。千万不要仅依赖 GROUP BY 排序数据。
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3;
SELECT order_num, COUNT(*) AS items
FROM OrderItems
GROUP BY order_num
HAVING COUNT(*) >= 3
ORDER BY items, order_num;
SELECT 子句顺序
| 子句 | 说明 | 是否必须使用 |
-- | -- |
---|---|
SELECT | 要返回的列或表达式 |
FROM | 从中检索数据的表 |
WHERE | 行级过滤 |
GROUP BY | 分组说明 |
组级过滤 | 否 |
ORDER BY | 输出排序顺序 |
子查询
SQL 还允许创建子查询(subquery),即嵌套在其他查询中的查询。
假如需要列出订购物品 RGAN01 的所有顾客:
- 检索包含物品 RGAN01 的所有订单的编号。
- 检索具有前一步骤列出的订单编号的所有顾客的 ID。
- 检索前一步骤返回的所有顾客 ID 的顾客信息。
SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01';
order_num
-----------
20007
20008
SELECT cust_id
FROM Orders
WHERE order_num IN (20007,20008);
cust_id
----------
1000000004
1000000005
SELECT cust_id
FROM Orders
WHERE order_num IN (SELECT order_num
FROM OrderItems
WHERE prod_id = 'RGAN01');
子查询总是从内向外处理。包含子查询的 SELECT 语句难以阅读和调试,它们在较为复杂时更是如此。把子查询分解为多行并进行适当的缩进,能极大地简化子查询的使用。
在 WHERE 子句中使用子查询能够编写出功能很强且很灵活的 SQL 语句。对于能嵌套的子查询的数目没有限制,不过在实际使用时由于性能的限制,不能嵌套太多的子查询。
警告:只能是单列
作为子查询的 SELECT 语句只能查询单个列。企图检索多个列将返回错误。
作为计算阻断使用字符串
SELECT cust_name,
cust_state,
(SELECT COUNT(*)
FROM Orders
WHERE Orders.cust_id = Customers.cust_id) AS orders
FROM Customers
ORDER BY cust_name;
用一个句点分隔表名和列名,这种语法必须在有可能混淆列名时使用。如果在 SELECT 语句中操作多个表,就应使用完全限定列名来避免歧义。
联结
联结是一种机制,用来在一条 SELECT 语句中关联表,因此称为联结。要理解联结不是物理实体。换句话说,它在实际的数据库表中并不存在。DBMS 会根据需要建立联结,它在查询执行期间一直存在。
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
WHERE 子句指示 DBMS 将 Vendors 表中的 vend_id 与 Products 表中的 vend_id 匹配起来。要匹配的两列指定为 Vendors.vend_id 和 Products.vend_id。这里需要这种完全限定列名,如果只给出 vend_id,DBMS 就不知道指的是哪一个(每个表中有一个)。
笛卡儿积(cartesian product)
由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
不要忘了 WHERE 子句
要保证所有联结都有 WHERE 子句,否则 DBMS 将返回比想要的数据多得多的数据。同理,要保证 WHERE 子句的正确性。不正确的过滤条件会导
致 DBMS 返回不正确的数据。
使用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内联结(inner join)。其实,可以对这种联结使用稍微不同的语法,明确指定联结的类型。
SELECT vend_name, prod_name, prod_price
FROM Vendors INNER JOIN Products
ON Vendors.vend_id = Products.Vend_id;
联结条件用特定的 ON 子句而不是 WHERE 子句给出。传递给 ON 的实际条件与传递给 WHERE 的相同。
SQL 不限制一条 SELECT 语句中可以联结的表的数目。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。
SELECT prod_name, vend_name, prod_price, quantity
FROM OrderItems, Products, Vendors
WHERE Products.vend_id = Vendors.vend_id
AND OrderItems.prod_id = Products.prod_id
AND order_num = 20007;
DBMS 在运行时关联指定的每个表,以处理联结。这种处理可能非常耗费资源,因此应该注意,不要联结不必要的表。联结的表越多,性能下降越厉害。
联结中表的最大数目
虽然 SQL 本身不限制每个联结约束中表的数目,但实际上许多 DBMS 都有限制。
使用表别名
SQL 除了可以对列名和计算字段使用别名,还允许给表名起别名。这样做有两个主要理由:
- 缩短 SQL 语句;
- 允许在一条 SELECT 语句中多次使用相同的表。
SELECT cust_name, cust_contact
FROM Customers AS C, Orders AS O, OrderItems AS OI
WHERE C.cust_id = O.cust_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';
警告:Oracle 中没有 AS
Oracle 不支持 AS 关键字。要在 Oracle 中使用别名,可以不用 AS,简单地指定列名即可(因此,应该是 Customers C,而不是 Customers ASC)。需要注意,表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户端。
不同类型的联结
联结类型:
- 内联结或等值联结
- 自联结(self-join)
- 自然联结(natural join)
- 外联结(outer join)
SELECT c1.cust_id, c1.cust_name, c1.cust_contact
FROM Customers AS c1, Customers AS c2
WHERE c1.cust_name = c2.cust_name
AND c2.cust_contact = 'Jim Jones';
提示:用自联结而不用子查询
自联结通常作为外部语句,用来替代从相同表中检索数据的使用子查询语句。虽然最终的结果是相同的,但许多 DBMS 处理联结远比处理子
查询快得多。应该试一下两种方法,以确定哪一种的性能更好。
自然联结
自然联结要求你只能选择那些唯一的列,一般通过对一个表使用通配符(SELECT *),而对其他表的列使用明确的子集来完成。
SELECT C.*, O.order_num, O.order_date,
OI.prod_id, OI.quantity, OI.item_price
FROM Customs AS C, OrderItems AS OI
WHERE C.cust_id = O.cut_id
AND OI.order_num = O.order_num
AND prod_id = 'RGAN01';
外联结
许多联结将一个表中的行与另一个表中的行相关联,但有时候需要包含没有关联行的那些行。例如,可能需要使用联结完成以下工作:
- 对每个顾客下的订单进行计数,包括那些至今尚未下订单的顾客;
- 列出所有产品以及订购数量,包括没有人订购的产品;
- 计算平均销售规模,包括那些至今尚未下订单的顾客。
内联结:检索所有顾客及其订单
SELECT Customers.cust_id, Order.order_num
FROM Customers INNER JOIN Orders On Customers.cust_id = Orders.cust_id;
外联结:检索包括没有订单顾客在内的所有顾客
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
外联结还包括没有关联行的行。在使用 OUTER JOIN 语法时,必须使用 RIGHT 或 LEFT 关键字指定包括其所有行的表(RIGHT 指出的是 OUTER JOIN 右边的表,而 LEFT 指出的是 OUTER JOIN 左边的表。
还存在另一种外联结,就是全外联结(full outer join),它检索两个表中的所有行并关联那些可以关联的行。与左外联结或右外联结包含一个表的不关联的行不同,全外联结包含两个表的不关联的行。全外联结的语法如下:
SELECT Customers.cust_id, Orders.order_num
FROM Orders FULL OUTER JOIN Customers
ON Orders.cust_id = Customers.cust_id;
Mysql 不支持 FULL OUTER JOIN
使用带聚集函数的联结
所有顾客及每个顾客所下的订单数
SELECT Customers.cust_id,
COUNT(Orders.order_num) AS num_ord
FROM Customers INNER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
使用左外部联结来包含所有顾客,甚至包含那些没有任何订单的顾客。
SELECT Customers.cust_id,
COUNT(Orders.order_num) AS num_ord
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id
GROUP BY Customers.cust_id;
组合查询
SQL 允许执行多个查询(多条 SELECT 语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
主要有两种情况需要使用组合查询:
- 在一个查询中从不同的表返回结构数据;
- 对一个表执行多个查询,按一个查询返回数据。
提示:组合查询和多个 WHERE 条件
多数情况下,组合相同表的两个查询所完成的工作与具有多个 WHERE 子句条件的一个查询所完成的工作相同。换句话说,任何具有多个 WHERE 子句的 SELECT 语句都可以作为一个组合查询。
UNION 关键字
利用 UNION,可给出多条 SELECT 语句,将它们的结果组合成一个结果集。
部分情况下,使用 UNION 可能比使用 WHERE 子句更为复杂。但对于较复杂的过滤条件,或者从多个表(而不是一个表)中检索数据的情形,使用 UNION 可能会使处理更简单。
UNION 中的每个查询必须包含相同的列、表达式或聚集函数(不过,各个列不需要以相同的次序列出)。
列数据类型必须兼容:类型不必完全相同,但必须是 DBMS 可以隐含转换的类型(例如,不同的数值类型或不同的日期类型)。
使用 UNION 时,重复的行会被自动取消。如果想返回所有的匹配行,可使用 UNION ALL 而不是 UNION。
UNION 几乎总是完成与多个 WHERE 条件相同的工作。UNION ALL 为 UNION 的一种形式,它完成 WHERE 子句完成不了的工作。如果确实需要每个条件的匹配行全部出现(包括重复行),就必须使用 UNION ALL,而不是 WHERE。
在用 UNION 组合查询时,只能使用一条 ORDER BY 子句,它必须位于最后一条 SELECT 语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条 ORDER BY 子句。
UNION 在需要组合多个表的数据时也很有用,即使是有不匹配列名的表,在这种情况下,可以将 UNION 与别名组合,检索一个结果集。
数据插入
- 插入完整的行;
- 插入行的一部分;
- 插入某些查询的结果。
事物处理
术语:
- **事务(transaction)**指一组 SQL 语句;
- **回退(rollback)**指撤销指定 SQL 语句的过程;
- **提交(commit)**指将未存储的 SQL 语句结果写入数据库表;
- **保留点(savepoint)**指事务处理中设置的临时占位符(placeholder),可以对它发布回退(与回退整个事务处理不同)。
**注:**事务处理用来管理 INSERT、UPDATE 和 DELETE 语句。不能回退 SELECT 语句(回退 SELECT 语句也没有必要),也不能回退 CREATE 或 DROP 操作。事务处理中可以使用这些语句,但进行回退时,这些操作也不撤销。
使用简单的 ROLLBACK 和 COMMIT 语句,就可以写入或撤销整个事务。但是,只对简单的事务才能这样做,复杂的事务可能需要部分提交或回退。
要支持回退部分事务,必须在事务处理块中的合适位置放置占位符。这样,如果需要回退,可以回退到某个占位符。这些占位符称为保留点。
在 MariaDB、MySQL 和 Oracle 中创建占位符,可使用 SAVEPOINT 语句:
mysql:
SAVEPOINT delete1;
ROLLBACK TO delete1;
每个保留点都要取能够标识它的唯一名字,以便在回退时,DBMS 知道回退到何处。
可以在 SQL 代码中设置任意多的保留点,越多越好。为什么呢?因为保留点越多,你就越能灵活地进行回退。
游标
游标(cursor)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
- 能够标记游标为只读,使数据能读取,但不能更新和删除。
- 能控制可以执行的定向操作(向前、向后、第一、最后、绝对位置、相对位置等)。
能标记某些列为可编辑的,某些列为不可编辑的。 - 规定范围,使游标对创建它的特定请求(如存储过程)或对所有请求可访问。
- 指示 DBMS 对检索出的数据(而不是指出表中活动数据)进行复制,使数据在游标打开和访问期间不变化。
- 游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。
索引
- 索引改善检索操作的性能,但降低了数据插入、修改和删除的性能。在执行这些操作时,DBMS 必须动态地更新索引。
- 索引数据可能要占用大量的存储空间。
- 并非所有数据都适合做索引。取值不多的数据(如州)不如具有更多可能值的数据(如姓或名),能通过索引得到那么多的好处。
- 索引用于数据过滤和数据排序。如果你经常以某种特定的顺序排序数据,则该数据可能适合做索引。
- 可以在索引中定义多个列(例如,州加上城市)。这样的索引仅在以州加城市的顺序排序时有用。如果想按城市排序,则这种索引没有用处。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于