第十五章 联结表
Sql最强大的功能之一就是能在数据检索查询的执行中联结(join)表。联结是利用sql的select能执行的最重要的操作,能很好的理解联结及其语法是学习sql的一个极为重要的组成部分。
外键:外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。
好处:供应商信息不重复,不浪费空间和时间,方便日后修改,一个表信息改动不影响另一个表的信息
联结是一种机制,使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
创建联结
SELECT vend_name , prod_name , prod_price
FROM vendors , products
WHERE vendors.vend_id = products . Vend_id
ORDER BY vend_name , prod_name; ;
如果没有WHERE子句,表一中的每行将于表二中的每行配对,而不管他们逻辑是否可以配在一起。其配对的结果是 笛卡尔积 。就是表一行数*表二行数
内部联结
前面讲的联结称为等值联结,它基于两个表之间的相等测试。这种联结也称为内部联结。前其实这种联结可以使用稍微不同的语法来明确指定联结的类型。下面是语句与上面返回一样
SELECT vend_name , prod_name , prod_price
FROM vendors
INNER JOIN products ON vendors . vend_id = products . vend_id;
这里的两个表之间的关系是由FROM 子句组成的。以 INNER JION指定。使用这种语法时。联结子句的条件用特定的ON子句而不是WHERE子句给出。传递给ON的跟传递给WHERE的子句相同。
首选使用INNER JOIN语法,这样不会忘记联结的条件,虽然WHERE子句定义联结比较简单。
联结多个表
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 = 20005;
考虑到多个表联结时会耗费资源,所以尽量不要联结不必要的表。
第十六章 创建高级联结
使用别名
SELECT Concat(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 = 'TN2'
表的别名和列的别名不一样,表的别名不返回给客户端
使用不同类型的联结
前面为止我们使用的只是称为内部联结或等值联结的简单联结。现在来看其他3中联结。分别是自联结、自然联结和外部联结
自联结
使用表别名的主要原因之一是能在单条SELECT语句中不止一次引用相同的表。举个例子:
如果发现某物品(id为abc)存在问题,因此想知道生产该物品的生产商生产的其他物品是否也存在问题。此程序要求首先找到生产id为abc的物品的生产商,然后找出这个生产商生产的其他的物品。下面一种解决方法:
SELECT prod_id , prod_name FROM products WHERE vend_id = (
SELECT vend_id FROM products WHERE prod_id = 'abc'
);
上面使用的子查询,现在看使用联结的相同查询:
SELECT p1.prod_id , prod_name
FROM products AS p1 , products AS p2
WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'abc';
此联结查询需要的两个表实际上同一个表,
用自联结不用子查询 子联结通常作为外部语句用来代替从相同表中检索数据时使用的子查询语句,虽然结果一样 ,但是处理联结远不处理子查询要快的多。
自然联结
SELECT * 改为 SELECT 表名1.列名1 , 表名2.列名3 这样可以去除无用的列。执行效率更快
外部联结
许多联结将一个表中的行与另一个表中的行相关联。但有时候会需要包含没有关联行的那些行。
例如完成需要使用联结完成以下任务:
1 对每个用户下了多少订单进行计数,包括那些至今尚未下订单的客户
2 列出所有产品以及订购数量,包括那些没有人订购的产品
3 计数平均销售规模,包括那些至今未下订单的客户
上述例子中,联结包含了那些在相关表中没有关联的行,这种联结类型的联结称为外部联结。
下面是SELECT 语句给出一个简单的内部联结。 它检索出了所有客户以及订单:
SELECT customers.cust_id , orders.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 = order_cust_id;
这条sql语句使用了关键字OUTER JOIN 来指定联结的类型(而不是WHERE指定)。但是与内部联结关联两个表中的行不同的是,外部联结还包括没有关联的行。
使用OUTER JOIN 必须使用LEFT 或 RIGHT关键字指定包括其所有含的表。
使用带聚集函数的联结
聚集函数是用来汇总数据。它可以中单个表中汇总数据,也可以在联结中一起使用。
例子:检索所有客户及每个客户所下的订单数。。下面使用了COUNT()函数的代码完成。
SELECT customer.cust_name , customer . cust_id , COUNT(order.order_num) ASnum_ord
FROM customers
INNER JOIJN orders ON customers.cust_id = orders.cust_id
GROUP BY customers.cust;
GROUP BY 子句按客户分组数据。因此,函数调用COUNT (orders.order_num)对每个客户的订单计数,将他作为num_ord返回
聚集函数也可以方便地与其他联结一起使用
SELECT customers.cust_name , customers.cust_id , COUNT(order.order_num) ASnum_ord
FROM customers
LEFT OUTER JOIN orders ON customers.cust_id = order.cust_id
GROUP BY customers.cust_id;
使用联结和联结条件
1 注意所使用的联结类型,一般我们使用内部联结,但使用外部联结也是有效的。
2 保证使用正确的联结条件,否则将返回不正确的数据;
3 应该始终提供联结条件,否则会得出笛卡尔积
4 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在测试它们之前,分别测试每个联结。这将使故障排除更为简单。
第十七章 组合查询
前面的都是从一个或多个表中返回数据的单条SELECT 语句。MYSQL也允许执行多个查询(多条SELECT语句),并将结果作为单条查询结果集返回。这些组合查询通常称为 并 union 或复合查询
有两种情况需要使用组合查询
1 在单个查询中从不同的表返回累世结构的数据
2 对单个表执行多个查询,按单个查询返回数据
组合查询和多个WHERE条件 多数情况下,组合相同表的两个查询完成的工作与具有多个WHERE子句完成的工作相同,换句话说,任何具有多个WHERE子句的SELECT语句 都可以作为一个组合查询给出。
创建组合查询
可用UNION操作符来组合数条SQL查询。给出多条SELECT语句,将它们的结合组合成单个结果返回
使用UNION
使用很简单,在多条SELECT语句之间放上关键词UNION。
例如:需要价格不大于5元,但是又要包含供应商1002和1001的所有产品,(不考虑价格)
SELECT vend_id ,prod_id , prod_price FROM products WHERE prod_price <= 5
UNION
SELECT vend_id ,prod_id , prod_price FROM products WHERE vend_id IN (1001,1002)
OR语句也能实现上面的查询
SELECT vend_id ,prod_id , prod_price FROM products
WHERE prod_price <= 5 OR vend_id IN (1001,1002)
从上面这个简单的例子中,使用UNION可能比使用WHERE子句更为复杂。 但对于更复杂的过滤条件,或者从多个表而不是单个表中检索数据的情形,使用UNION肯会使处理更简单
UNION规则
正如所见,并是非常容易使用的,但在进行并时有几条规则需要注意
1 UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔
2 UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各列不需要以相同的次序列出)
3 列数据类型必须兼容:类型不完全相同,但不想是DBMS可以隐含滴转换的类型。
包含或取消重复的行
如果SELECT语句中返回的行相同,UNION会自动的去除重复的行。这是他的默认行为,但是如果需要,可以改变它,可以使用UNION ALL 而不是UNION
SELECT vend_id ,prod_id , prod_price FROM products WHERE prod_price <= 5
UNION ALL
SELECT vend_id ,prod_id , prod_price FROM products WHERE vend_id IN(1001,1002)
对组合查询结果排序
SELECT语句的输出用ORDER BY子句排序。在UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。
SELECT vend_id ,prod_id , prod_price FROM products WHERE prod_price <= 5
UNION ALL
SELECT vend_id ,prod_id , prod_price FROM products WHERE vend_id IN(1001,1002)
GROUP BY vend_id,prod_price;
第十八章 全文本搜索
理解全文本搜索
并非所有的引擎都支持全文本搜索。最常使用的是MyISAM和InnoDB,前者支持全文本搜索,而后者不支持。如果你需要全文本搜索功能,应该记住这一点
关键字LIKE,它利用通配操作匹配的文本(和部分文本)。使用LIKE,能够查找包含特殊值或部分值的行(不管这些值位于那些位置)
基于文本的搜索作为正则表达式匹配列值的更进一步的介绍。使用正则表达式,可以编写查找所有行的非常复杂的匹配模式
虽然这些搜索机制非常有用。但存在几个重要的限制:
1 性能——通配符和正则表达式匹配通常要求MYSQL尝试匹配表中所有的行(而且这些搜索极少使用表索引),因此,由于被搜索行数不断增加,这些搜索非常耗时
2 明确控制——使用通配符和正则表达式匹配,很难(而且并不总能)明确的控制匹配什么和不匹配什么
3 智能化的结果——虽然前两者都能提供了非常灵活的搜索,但它们都不能提供非常智能化的匹配结果。
前面的很多限制都可以使用全文本搜索来解决。使用全文本搜索。Mysql创建指定列中各词的一个索引, 搜索可以针对这些词进行。
使用全文本搜索
启用全文本搜索
一般在创建表时启用全文本搜索。CREATE TABLE语句接受FULLTEXT子句。它给出被索引列的一个逗号分隔的列表。
CREATE TABLE productontes
(
note_id int NOT_NULL AUTO_INCREMENT,
prod_id char(10) NOT_NULL,
note_date datetime NOT_NULL,
note_text text NOT_NULL,
PRIMARY KEY(note_id),
FULLTEXT(note_text)
)ENGINE=MyISAM;
为了进行全文本搜索,mysql根据子句FULLTEXT(note_text)的指示对它进行索引。这里FUNLL_TEXT索引了单个列,如果需要可以索引多个列,多列之间用逗号分隔
在定义之后mysql对自动维护该索引,在增加、更新和删除行时,索引随之自动更新。
不要在导入数据时使用FUNLLTEXT,否则会耗费很多时间,应该先导入数据,再定义FULLTEXT这样有助于更快的导入数据
进行全文本搜索
在索引之后,使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列Against()指定要使用的搜索表达式。
SELECT note_text FROM productnotes WHERE Match(note_text) Against('rabbit');
使用完整的Match()说明: 传递给Match()的值必须和与FULLTEXT()定义中的相同。如果指定了多个列,则必须列出它们(而且次序正确)
除非使用BINARY方式,否则全文本搜索不区分大小写
使用SELECT 语句同样可以检索出两行,但次序不同。
全文本搜索返回一文本匹配的良好程度的数据。具有较高等级的行先返回。
使用查询扩展
查询扩展用来设法放宽所返回的全文本搜索结果的范围。想找出与搜索有关的其他行。不包含要搜索才字母。
如SELECT note_text FROM productnotes WHERE Match(note_text)Against('anvils');
返回一行数据
使用查询扩展
SELECT note_text FROM productnotes
WHERE Match(note_text) Against('anvils' WITH QUERY EXPANSION);
返回7行
布尔文本搜索
Mysql支持全文本搜索的另一种形式,称为布尔方式。布尔方式可以提供以下细节:
1 要匹配的词
2 要排斥的词(如果该行包含这个词,则不返回该行,即使它已经包含指定的词也是如此)
3 排列提示(指定某些词比其他词重要,更重要的词的词等级更高)
4 表达式分组
5 另外一些内容
即使没有FULLTEXT索引也可以使用 布尔方式不同迄今为止使用的全文本搜索语法的地方在于,即使没有定义FULLTEXT索引,也可以使用它,但这是一种非常缓慢的操作(其性能将随着数据量的增加而降低)
例如:匹配包含heavy但不包含任意以rope开始的词的行,可以使用以下查询:
SELECT note_text FROM productnotes
WHERE Match(note_text) Against('heavy -rope*' IN BOOLEAN MODE)
全文本布尔操作符
+ 包含,词必须存在
- 排除,词必须不出现
> 包含,而且增加等级值
< 包含,而且减少等级值
() 吧词组成子表达式(允许这些子表达式作为一个组被包含)
~ 取消一个词的排序值
* 词尾的通配符
"" 定义一个短语,(与单词不一样,它匹配整个短语以便包含或排除这个短语)
SELECT note_text FROM productnotes
WHERE Match(note_text) Against('+hea +rop' IN BOOLEAN MODE)
//包含hea 和rop的行
SELECT note_text FROM productnotes
WHERE Match(note_text) Against('hea rop' IN BOOLEAN MODE)
//包含hea 和rop至少一个就行的行
SELECT note_text FROM productnotes
WHERE Match(note_text) Against('"hea rop"' IN BOOLEAN MODE)
//包含hea rop这个短语而不是hea 和rop的行
SELECT note_text FROM productnotes
WHERE Match(note_text) Against('>hea <rop' IN BOOLEAN MODE)
//包含hea 和rop的行,增加前者的等级,降低后者的等级
SELECT note_text FROM productnotes
WHERE Match(note_text) Against('+hea +(<rop)' IN BOOLEAN MODE)
//包含hea 和rop的行降低后者的等级
全文本搜索使用说明:
1 在索引全文本数据时,短语被忽略且从索引中排除,短语定义为那些具有3个或3个以下字符的词,如果需要,这个数目可以更改
2 mysql内建一个非用词(stopword)列表,这些词在索引全文本数据时总是被忽略,如果需要,可以覆盖这个列表
3 许多词出现的频率高,搜索它们没有用处(返回太多的结果),因此mysql规定了一条50%的规则,如果一个词出现在50%以上的行中,则它将作为一个非用词忽略,50%规则不适用与IN BOOLEAN MODE
4 如果表中的行数少于3行,则全文本搜索不返回结果(因为每个词或者不出现,或者少于出现在50%的行中)
5 忽略词中的单引号,例如 don't 索引为 dont
6 不具有词分隔符(包含日语和汉语)的语言不能恰当地返回全文本搜索结果
7 如前所述,仅在MyISAM数据库引擎中是支持全文本搜索的