表
存储在表中的数据是同一种类型的数据或清单。
数据库中的表有为一个名字来标识自己。
表具有一些特性,这些特性定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。描述表的这组信息就是所谓的模式(schema),模式可以用来描述数据库中特定的表,也可以用来面熟整个数据库(和其中表的关系)。
列和数据类型
表由列组成。列存储表中某部分的信息。
行
主键
表中的每一行都应该有一列可以唯一标识自己。
主键(primary key),一列(或一组列),其值可以唯一标识表中每一行。
主键的特性:
a.任意两行都不具有相同的主键值
b.每一行都必须具有一个主键值
c.主键列的值不允许被修改和更新
d.主键值不能重用
什么是SQL
SQL不是某个特定数据库供应商专有的语言。几乎所有重要的DBMS都支持SQL
SQL简单易学
SQL虽然看起来很简单,但实际上是一种强有力的语言,灵活使用其语言元素,可以进行非常复杂和高级的数据库操作。
检索数据库
Select语句
SELECT prod_name FROM Products;
上句表示从表Products中检索一个名为product_name的列。
Tips:
t1:结束SQL语句
多条SQL语句必须以(;)分隔
t2:SQL语句和大小写
SQL语句不区分大小写
t3:使用空格
在处理SQL语句是,其中所有空格都被忽略。SQL语句可以写成长长的一行,也可以分写在多行。
检索多个列
SELECT prod_id,prod_name,prod_price FROM Products;
检索所有列
SELECT * FROM Products;
通配符(*),返回表中所有列。列的顺序一般是列在表定义中出现的物理顺序,但并不总是如此。
检索不同的值
前述SELECT语句返回所有匹配的行,但是,如果你不希望每个值每次都出现。
如:SELECT vend_id FROM Products; 返回表products中所有产品供应商的id.
SELECT DISTINCT vend_id FROM Products;
注意:
DISTINCT关键字作用于所有列,不仅仅是跟在其后的那一列。
注释:
-- 这是一条注释(跟在命令后面)
#这是一条注释(在一行开始处使用#,这一整行都被作为注释。)
排序检索数据
排序数据
SELECT prod_name FROM Products;
其实,检索出的数据并不是随机显示的。如果不排序,数据一般将以它在底层表中出现的顺序显示,这有可能是数据最初添加到表中的顺序。但是,如果数据随后进行过更新或删除,那么这个顺序将会受到 DBMS 重用回收存储空间的方式的影响。因此,如果不明确控制的话,则最终的结果不能(也不应该)依赖该排序顺序。关系数据库设计理论认为,如果不明确规定排序顺序,则不应该假定检索出的数据的顺序有任何意义。
SELECT prod_name
FROM Products
ORDER BY prod_name;
ORDER BY:在指定一条ORDER BY子句时,应该保证它是SELECT语句中最后一条子句。如果它不是最后的子句,将会出现错误消息。
按多列排序
SELECT prod_id,prod_name,prod_price
FROM Products
ORDER BY prod_price,prod_name;
按列位置排序
SELECT prod_id,prod_name,prod_price
FROM Products
ORDER BY 2,3;
可以看到,这里的输出与上面的查询相同,不同之处在于ORDER BY子句。SELECT清单中指定的是选择列的相对位置而不是列名。ORDER BY 2表示按SELECT清单中的第二个列prod_name进行排序。ORDER BY 2,3表示先按prod_price,再按prod_name进行排序。
这一技术的主要好处在于不用重新输入列名。但它也有缺点。首先,不明确给出列名有可能造成错用列名排序。其次,在对SELECT清单进行更改时容易错误地对数据进行排序(忘记对ORDER BY子句做相应的改动)。最后,如果进行排序的列不在SELECT清单中,显然不能使用这项技术。
指定排序方向
数据排序不限于升序排序(A-Z,0-9),这只是默认的排序顺序。还可以使用ORDER BY 子句进行降序排序。
SELECT prod_id,prod_name,prod_price
FROM Products
ORDER BY prod_price DESC;
SELECT prod_id,prod_name,prod_price
FROM Products
ORDER BY prod_price DESC,prod_name;
DESC只作用于直接位于其前面的列名。在上例中,只对prod_price列指定DESC,对prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(在每个价格内)仍然按标准的升序排序。
过滤数据
WHERE子句
数据库表一般包含大量的数据,很少需要检索表中的所有行。通常只会根据特定操作或报告的需要提取表数据的子集。只检索所需数据需要指定 搜索条件( search criteria ),搜索条件也称为 过滤条件( filter condition )。
SELECT prod_name,prod_price
FROM Products
WHERE prod_price = 3.49;
在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后,否则将会产生错误(关于ORDER BY的使用,请参阅第 3 课)。
WHERE子句操作符
=等于
<>不等于
!=不等于
<小于
<=小于等于
!<不小于
>大于
>= 大于等于
!> 不大于
BETWEEN 在指定的两个值之间 BETWEEN 5 AND 10
IS NULL 为NULL值
SELECT prod_name,prod_price
FROM Products
WHERE prod_price BETWEEN 5 AND 10;
SELECT prod_name
FROM Products
WHERE prod_price IS NULL;
这条语句返回所有没有价格(空prod_price字段,不是价格为0)的产品,由于表中没有这样的行,所以没有返回数据。但是,Customers表确实包含具有NULL值的列:如果没有电子邮件地址,则cust_email列将包含NULL值。
SELECT cust_name
FROM Customers
WHERE cust_email IS NULL;
高级数据过滤
AND OR
SELECT prod_id,prod_name,prod_price
FROM Products
WHERE vend_id = 'DLL01' AND prod_price <= 4;
SELECT prod_id,prod_name,prod_price
FROM Products
WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';
求值顺序
WHERE子句可以包含任意数目的AND和OR操作符。允许两者结合以进行复杂、高级的过滤。
SELECT prod_id,prod_name,prod_price
FROM Products
WHERE (vend_id = 'DLL01' OR vend_id = 'BRS01') AND prod_price <= 4;
任何时候使用具有AND和OR操作符的WHERE子句,都应该使用圆括号明确地分组操作符。不要过分依赖默认求值顺序,即使它确实如你希望的那样。使用圆括号没有什么坏处,它能消除歧义。
IN操作符
IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取一组由逗号分隔、括在圆括号中的合法值。
SELECT prod_name, prod_price
FROM Products
WHERE vend_id IN ( 'DLL01', 'BRS01' )
ORDER BY prod_name;
在有很多合法选项时,IN操作符的语法更清楚,更直观。
在与其他AND和OR操作符组合使用IN时,求值顺序更容易管理。
IN操作符一般比一组OR操作符执行得更快(在上面这个合法选项很少的例子中,你看不出性能差异)。
IN的最大优点是可以包含其他SELECT语句,能够更动态地建立WHERE子句。
WHERE子句中用来指定要匹配值的清单的关键字,功能与OR相当。
NOT操作符
HERE子句中的NOT操作符有且只有一个功能,那就是否定其后所跟的任何条件。因为NOT从不单独使用(它总是与其他操作符一起使用),所以它的语法与其他操作符有所不同。NOT关键字可以用在要过滤的列前,而不仅是在其后。
SELECT prod_name
FROM Products
WHERE NOT vend_id = 'DLL01'
ORDER BY prod_name;
用通配符进行过滤
LIKE操作符
前面介绍的所有操作符都是针对已知值进行过滤的。不管是匹配一个值还是多个值,检验大于还是小于已知值,或者检查某个范围的值,其共同点是过滤中使用的值都是已知的。
但是,这种过滤方法并不是任何时候都好用。例如,怎样搜索产品名中包含文本 bean bag 的所有产品?用简单的比较操作符肯定不行,必须使用通配符。利用通配符,可以创建比较特定数据的搜索模式。在这个例子中,如果你想找出名称包含 bean bag 的所有产品,可以构造一个通配符搜索模式,找出在产品名的任何位置出现 bean bag 的产品。
通配符(wildcard):用来匹配值的一部分的特殊字符。
搜索模式(search pattern):由字面值、通配符或两者组合构成的搜索条件。
通配符本身实际上是 SQL 的WHERE子句中有特殊含义的字符,SQL支持几种通配符。为在搜索子句中使用通配符,必须使用LIKE操作符。LIKE指示 DBMS ,后跟的搜索模式利用通配符匹配而不是简单的相等匹配进行比较。
百分号(%)通配符
在搜索串中,%表示任何字符出现任意次数。例如,为了找出所有以词Fish起头的产品,可发布以
下SELECT语句:
SELECT prod_name
FROM Products
WHERE NOT prod_name LIKE 'Fish%'
SELECT prod_id,prod_name
FROM Products
WHERE NOT prod_name LIKE '%bean bag%'
下划线(_)通配符
另一个有用的通配符是下划线(_)。下划线的用途与%一样,但它只匹配单个字符,而不是多个字符。
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '__ inch teddy bear';--是两个下划线,所以匹配两个字符
SELECT prod_id, prod_name
FROM Products
WHERE prod_name LIKE '% inch teddy bear';--是两个下划线,所以匹配两个字符
方括号( [ ] )通配符
方括号([])通配符用来指定一个字符集,它必须匹配指定位置(通配符的位置)的一个字符。
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[JM]%'
ORDER BY cust_contact;
此语句的WHERE子句中的模式为'[JM]%'。这一搜索模式使用了两个不同的通配符。[JM]匹配任何以方括号中字母开头的联系人名,它也只能匹配单个字符。因此,任何多于一个字符的名字都不匹配。[JM]之后的%通配符匹配第一个字符之后的任意数目的字符,返回所需结果。
此通配符可以用前缀字符^(脱字号)来否定。例如,下面的查询匹配不以J或M起头的任意联系人名(与前一个例子相反):
SELECT cust_contact
FROM Customers
WHERE cust_contact LIKE '[^JM]%'
ORDER BY cust_contact;
创建计算字段
需要显示公司名,同时还需要显示公司的地址,但这两个信息存储在不同的表列中。
城市、州和邮政编码存储在不同的列中(应该这样),但邮件标签打印程序需要把它们作为一个有恰当格式的字段检索出来。
列数据是大小写混合的,但报表程序需要把所有数据按大写表示出来。
物品订单表存储物品的价格和数量,不存储每个物品的总价格(用价格乘以数量即可)。但为打印发票,需要物品的总价格。
需要根据表数据进行诸如总数、平均数的计算。
在上述每个例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据,而不是检索
出数据,然后再在客户端应用程序中重新格式化。
这就是计算字段可以派上用场的地方了。与前几课介绍的列不同,计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。
字段(field):基本上与列( column )的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常与计算字段一起使用。
需要特别注意,只有数据库知道SELECT语句中哪些列是实际的表列,哪些列是计算字段。从客户端(如应用程序)来看,计算字段的数据与其他列的数据的返回方式相同。
拼接字段(concatenate)
拼接( concatenate ):将值联结到一起(将一个值附加到另一个值)构成单个值。
解决办法是把两个列拼接起来。在 SQL 中的SELECT语句中,可使用一个特殊的操作符来拼接两个列。根据你所使用的 DBMS ,此操作符可用加号(+)或两个竖杠(||)表示。在 MySQL 和 MariaDB 中,必须使用特殊的函数。
SELECT vend_name + ' (' + vend_country + ')'
FROM Vendors
ORDER BY vend_name;
使用别名
从前面的输出可以看到,SELECT语句可以很好地拼接地址字段。但是,这个新计算列的名字是什么呢?实际上它没有名字,它只是一个值。如果仅在 SQL 查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户端应用中,因为客户端没有办法引用它。
为了解决这个问题, SQL 支持列别名。别名( alias )是一个字段或值的替换名。别名用AS关键字赋予。
#RTRIM()函数去掉值右边的所有空格。通过使用RTRIM(),各个列都进行了整理。
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
AS vend_title
FROM Vendors
ORDER BY vend_name;
SELECT语句本身与以前使用的相同,只不过这里的计算字段之后跟了文本AS vend_title。它指示 SQL 创建一个包含指定计算结果的名为vend_title的计算字段。从输出可以看到,结果与以前的相同,但现在列名为vend_title,任何客户端应用都可以按名称引用这个列,就像它是一个实际的表列一样。
别名还有其他用途。常见的用途包括在实际的表列名包含不合法的字符(如空格)时重新命名它,在原来的名字含混或容易误解时扩充它。
执行算术计算
SELECT prod_id, quantity, item_price
FROM OrderItems
WHERE order_num = 20008;
SELECT prod_id,
quantity,
item_price,
quantity*item_price AS expanded_price
FROM OrderItems
WHERE order_num = 20008;
数据分组
SELECT vend_id, COUNT(*) AS num_prods
FROM Products
GROUP BY vend_id;
GROUP BY子句可以包含任意数目的列,因而可以对分组进行嵌套,更细致地进行数据分组。
如果在GROUP BY子句中嵌套了分组,数据将在最后指定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。
GROUP BY子句中列出的每一列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。
大多数 SQL 实现不允许GROUP BY列带有长度可变的数据类型(如文本或备注型字段)。
除聚集计算语句外,SELECT语句中的每一列都必须在GROUP BY子句中给出。
如果分组列中包含具有NULL值的行,则NULL将作为一个分组返回。如果列中有多行NULL值,它们将分为一组。
GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
过滤分组
除了能用GROUP BY分组数据外, SQL 还允许过滤分组,规定包括哪些分组,排除哪些分组。例如,你可能想要列出至少有两个订单的所有顾客。为此,必须基于完整的分组而不是个别的行进行过滤。
我们已经看到了WHERE子句的作用(第 4 课提及)。但是,在这个例子中WHERE不能完成任务,因为WHERE过滤指定的是行而不是分组。事实上,WHERE没有分组的概念。
那么,不使用WHERE使用什么呢? SQL 为此提供了另一个子句,就是HAVING子句。HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是,WHERE过滤行,而HAVING过滤分组。
SELECT cust_id, COUNT(*) AS orders
FROM Orders
GROUP BY cust_id
HAVING COUNT(*) >= 2;
HAVING 和 WHERE 的差别
这里有另一种理解方法,WHERE在数据分组前进行过滤,HAVING在数据分组后进行过滤。这是一个重要的区别,WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组。
分组和排序
GROUP BY和ORDER BY经常完成相同的工作,但它们非常不同,理解这一点很重要。表 10-1 汇总了它们之间的差别。
表 10-1 中列出的第一项差别极为重要。我们经常发现,用GROUP BY分组的数据确实是以分组顺序输出的。但并不总是这样,这不是 SQL 规范所要求的。此外,即使特定的 DBMS 总是按给出的GROUP BY子句排序数据,用户也可能会要求以不同的顺序排序。就因为你以某种方式分组数据(获得特定的分组聚集值),并不表示你需要以相同的方式排序输出。应该提供明确的ORDER BY子句,即使其效果等同于GROUP BY子句。
提示:不要忘记 ORDER 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语句与前面所有语句一样指定要检索的列。这里最大的差别是所指定的两列(prod_name和prod_price)在一个表中,而第三列(vend_name)在另一个表中。
SELECT vend_name, prod_name, prod_price
FROM Vendors, Products
WHERE Vendors.vend_id = Products.vend_id;
现在来看FROM子句。与以前的SELECT语句不一样,这条语句的FROM子句列出了两个表:Vendors和Products。它们就是这条SELECT语句联结的两个表的名字。这两个表用WHERE子句正确地联结,WHERE子句指示 DBMS 将Vendors表中的vend_id与Products表中的vend_id匹配起来。
可以看到,要匹配的两列指定为Vendors.vend_id和Products.vend_id。这里需要这种完全限定列名,如果只给出vend_id, DBMS 就不知道指的是哪一个(每个表中有一个)。从前面的输出可以看到,一条SELECT语句返回了两个不同表中的数据。
要保证所有联结都有WHERE子句,否则 DBMS 将返回比想要的数据多得多的数据。同理,要保证WHERE子句的正确性。不正确的过滤条件会导致 DBMS 返回不正确的数据。
内联结
目前为止使用的联结称为等值联结( equijoin ),它基于两个表之间的相等测试。这种联结也称为内联结( inner join )。其实,可以对这种联结使用稍微不同的语法,明确指定联结的类型。下面的SELECT语句返回与前面例子完全相同的数据:
SELECT vend_name, prod_name, prod_price
FROM Vendors INNER JOIN Products
ON Vendors.vend_id = Products.vend_id;
此语句中的SELECT与前面的SELECT语句相同,但FROM子句不同。这里,两个表之间的关系是以INNER JOIN指定的部分FROM子句。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。
创建高级链接
SELECT RTRIM(vend_name) + ' (' + RTRIM(vend_country) + ')'
AS vend_title
FROM Vendors
ORDER BY vend_name;
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';
使用不同类型的联结
迄今为止,我们使用的只是内联结或 等值联结的简单联结。现在来看三种其他联结:自联结( self-join )、自然联结( natural join )和外联结( outer join )。
自联结(self-join)
假如要给与 Jim Jones 同一公司的所有顾客发送一封信件。这个查询要求首先找出 Jim Jones 工作的公司,然后找出在该公司工作的顾客。下面解决此问题的一种方法:
SELECT cust_id, cust_name, cust_contact
FROM Customers
WHERE cust_name = (SELECT cust_name
FROM Customers
WHERE cust_contact = 'Jim Jones');
这是第一种解决方案,使用了子查询。内部的SELECT语句做了一个简单检索,返回 Jim Jones 工作公司的cust_name。该名字用于外部查询的WHERE子句中,以检索出为该公司工作的所有雇员。
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';
此查询中需要的两个表实际上是相同的表,因此Customers表在FROM子句中出现了两次。虽然这是完全合法的,但对Customers的引用具有歧义性,因为 DBMS 不知道你引用的是哪个Customers表。
解决此问题,需要使用表别名。Customers第一次出现用了别名C1,第二次出现用了别名C2。现在可以将这些别名用作表名。例如,SELECT语句使用C1前缀明确给出所需列的全名。如果不这样, DBMS 将返回错误,因为名为cust_id、cust_name、cust_contact的列各有两个。 DBMS 不知道想要的是哪一列(即使它们其实是同一列)。WHERE首先联结两个表,然后按第二个表中的cust_contact过滤数据,返回所需的数据。
自然联结
无论何时对表进行联结,应该至少有一列不止出现在一个表中(被联结的列)。标准的联结(前一课中介绍的内联结)返回所有数据,相同的列甚至多次出现。自然联结排除多次出现,使每一列只返回一次。
SELECT C.*, O.order_num, O.order_date,OI.prod_id, OI.quantity, OI.item_price
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';
外联结
许多联结将一个表中的行与另一个表中的行相关联,但有时候需要包含没有关联行的那些行。例如,可能需要使用联结完成以下工作:
对每个顾客下的订单进行计数,包括那些至今尚未下订单的顾客;
列出所有产品以及订购数量,包括没有人订购的产品;
计算平均销售规模,包括那些至今尚未下订单的顾客。
SELECT Customers.cust_id, Orders.order_num
FROM Customers LEFT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
类似上一课提到的内联结,这条SELECT语句使用了关键字OUTER JOIN来指定联结类型(而不是在WHERE子句中指定)。但是,与内联结关联两个表中的行不同的是,外联结还包括没有关联行的行。在使用OUTER JOIN语法时,必须使用RIGHT或LEFT关键字指定包括其所有行的表(RIGHT指出的是OUTER JOIN右边的表,而LEFT指出的是OUTER JOIN左边的表)。上面的例子使用LEFT OUTER JOIN从FROM子句左边的表(Customers表)中选择所有行。为了从右边的表中选择所有行,需要使用RIGHT OUTER JOIN,如下例所示
SELECT Customers.cust_id, Orders.order_num
FROM Customers RIGHT OUTER JOIN Orders
ON Customers.cust_id = Orders.cust_id;
存在另一种外联结,就是全外联结( full outer join ),它检索两个表中的所有行并关联那些可以关联的行。与左外联结或右外联结包含一个表的不关联的行不同,全外联结包含两个表的不关联的行。全外联结的语法如下:
SELECT Customers.cust_id, Orders.order_num
FROM Orders FULL OUTER JOIN Customers
ON Orders.cust_id = Customers.cust_id;
警告: FULL OUTER JOIN 的支持
Access 、 MariaDB 、 MySQL 、 Open Office Base 或 SQLite 不支持FULL OUTER JOIN语法。
使用带聚集函数的联结
注意所使用的联结类型。一般我们使用内联结,但使用外联结也有效。
关于确切的联结语法,应该查看具体的文档,看相应的 DBMS 支持何种语法(大多数 DBMS 使用这两课中描述的某种语法)。
保证使用正确的联结条件(不管采用哪种语法),否则会返回不正确的数据。
应该总是提供联结条件,否则会得出笛卡儿积。
在一个联结中可以包含多个表,甚至可以对每个联结采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前分别测试每个联结。这会使故障排除更为简单。
组合查询
本课讲述如何利用UNION操作符将多条SELECT语句组合成一个结果集。
组合查询
多数 SQL 查询只包含从一个或多个表中返回数据的单条SELECT语句。但是,SQL也允许执行多个查询(多条SELECT语句),并将结果作为一个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
主要有两种情况需要使用组合查询:
在一个查询中从不同的表返回结构数据;
对一个表执行多个查询,按一个查询返回数据。
多数情况下,组合相同表的两个查询所完成的工作与具有多个WHERE子句条件的一个查询所完成的工作相同。换句话说,任何具有多个WHERE子句的SELECT语句都可以作为一个组合查询,在下面可以看到这一点。
创建组合查询
可用UNION操作符来组合数条 SQL 查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成一个结果集。
使用 UNION
假如需要 Illinois 、 Indiana 和 Michigan 等美国几个州的所有顾客的报表,还想包括不管位于哪个州的所有的Fun4All。当然可以利用WHERE子句来完成此工作,不过这次我们使用UNION。
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI');
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
组合查询
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
UNION
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_name = 'Fun4All';
为了便于参考,这里给出使用多条WHERE子句而不是UNION的相同查询:
SELECT cust_name, cust_contact, cust_email
FROM Customers
WHERE cust_state IN ('IL','IN','MI')
OR cust_name = 'Fun4All';
UNION规则
UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合四条SELECT语句,将要使用三个UNION关键字)。
UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过,各个列不需要以相同的次序列出)。
列数据类型必须兼容:类型不必完全相同,但必须是 DBMS 可以隐含转换的类型(例如,不同的数值类型或不同的日期类型)。
我们看看所用的SELECT语句。注意到在分别执行语句时,第一条SELECT语句返回 3 行,第二条SELECT语句返回 2 行。而在用UNION组合两条SELECT语句后,只返回 4 行而不是 5 行。UNION从查询结果集中自动去除了重复的行;换句话说,它的行为与一条SELECT语句中使用多个WHERE子句条件一样。因为 Indiana 州有一个Fun4All 单位,所以两条SELECT语句都返回该行。使用UNION时,重复的行会被自动取消。
SELECT语句的输出用ORDER BY子句排序。在用UNION组合查询时,只能使用一条ORDER BY子句,它必须位于最后一条SELECT语句之后。对于结果集,不存在用一种方式排序一部分,而又用另一种方式排序另一部分的情况,因此不允许使用多条ORDER BY子句。
使用函数
常用的文本处理函数
LEFT() 返回字符串左边的字符
LENGTH() 返回字符串的长度
LOWER() 将字符串转换为小写
UPPER() 将字符串转换为大写
LTRIM() 去掉字符串左边的空格
RIGHT()返回字符串右边的字符
RTRIM()去掉字符串右边的空格
SOUNDEX() 返回字符串的SOUNDEX值
插入数据
INSERT用来将行插入(或添加)到数据库表。插入有几种方式:
插入完整的行;
插入行的一部分;
插入某些查询的结果。
INSERT INTO Customers
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA',
NULL,
NULL);
这个例子将一个新顾客插入到Customers表中。存储到表中每一列的数据在VALUES子句中给出,必须给每一列提供一个值。如果某列没有值,如上面的cust_contact和cust_email列,则应该使用NULL值(假定表允许对该列指定空值)。各列必须以它们在表定义中出现的次序填充。
虽然这种语法很简单,但并不安全,应该尽量避免使用。上面的 SQL 语句高度依赖于表中列的定义次序,还依赖于其容易获得的次序信息。即使可以得到这种次序信息,也不能保证各列在下一次表结构变动后保持完全相同的次序。因此,编写依赖于特定列次序的 SQL 语句是很不安全的,这样做迟早会出问题。
编写INSERT语句的更安全(不过更烦琐)的方法如下:
INSERT INTO Customers(cust_id,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country,
cust_contact,
cust_email)
VALUES('1000000006',
'Toy Land',
'123 Any Street',
'New York',
'NY',
'11111',
'USA',
NULL,
NULL);
这个例子与前一个INSERT语句的工作完全相同,但在表名后的括号里明确给出了列名。在插入行时, DBMS 将用VALUES列表中的相应值填入列表中的对应项。VALUES中的第一个值对应于第一个指定列名,第二个值对应于第二个列名,如此等等。
因为提供了列名,VALUES必须以其指定的次序匹配指定的列名,不一定按各列出现在表中的实际次序。其优点是,即使表的结构改变,这条INSERT语句仍然能正确工作。
插入检索出的数据
插入部分行
使用视图
下面是视图的一些常见应用。
重用 SQL 语句。
简化复杂的 SQL 操作。在编写查询后,可以方便地重用它而不必知道其基本查询细节。
使用表的一部分而不是整个表。
保护数据。可以授予用户访问表的特定部分的权限,而不是整个表的访问权限。
更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
创建视图之后,可以用与表基本相同的方式使用它们。可以对视图执行SELECT操作,过滤和排序数据,将视图联结到其他视图或表,甚至添加和更新数据(添加和更新数据存在某些限制,关于这个内容稍后做介绍)。
重要的是,要知道视图仅仅是用来查看存储在别处数据的一种设施。视图本身不包含数据,因此返回的数据是从其他表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据。
下面是关于视图创建和使用的一些最常见的规则和限制。
与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
对于可以创建的视图数目没有限制。
创建视图,必须具有足够的访问权限。这些权限通常由数据库管理人员授予。
视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造视图。所允许的嵌套层数在不同的 DBMS 中有所不同(嵌套视图可能会严
重降低查询的性能,因此在产品环境中使用之前,应该对其进行全面测试)。
许多 DBMS 禁止在视图查询中使用ORDER BY子句。
有些 DBMS 要求对返回的所有列进行命名,如果列是计算字段,则需要使用别名(关于列别名的更多信息,请参阅第 7 课)。
视图不能索引,也不能有关联的触发器或默认值。
有些 DBMS 把视图作为只读的查询,这表示可以从视图检索数据,但不能将数据写回底层表。详情请参阅具体的 DBMS 文档。
有些 DBMS 允许创建这样的视图,它不能进行导致行不再属于视图的插入或更新。例如有一个视图,只检索带有电子邮件地址的顾客。如
果更新某个顾客,删除他的电子邮件地址,将使该顾客不再属于视图。这是默认行为,而且是允许的,但有的 DBMS 可能会防止这种情况发生。
CASE语句
case when condition1 then 取值1
when condition2 then 取值2
else 取值3 end
row_number() over (partition by variable1 order by variable2) rank
按variable1分组并在每个组内按variable2排序,最终生成序号,并将其命名为RANK.