mysql笔记总结
一、数据库基础篇
主键的条件
- 任意两行都不具有相同的主键值;
- 每个行都必须具有一个主键值(主键列不允许NULL值)。
主键的好习惯
- 不更新主键列中的值;
- 不重用主键列的值;
- 不在主键列中使用可能会更改的值。(例如,如果使用一个
名字作为主键以标识某个供应商,当该供应商合并和更改其
名字时,必须更改这个主键)。
SHOW关键字的常用语句
- SHOW DATABASES;返回可用数据库的一个列表。
- SHOW TABLES;返回当前选择的数据库内可用表的列表。
- SHOW COLUMNS FROM TABLE用来显示表列,与DESCRIBE TABLE作用相同,MYSQL用DESCRIBE作为SHOW COLUMNS FROM 的一种快捷方式。
- SHOW STATUS,用于显示广泛的服务器状态信息;
- SHOW CREATE DATABASE和SHOW CREATE TABLE,分别用来显示创建特定数据库或表的MySQL语句;
- SHOW GRANTS,用来显示授予用户(所有用户或特定用户)的安全权限;
- SHOW ERRORS和SHOW WARNINGS,用来显示服务器错误或警告消息。
SELECT关键字的常用语句(检索数据)
使用SELECT检索表数据,必须至少给出两条信息——想选择什么,以及从什么地方选择。
(此小节以student学生表为例,假设学生表有学号id、姓名name和class班级三列。)来演示select关键字的使用。
- 检索单个列:select id from student;
- 检索多个列:select id,name from student;
- 检索所有列:select * from student;(一般,除非你确实需要表中的每个列,否则最好别使用*通配符。虽然使用通配符可能会使你自己省事,不用明确列出所需列,但检索不需要的列通常会降低检索和应用程序的性能。)
- 检索不同的行:select distinct class from student;只返回不同的class(班级)行,如果使用DISTINCT关键字,它必须直接放在列名的前面。
- 限制结果:select name from student limit 5;此语句使用SELECT语句检索单个列,LIMIT 5限制MySQL返回不多于5行。同时为得出下一个5行,可指定要检索的开始行和行数:select name from student limit 5,5;LIMIT 5, 5限制MySQL返回从行5开始的5行。第一个数为开始位置,第二个数为要检索的行数。(注意: 检索出来的第一行为行0而不是行1。因此,LIMIT 1, 1将检索出第二行而不是第一行。) MySQL 5支持LIMIT的另一种替代语法。LIMIT 4 OFFSET 3 意为从行3开始取4行,就像LIMIT 3, 4一样。
排序检索数据
排序数据
通过使用类似select name from student;语句检索出的数据并不是以纯粹的随机顺序显示的。如果不排序,数据一般将以它在底层表中出现的顺序显示。这可以是数据最初添加到表中的顺序。但是,如果数据后来进行过更新或删除,则此顺序将会受到MySQL重用回收存储空间的影响。因此,如果不明确控制的话,不能(也不应该)依赖该排序顺序。关系数据库设计理论认为,如果不明确规定排序顺序,则不应该假定检索出的数据的顺序有意义。
为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。ORDER BY子句取一个或多个列的名字,据此对输出进行排序。
例如select name from student order by name;这条语句指示MySQL对name列以字母顺序排序数据。
通过非选择列进行排序 通常,ORDER BY子句中使用的列将是为显示所选择的列。但是,实际上并不一定要这样,用非检索的列排序数据是完全合法的。
按多个列排序
为了按多个列排序,只要指定列名,列名之间用逗号分开即可(就像选择多个列时所做的那样)。
下面的代码检索3个列,并按其中两个列对结果进行排序——首先按价格,然后再按名称排序.
理解在按多个列排序时,排序完全按所规定的顺序进行。换句话说,对于上述例子中的输出,仅在多个行具有相同的prod_price值时才对产品按prod_name进行排序。如果prod_price列中所有的值都是唯一的,则不会按prod_name排序。
指定排序方向
数据排序不限于升序排序(从A到Z)。这只是默认的排序顺序,还可以使用ORDER BY子句以降序(从Z到A)顺序排序。为了进行降序排序,必须指定DESC关键字。
下面的例子按价格以降序排序产品(最贵的排在最前面):
但是,如果打算用多个列排序怎么办?下面的例子以降序排序产品(最贵的在最前面),然后再对产品名排序:
DESC关键字只应用到直接位于其前面的列名。在上例中,只对prod_price列指定DESC,对prod_name列不指定。因此,prod_price列以降序排序,而prod_name列(在每个价格内)仍然按标准的升序排序。
与DESC相反的关键字是ASC(ASCENDING),在升序排序时可以指定它。但实际上,ASC没有多大用处,因为升序是默认的(如果既不指定ASC也不指定DESC,则假定为ASC)。
过滤数据
使用where子句
在SELECT语句中,数据根据WHERE子句中指定的搜索条件进行过滤。WHERE子句在表名(FROM子句)之后给出,如下所示:
这条语句从products表中检索两个列,但不返回所有行,只返回prod_price值为2.50的行。
WHERE子句操作符
需要注意的是,<>和!=均不包括值为null的字段。
特殊说明,BETWEEN操作符其语法与其他WHERE子句的操作符稍有不同,因为它需要两个值,即范围的开始值和结束值。下面的例子说明如何使用BETWEEN操作符,它检索价格在5美元和10美元之间的所有产品:
空值检查,SELECT语句有一个特殊的WHERE子句,可用来检查具有NULL值的列。这个WHERE子句就是IS NULL子句。其语法如下:
数据过滤
前面介绍的所有WHERE子句在过滤数据时使用的都是单一的条件。为了进行更强的过滤控制,MySQL允许给出多个WHERE子句。这些子句可以两种方式使用:以AND子句的方式或OR子句的方式使用。
组合WHERE子句
AND操作符
为了通过不止一个列进行过滤,可使用AND操作符给WHERE子句附加条件。下面的代码给出了一个例子:
此SQL语句检索由供应商1003制造且价格小于等于10美元的所有产品的名称和价格。上述例子中使用了只包含一个关键字AND的语句,把两个过滤条件组合在一起。还可以添加多个过滤条件,每添加一条就要使用一个AND。
OR操作符
OR操作符与AND操作符不同,它指示MySQL检索匹配任一条件的行。
此SQL语句检索由两个中的任一个指定供应商制造的所有产品的产品名和价格。
计算次序
WHERE可包含任意数目的AND和OR操作符。允许两者结合以进行复杂和高级的过滤。
但是SQL(像多数语言一样)在处理OR操作符前,优先处理AND操作符。即AND在计算次序中优先级更高,于是在进行AND和OR的组合过滤时应使用圆括号明确地分组相应的操作符。示例如下:
这样才能够优先过滤OR操作符的连接语句。
IN操作符
圆括号在WHERE子句中还有另外一种用法。IN操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN取合法值的由逗号分隔的清单,全都括在圆括号中。下面的例子说明了这个操作符:
此SELECT语句检索供应商1002和1003制造的所有产品。IN操作符后跟由逗号分隔的合法值清单,整个清单必须括在圆括号中。
其实IN操作符完成与OR相同的功能,只不过其具有以下优点:
- 在使用长的合法选项清单时,IN操作符的语法更清楚且更直观。
- 在使用IN时,计算的次序更容易管理(因为使用的操作符更少)。
- IN操作符一般比OR操作符清单执行更快。
- IN的最大优点是可以包含其他SELECT语句,使得能够更动态地建立WHERE子句。在后面将会对此进行详细介绍。
NOT操作符
WHERE子句中的NOT操作符有且只有一个功能,那就是否定它之后所跟的任何条件。MySQL中的NOT MySQL支持使用NOT对IN、BETWEEN和EXISTS子句取反,这与多数其他DBMS允许使用NOT对各种条件取反有很大的差别。
使用通配符进行过滤
通配符(wildcard) 用来匹配值的一部分的特殊字符。
为在搜索子句中使用通配符,必须使用LIKE操作符。LIKE指示MySQL,后跟的搜索模式利用通配符匹配而不是直接相等匹配进行比较。
百分号(%)通配符
最常使用的通配符是百分号(%)。在搜索串中,%表示任何字符出现任意次数。例如,为了找出所有以词jet起头的产品,可使用以下SELECT语句:
此例子使用了搜索模式’jet%’。在执行这条子句时,将检索任意以jet起头的词。
通配符可在搜索模式中任意位置使用,并且可以使用多个通配符。下面的例子使用两个通配符,它们位于模式的两端:
搜索模式’%anvil%'表示匹配任何位置包含文本anvil的值,而不论它之前或之后出现什么字符。需要注意的是,除了一个或多个字符外,%还能匹配0个字符。%代表搜索模式中给定位置的0个、1个或多个字符。
虽然似乎%通配符可以匹配任何东西,但有一个例外,即NULL。即使是WHERE prod_name LIKE '%'也不能匹配用值NULL作为产品名的行。
下划线(_)通配符
另一个有用的通配符是下划线(_)。下划线的用途与%一样,但下划线只匹配单个字符而不是多个字符。
使用通配符的技巧
正如所见,MySQL的通配符很有用。但这种功能是有代价的:通配符搜索的处理一般要比前面讨论的其他搜索所花时间更长。这里给出一些使用通配符要记住的技巧。
- 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
- 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
- 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
用正则表达式进行搜索
基本字符匹配
使用正则表达式进行搜索时,需要在WHERE子句中使用REGEXP关键字。
- "."是正则表达式语言中一个特殊的字符,它表示匹配任意一个字符。
匹配不区分大小写 MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大小写,可使用BINARY关键字,如WHERE prod_name REGEXP BINARY ‘JetPack .000’。
进行OR匹配
为搜索两个串之一(或者为这个串,或者为另一个串),使用 |,如下所示:
语句中使用了正则表达式1000|2000。|为正则表达式的OR操作符。它表示匹配其中之一。
匹配几个字符之一
匹配任何单一字符。但是,如果你只想匹配特定的字符,怎么办?可通过指定一组用[和]括起来的字符来完成,如下所示:
这里,使用了正则表达式[123] Ton。[123]定义一组字符,它的意思是匹配1或2或3。
字符集合也可以被否定,即,它们将匹配除指定字符外的任何东西。为否定一个字符集,在集合的开始处放置一个^即可。因此,尽管[123]匹配字符1、2或3,但[^123]却匹配除这些字符外的任何东西。
匹配范围
集合可用来定义要匹配的一个或多个字符。例如:[0-9]匹配数字0-9,[a-z]匹配任意字母字符。
匹配特殊字符
为了匹配特殊字符,必须用\为前导。\-表示查找-,\.表示查找.。例如,要找出包含.字符的值:
这种处理就是所谓的转义(escaping),正则表达式内具有特殊意义的所有字符都必须以这种方式转义。这包括.、|、[]以及迄今为止使用过的其他特殊字符。
\\也用来引用元字符(具有特殊含义的字符),如图所列
匹配字符类
为更方便工作,可以使用预定义的字符集,称为字符类(character class)。下图列出字符类以及它们的含义。
匹配多个实例
目前为止使用的所有正则表达式都试图匹配单次出现,但有时需要对匹配的数目进行更强的控制。例如,你可能需要寻找所有的数,不管数中包含多少数字,或者你可能想寻找一个单词并且还能够适应一个尾随的s(如果存在),等等。这可以用下图列出的正则表达式重复元字符来完成。
示例:
结果:
正则表达式\\([0-9] sticks?\\)需要解说一下。\\(匹配(,[0-9]匹配任意数字,sticks?匹配stick和sticks(s后的?使s可选,因为?匹配它前面的任何字符的0次或1次出现),\\)匹配)。如果没有?,匹配stick和sticks会非常困难。
定位符
目前为止的所有例子都是匹配一个串中任意位置的文本。为了匹配特定位置的文本,需要使用下图列出的定位符。
例如,如果你想找出以一个数(包括以小数点开始的数)开始的所有产品,怎么办?简单搜索[0-9\\.](或[[:digit:]\\.])不行,因为它将在文本内任意位置查找匹配。解决办法是使用^定位符,如下所示:
结果:
^匹配串的开始。因此,^[0-9\\.]只在.或任意数字为串中第一个字符时才匹配它们。
^的双重用途 ^有两种用法。在集合中即之前的例子[^123],用它来表示否定该集合,而在集合外,即上例所示^[0-9\\.]则用来表示串的开始处。
创建计算字段
计算字段
在实际应用中,存储在数据库表中的数据一般不是应用程序所直接需要的格式。下面举几个例子。
- 如果想在一个字段中既显示公司名,又显示公司的地址,但这两个信息一般包含在不同的表列中。
- 城市、州和邮政编码存储在不同的列中(应该这样),但邮件标签打印程序却需要把它们作为一个恰当格式的字段检索出来。
- 列数据是大小写混合的,但报表程序需要把所有数据按大写表示出来。
- 物品订单表存储物品的价格和数量,但不需要存储每个物品的总价格(用价格乘以数量即可)。为打印发票,需要物品的总价格。
- 需要根据表数据进行总数、平均数计算或其他计算。
在上述每个例子中,存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。
这就是计算字段发挥作用的所在了。与前面介绍过的列不同,计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句内创建的。
客户机与服务器的格式 可在SQL语句内完成的许多转换和格式化工作都可以直接在客户机应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户机中完
成要快得多,因为DBMS是设计来快速有效地完成这种处理的。
拼接字段
拼接(concatenate) 将值联结到一起构成单个值。
在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。
输出:
Concat()需要一个或多个指定的串,各个串之间用逗号分隔。上面的SELECT语句连接以下4个元素:vend_name,(,vend_country,)。即按照name(location)这样的格式输出计算后的字段。
Trim函数 RTrim()去掉串右边的空格,LTrim() 去掉串左边的空格,以及Trim()去掉串左右两边的空格。
使用别名
从前面的输出中可以看到,SELECT语句拼接地址字段工作得很好。但此新计算列的名字是什么呢?实际上它没有名字,它只是一个值。如果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户机应用中,因为客户机没有办法引用它。
为了解决这个问题,SQL支持列别名。别名(alias)是一个字段或值的替换名。别名用AS关键字赋予。请看下面的SELECT语句:
输出:
SELECT语句本身与以前使用的相同,只不过这里的语句中计算字段之后跟了文本AS vend_title。它指示SQL创建一个包含指定计算的名为vend_title的计算字段。从输出中可以看到,结果与以前的相同,但现在列名为vend_title,任何客户机应用都可以按名引用这个列,就像它是一个实际的表列一样。别名有时也称为导出列(derived column),不管称为什么,它们所代表的都是相同的东西。
执行算术计算
计算字段的另一常见用途是对检索出的数据进行算术计算。举一个例子,orders表包含收到的所有订单,orderitems表包含每个订单中的各项物品。下面的SQL语句检索订单号20005中的所有物品,并汇总物品的价格(单价乘以订购数量):
输出:
输出中显示的expanded_price列为一个计算字段,此计算为quantity*item_price。客户机应用现在可以使用这个新计算列,就像使用其他列一样。
MySQL支持下表中列出的基本算术操作符。此外,圆括号可用来区分优先顺序。
使用数据处理函数
函数
与其他大多数计算机语言一样,SQL支持利用函数来处理数据。函数一般是在数据上执行的,它给数据的转换和处理提供了方便。
使用函数
大多数SQL实现支持以下类型的函数。
- 用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数。
- 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
- 用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。
- 返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。
文本处理函数
常用的文本处理函数:
函数 | 说明 |
---|---|
Left() | 返回串左边的字符 |
Length() | 返回串的长度 |
Locate() | 找出串的一个子串 |
Lower() | 将串转换为小写 |
LTrim() | 去掉串左边的空格 |
Right() | 返回串右边的字符 |
RTrim() | 去掉串右边的空格 |
Soundex() | 返回串的SOUNDEX值 |
SubString() | 返回子串的字符 |
Upper() | 将串转换为大写 |
其中需要特殊说明的是,SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。SOUNDEX考虑了类似的发音字符和音节,使得能对串进行发音比较而不是字母比较。
日期和时间处理函数
日期和时间采用相应的数据类型和特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。
下图列出了某些常用的日期和时间处理函数。
数值处理函数
数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此没有串或日期—时间处理函数的使用那么频繁。下表列出一些常用的数值处理函数。
汇 总 数 据
聚集函数
我们经常需要汇总数据而不用把它们实际检索出来,为此MySQL提供了专门的函数。使用这些函数,MySQL查询可用于检索数据,以便分析和报表生成。这种类型的检索例子有以下几种。
- 确定表中行数(或者满足某个条件或包含某个特定值的行数)。
- 获得表中行组的和。
- 找出表列(或所有行或某些特定的行)的最大值、最小值和平均值。
上述例子都需要对表中数据(而不是实际数据本身)汇总。因此,返回实际表数据是对时间和处理资源的一种浪费(更不用说带宽了)。重复一遍,实际想要的是汇总信息。
为方便这种类型的检索,MySQL给出了5个聚集函数,见下表。这些函数能进行上述罗列的检索。
聚集函数(aggregate function) 运行在行组上,计算和返回单个值的函数。
组合聚集函数
SELECT语句可根据需要包含多个聚集函数。请看下面的例子:
输出:
分组数据
数据分组
分组允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。
创建分组
分组是在SELECT语句的GROUP BY子句中建立的。理解分组的最好办法是看一个例子:
输出:
上面的SELECT语句指定了两个列,vend_id包含产品供应商的ID,num_prods为计算字段(用COUNT(*)函数建立)。GROUP BY子句指示MySQL按vend_id排序并分组数据。这导致对每个vend_id而不是整个表计算num_prods一次。
在具体使用GROUP BY子句前,需要知道一些重要的规定。
- GROUP BY子句可以包含任意数目的列。这使得能对分组进行嵌套,为数据分组提供更细致的控制。
- 如果在GROUP BY子句中嵌套了分组,数据将在最后规定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。
- GROUP BY子句中列出的每个列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY子句中指定相同的表达式。不能使用别名。
- 除聚集计算语句外,SELECT语句中的每个列都必须在GROUP BY子
句中给出。 - 如果分组列中具有NULL值,则NULL将作为一个分组返回。如果列
中有多行NULL值,它们将分为一组。 - GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前。
过滤分组
除了能用GROUP BY分组数据外,MySQL还允许过滤分组,规定包括哪些分组,排除哪些分组。例如,可能想要列出至少有两个订单的所有顾客。为得出这种数据,必须基于完整的分组而不是个别的行进行过滤。
在这个例子中WHERE不能完成任务,因为WHERE过滤指定的是行而不是分组。为此MYSQL提供了另外的子句,那就是HAVING子句。HAVING非常类似于WHERE。事实上,目前为止所学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是WHERE过滤行,而HAVING过滤分组。具体如下示例:
输出:
在上面的示例中,最后一行增加了HAVING子句,它过滤COUNT(*) >=2(两个以上的订单)的那些分组。
HAVING和WHERE的差别: 这里有另一种理解方法,WHERE在数据分组前进行过滤,HAVING在数据分组后进行过滤。这是一个重要的区别,WHERE排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING子句中基于这些值过滤掉的分组。
分组和排序
虽然GROUP BY和ORDER BY经常完成相同的工作,但它们是非常不同的。下表汇总了它们之间的差别。
不要忘记ORDER BY 一般在使用GROUP BY子句时,应该也给出ORDER BY子句。这是保证数据正确排序的唯一方法。千万不要仅依赖GROUP BY排序数据。
SELECT子句顺序
下面回顾一下SELECT语句中子句的顺序。下表以在SELECT语句中使用时必须遵循的次序,列出迄今为止所学过的子句。
子 句 | 说明 | 是否必须使用 |
---|---|---|
SELECT | 要返回的列或表达式 | 是 |
FROM | 从中检索数据的表 | 仅在从表选择数据时使用 |
WHERE | 行级过滤 | 否 |
GROUP BY | 分组说明 | 仅在按组计算聚集时使用 |
HAVING | 组级过滤 | 否 |
ORDER BY | 输出排序顺序 | 否 |
LIMIT | 要检索的行数 | 否 |
使用子查询
利用子查询进行过滤
子查询(subquery),即嵌套在其他查询中的查询。示例:
输出:
为了执行上述SELECT语句,MySQL实际上必须执行3条SELECT语句。最里边的子查询返回订单号列表,此列表用于其外面的子查询的WHERE子句。外面的子查询返回客户ID列表,此客户ID列表用于最外层查询的WHERE子句。最外层查询确实返回所需的数据。
列必须匹配 在WHERE子句中使用子查询(如这里所示),应该保证SELECT语句具有与WHERE子句中相同数目的列。通常,子查询将返回单个列并且与单个列匹配,但如果需要也可以使用多个列。
作为计算字段使用子查询
使用子查询的另一方法是创建计算字段。假如需要显示customers表中每个客户的订单总数。订单与相应的客户ID存储在orders表中。
为了执行这个操作,遵循下面的步骤。
(1) 从customers表中检索客户列表。
(2) 对于检索出的每个客户,统计其在orders表中的订单数目。
为了对每个客户执行COUNT(*)计算,应该将COUNT(*)作为一个子查询。请看下面的代码:
输出:
该子查询对检索出的每个客户执行一次。在此例子中,该子查询执行了5次,因为检索出了5个客户。
联 结 表
创建联结
联结的创建非常简单,规定要联结的所有表以及它们如何关联即可。请看下面的例子:
这里vend_name在vendors表中,prod_name和prod_price在products表中,所以想要通过一个select查询出两个表中的数据便需要使用联结,这里使用WHERE子句正确联结,WHERE子句指示MySQL匹配vendors表中的vend_id和products表中的vend_id。
WHERE子句的重要性
在联结两个表时,你实际上做的是将第一个表中的每一行与第二个表中的每一行配对。WHERE子句作为过滤条件,它只包含那些匹配给定条件(这里是联结条件)的行。没有WHERE子句,第一个表中的每个行将与第二个表中的每个行配对,而不管它们逻辑上是否可以配在一起。
笛卡儿积(cartesian product): 由没有联结条件的表关系返回的结果为笛卡儿积。检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
内部联结
目前为止所用的联结称为等值联结(equijoin),它基于两个表之间的相等测试。这种联结也称为内部联结。
其实,对于这种联结可以使用稍微不同的语法来明确指定联结的类型。下面的SELECT语句返回与前面例子完全相同的数据:
此语句中的SELECT与前面的SELECT语句相同,但FROM子句不同。这里,两个表之间的关系是FROM子句的组成部分,以INNER JOIN指定。在使用这种语法时,联结条件用特定的ON子句而不是WHERE子句给出。传递给ON的实际条件与传递给WHERE的相同。
联结多个表
SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系。例如:
输出:
这里的FROM子句列出了3个表,而WHERE子句定义了这两个联结条件,而第三个联结条件用来过滤出订单20005中的物品。
创建高级联结
使用表别名
别名除了用于列名和计算字段外,SQL还允许给表名起别名。这样做有两个主要理由:
- 缩短SQL语句;
- 允许在单条SELECT语句中多次使用相同的表。
例如:
在此例子中,表别名只用于WHERE子句。但是,表别名不仅能用于WHERE子句,它还可以用于SELECT的列表、ORDER BY子句以及语句的其他部分。应该注意,表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机。
使用不同类型的联结
迄今为止,我们使用的只是称为内部联结或等值联结(equijoin)的简单联结。现在来看2种其他联结,它们分别是自联结和外部联结。
自联结
如前所述,使用表别名的主要原因之一是能在单条SELECT语句中不止一次引用相同的表。
假如你发现某物品(其ID为DTNTR)存在问题,因此想知道生产该物品的供应商生产的其他物品是否也存在这些问题。便可以通过自联结进行查询:
输出:
此查询中需要的两个表实际上是相同的表,因此products表在FROM子句中出现了两次。WHERE(通过匹配p1中 的vend_id和p2中的vend_id)首先联结两个表,然后按第二个表中的prod_id过滤数据,返回所需的数据。
自联结的技巧就是将一张表看成两张表。
外部联结
联结包含了那些在相关表中没有关联行的行。这种类型的联结称为外部联结。
例如,下面的SELECT语句给出一个简单的内部联结。它检索所有客户及其订单:
此时通过内联结查询出的结果不包含没有订单的客户,而通过外部联结如下所示:
输出:
此时的结果包含了没有订单的客户,即通过使用LEFT OUTER JOIN从FROM子句的左边表(customers表)中选择所有行全部查询出来。
LEFT表示将JOIN关键字左边的这张表看成主表,主要是为了将这张表的数据全部查询出来,捎带着关联查询右边的表。反之RIGHT亦然。在外联结当中,两张表联结,产生了主次关系。而在内联结中是不存在主次关系的,表与表之间是平等的
任何一个左联结都有右联结的写法,任何一个右联结也都有左联结的写法。
组 合 查 询
多数SQL查询都只包含从一个或多个表中返回数据的单条SELECT语句。MySQL也允许执行多个查询(多条SELECT语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
有两种基本情况,其中需要使用组合查询:
- 在单个查询中从不同的表返回类似结构的数据;
- 对单个表执行多个查询,按单个查询返回数据。
创建组合查询
可用UNION操作符来组合数条SQL查询。利用UNION,可给出多条SELECT语句,将它们的结果组合成单个结果集。
UNION的使用很简单。所需做的只是给出每条SELECT语句,在各条语句之间放上关键字UNION。举一个例子,假如需要价格小于等于5的所有物品的一个列表,而且还想包括供应商1001和1002生产的所有物品(不考虑价格)。当然,可以利用WHERE子句来完成此工作,不过这里我们将使用UNION关键字进行演示。
输出:
这条语句由前面的两条SELECT语句组成,语句中用UNION关键字分隔。UNION指示MySQL执行两条SELECT语句,并把输出组合成单个查询结果集。
UNION规则
正如所见,并是非常容易使用的。但在进行并时有几条规则需要注意。
- UNION必须由两条或两条以上的SELECT语句组成,语句之间用关键字UNION分隔(因此,如果组合4条SELECT语句,将要使用3个UNION关键字)。
- UNION中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
- 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。
全文本搜索
MySQL支持几种基本的数据库引擎。但并非所有的引擎都支持全文本搜索。两个最常使用的引擎为MyISAM和InnoDB,前者支持全文本搜索,而后者不支持。
在使用全文本搜索时,MySQL不需要分别查看每个行,不需要分别分析和处理每个词。MySQL创建指定列中各词的一个索引,搜索可以针对这些词进行 (mysql在查询方面主要就是两种方式:全表扫描和根据索引检索)。这样,MySQL可以快速有效地决定哪些词匹配(哪些行包含它们),哪些词不匹配,它们匹配的频率,等等。
使用全文本搜索
为了进行全文本搜索,必须索引被搜索的列,而且要随着数据的改变不断地重新索引。在对表列进行适当设计后,MySQL会自动进行所有的索引和重新索引。在索引之后,SELECT可与Match()和Against()一起使用以实际执行搜索。
启用全文本搜索支持
一般在创建表时启用全文本搜索。CREATE TABLE语句接受FULLTEXT子句,它给出被索引列的一个逗号分隔的列表。下面的CREATE语句演示了FULLTEXT子句的使用:
这些列中有一个名为note_text的列,为了进行全文本搜索,MySQL根据子句FULLTEXT(note_text)的指示对它进行索引。这里的FULLTEXT索引单个列,如果需要也可以指定多个列。
在定义之后,MySQL自动维护该索引。在增加、更新或删除行时,索引随之自动更新。
进行全文本搜索
在索引之后,使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列,Against()指定要使用的搜索表达式。例如:
输出:
此SELECT语句检索单个列note_text。由于WHERE子句,一个全文本搜索被执行。Match(note_text)指示MySQL针对指定的列进行搜索,Against(‘rabbit’)指定词rabbit作为搜索文本。由于有两行包含词rabbit,这两个行被返回。
使用完整的 Match() 传递给 Match() 的值必须与FULLTEXT()定义中的相同。如果指定多个列,则必须列出它们(而且次序正确)。
搜索不区分大小写 除非使用BINARY方式,否则全文本搜索不区分大小写。
其实上面的例子,可以通过使用LIKE子句完成,能够返回相同的两行结果。但是需要注意的是返回查询结果的次序不同,这便体现出了全文本搜索的特别之处。使用全文本搜索返回以文本匹配的良好程度排序的数据,而使用LIKE则是以不特别有用的顺序返回数据。
全文本搜索提供了简单LIKE搜索不能提供的功能。而且,由于数据是索引的,全文本搜索还相当快。
布尔文本搜索
MySQL支持全文本搜索的另外一种形式,称为布尔方式(boolean mode)。以布尔方式,可以提供关于如下内容的细节:
- 要匹配的词;
- 要排斥的词(如果某行包含这个词,则不返回该行,即使它包含其他指定的词也是如此);
- 排列提示(指定某些词比其他词更重要,更重要的词等级更高);
- 表达式分组;
- 另外一些内容。
即使没有FULLTEXT索引也可以使用 布尔方式不同于迄今为止使用的全文本搜索语法的地方在于,即使没有定义FULLTEXT索引,也可以使用它。但这是一种非常缓慢的操作(其性能将随着数据量的增加而降低)。
为演示IN BOOLEAN MODE的作用,下面通过两个例子进行对比演示:
输出:
此全文本搜索检索包含词heavy的所有行(有两行)。
为了匹配包含heavy但不包含任意以rope开始的词的行,可使用以下查询:
输出:
这次只返回一行。这一次仍然匹配词heavy,但-rope明确地指示MySQL排除包含rope(任何以rope开始的词,包括ropes)的行,这就是为什么上一个例子中的第一行被排除的原因。
我们已经看到了两个全文本搜索布尔操作符-和*,-排除一个词,而*是截断操作符(可想象为用于词尾的一个通配符)。下表列出支持的所有全文本布尔操作符。
插 入 数 据
INSERT是用来插入(或添加)行到数据库表的。插入可以用几种方式使用:
- 插入完整的行;
- 插入行的一部分;
- 插入多行;
- 插入某些查询的结果。
插入完整的行
把数据插入表中的最简单的方法是使用基本的INSERT语法,它要求指定表名和被插入到新行中的值。如下所示:
虽然这种语法很简单,但并不安全,应该尽量避免使用。其SQL语句高度依赖于表中列的定义次序,在实际项目开发中并不能保证下一次表结构变动后各个列保持完全相同的次序。因此,编写依赖于特定列次序的SQL语句是很不安全的。
编写INSERT语句的更安全(不过更烦琐)的方法如下:
INSERT INTO customers(cust_name,
cust_address,
cust_city,
cust_state,cust_zip,
cust_country,
cust_contact,
cust_emai1)
VALUES( 'Pep E. LaPew ’ ,'100 Main Street ’ ,'Los Angeles ’ ,‘CA’,‘90046’,‘USA’,NULL,
NULL);
此例子完成与前一个INSERT语句完全相同的工作,但在表名后的括号里明确地给出了列名。在插入行时,MySQL将用VALUES列表中的相应值填入列表中的对应项。
因为提供了列名,VALUES必须以其指定的次序匹配指定的列名,不一定按各个列出现在实际表中的次序。其优点是,即使表的结构改变,此INSERT语句仍然能正确工作。
插入多个行
插入多个行可以使用多条INSERT语句,然后一次提交它们,每条语句用一个分号结束,如下所示:
或者,只要每条INSERT语句中的列名(和次序)相同,可以如下组合各语句:
INSERT INTO customers(cust_name ,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country)
VALUES(
‘Pep E. LaPew’,‘100 Main Street’ ,'Los Angeles ',‘CA’,‘90046’,‘USA’),
('M. Martian ',‘42 Galaxy way ’ ,’ New York ’ ,'NY ',‘11213’,‘USA’) ;
其中单条INSERT语句有多组值,每组值用一对圆括号括起来,用逗号分隔。
插入检索出的数据
INSERT一般用来给表插入一个指定列值的行。但是,INSERT还存在另一种形式,可以利用它将一条SELECT语句的结果插入表中。这就是所谓的INSERT SELECT,顾名思义,它是由一条INSERT语句和一条SELECT语句组成的。
假如你想从另一表中合并客户列表到你的customers表,可以如下进行:
INSERT INTO customers(cust_id,
cust_contact,
cust_emai1,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,
cust_country)
SELECT cust_id,cust_contact,
cust_email,
cust_name,
cust_address,
cust_city,
cust_state,
cust_zip,cust_country
FROM custnew;
这个例子使用INSERT SELECT从custnew中将所有数据导入customers。
INSERT SELECT中的列名 为简单起见,这个例子在INSERT和SELECT语句中使用了相同的列名。但是,不一定要求列名匹配。事实上,MySQL甚至不关心SELECT返回的列名。它使用的是列的位置,因此SELECT中的第一列(不管其列名)将用来填充表列中指定的第一个列,第二列将用来填充表列中指定的第二个列,如此等等。这对于从使用不同列名的表中导入数据是非常有用的。
INSERT SELECT中SELECT语句可包含WHERE子句以过滤插入的数据。
更新和删除数据
更新数据
为了更新(修改)表中的数据,可使用UPDATE语句。可采用两种方式使用UPDATE:
- 更新表中特定行;
- 更新表中所有行。
UPDATE语句非常容易使用,甚至可以说是太容易使用了。基本的UPDATE语句由3部分组成,分别是:
- 要更新的表;
- 列名和它们的新值;
- 确定要更新行的过滤条件。
例如,更新客户10005的电子邮件地址:
UPDATE语句总是以要更新的表的名字开始。在此例子中,要更新的表的名字为customers。SET命令用来将新值赋给被更新的列。UPDATE语句以WHERE子句结束,它告诉MySQL更新哪一行。没有WHERE子句,MySQL将会用这个电子邮件地址更新customers表中所有行,这不是我们所希望的。
更新多个列的语法稍有不同:
在更新多个列时,只需要使用单个SET命令,每个“列=值”对之间用逗号分隔(最后一列之后不用逗号)。
不要省略WHERE子句 在使用UPDATE时一定要注意细心。因为稍不注意,就会更新表中所有行。
在UPDATE语句中使用子查询 UPDATE语句中可以使用子查询,使得能用SELECT语句检索出的数据更新列数据。
IGNORE关键字 如果用UPDATE语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个UPDATE操作被取消(错误发生前更新的所有行被恢复到它们原来的值)。为即使是发生错误,也继续进行更新,可使用IGNORE关键字,如下所示: UPDATE IGNORE customers…
删除数据
为了从一个表中删除(去掉)数据,使用DELETE语句。可以两种方式使用DELETE:
- 从表中删除特定的行;
- 从表中删除所有行。
下面的语句从customers表中删除一行:
DELETE FROM要求指定从中删除数据的表名。WHERE子句过滤要删除的行。
DELETE不需要列名或通配符。DELETE删除整行而不是删除列。为了删除指定的列,请使用UPDATE语句。
不要省略WHERE子句 在使用DELETE时一定要注意细心。因为稍不注意,就会错误地删除表中所有行。
删除表的内容而不是表 DELETE语句从表中删除行,甚至是删除表中所有行。但是,DELETE不删除表本身。
更快的删除 如果想从表中删除所有行,不要使用DELETE。可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更快(TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。
创建和操纵表
创建表
一般有两种创建表的方法:
- 使用具有交互式创建和管理表的工具(如第2章讨论的工具);
- 表也可以直接用MySQL语句操纵。
为利用CREATE TABLE创建表,必须给出下列信息:
- 新表的名字,在关键字CREATE TABLE之后给出;
- 表列的名字和定义,用逗号分隔。
例如下面是创建一个客户表的语句:
从上面的例子中可以看到,表名紧跟在CREATE TABLE关键字后面。实际的表定义(所有列)括在圆括号之中。各列之间用逗号分隔。
处理现有的表 在创建新表时,指定的表名必须不存在,否则将出错。如果要防止意外覆盖已有的表,SQL要求首先手工删除该表(后面将会介绍),然后再重建它,而不是简单地用创建表语句覆盖它。如果你仅想在一个表不存在时创建它,应该在表名后给出IF NOT EXISTS。这样做不检查已有表的模式是否与你打算创建的表模式相匹配。它只是查看表名是否存在,并且仅在表名不存在时创建它。
使用AUTO_INCREMENT
创建customers表的cust_id列语句如下所示:
AUTO_INCREMENT告诉MySQL,本列每当增加一行时自动增量。每次执行一个INSERT操作时,MySQL自动对该列增量(从而才有这个关键字AUTO_INCREMENT),给该列赋予下一个可用的值。这样给每个行分配一个唯一的cust_id,从而可以用作主键值。每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通过使它成为主键)。
覆盖AUTO_INCREMENT 如果一个列被指定为AUTO_INCREMENT,则它需要使用特殊的值吗?你可以简单地在INSERT语句中指定一个值,只要它是唯一的(至今尚未使用过)即可,该值将被用来替代自动生成的值。后续的增量将开始使用该手工插入的值。
确定AUTO_INCREMENT值 让MySQL生成(通过自动增量)主键的一个缺点是你不知道这些值都是谁。那么,如何在使用AUTO_INCREMENT列时获得这个值呢?可使用last_insert_id()函数获得这个值,如下所示:
SELECT last_insert_id()
此语句返回最后一个AUTO_INCREMENT值,然后可以将它用于后续的MySQL语句。
指定默认值
如果在插入行时没有给出值,MySQL允许指定此时使用的默认值。默认值用CREATE TABLE语句的列定义中的DEFAULT关键字指定。
如下所示,在创建表时,指定quantity列的默认值为1:
引擎类型
以下是几个需要知道的引擎:
- InnoDB是一个可靠的事务处理引擎,它不支持全文本搜索;
- MEMORY在功能等同于MyISAM,但由于数据存储在内存(不是磁盘)中,速度很快(特别适合于临时表);
- MyISAM是一个性能极高的引擎,它支持全文本搜索,但不支持事务处理。
外键不能跨引擎 引擎类型可以混用,但混用引擎类型有一个大缺陷。外键(用于强制实施引用完整性)不能跨引擎,即使用一个引擎的表不能引用具有使用不同引擎的表的外键。
更新表
为更新表定义,可使用ALTER TABLE语句。但是,理想状态下,当表中存储数据以后,该表就不应该再被更新。在表的设计过程中需要花费大量时间来考虑,以便后期不对该表进行大的改动。
为了使用ALTER TABLE更改表结构,必须给出下面的信息:
- 在ALTER TABLE之后给出要更改的表名(该表必须存在,否则将出错);
- 所做更改的列表。
例如,下面的例子给表增加一列:
删除刚刚添加的列,可以这样做:
ALTER TABLE的一种常见用途是定义外键,如下所示:
删除表
删除表(删除整个表而不是其内容)非常简单,使用DROP TABLE语句即可:
重命名表
使用RENAME TABLE语句可以重命名一个表:
使用下面的语句对多个表重命名:
二、数据库进阶篇
索引
什么是索引?
索引是在数据库表的字段上添加的,是为了提高查询效率存在的一种机制。
一张表的一个字段可以添加一个索引,当然,多个字段联合起来也可以添加索引。索引相当于一本书的目录,是为了缩小扫描范围而存在的一种机制。
mysql在查询方面主要就是两种方式:
- 全表扫描。
- 根据索引检索。
在mysql数据库当中索引也是需要排序的,并且这个索引的排序和Treeset数据结构和同。Treeset (TreeMap)底层是一个自平衡的二叉树!在mysql当中索引是一个B-Tree数据结构。
遵循左小右大原则存放。采用中序遍历方式遍历取数据。
索引的实现原理
扩展1: 在任何数据库当中主键上都会自动添加索引对象,另外在mysql当中一个字段上如果有unique约束的话,也会自动创建索引对象。
扩展2: 在任何数据库当中,任何一张表的任何一条记录在硬盘存储上都有一个硬盘的物理存储编号。
扩展3: 在mysql当中,索引是一个单独的对象,不同的存储引擎以不同的形式存在,在MyISAM存储引擎中,索引存储在一个.MYI文件中。在InnoDB存储引擎中索引存储在一个逻辑名称叫做tablespace的当中。在MEMORY存储引擎当中索引被存储在内存当中。不管索引存储在哪里,索引在mysql当中都是一个树的形式存在。(自平衡二叉树:B-Tree)
则主键索引通过B-Tree的形式进行存储,具体原理如下:
可以看出此时只需要两次查找便可以找到id为101的索引,然后通过索引对应的硬盘地址直接找到相应的数据。
什么条件下,我们会考虑给字段添加索引呢?
- 数据量庞大(到底有多么庞大算庞大,这个需要测试,因为每一个硬件环境不同)
- 该字段经常出现在where的后面,以条件的形式存在,也就是说这个字段总是被扫描。
- 该字段很少的DML(insert delete update)操作。(因为DML之后,索引需要重新排序。
建议不要随意添加索引,因为索引也是需要维护的,太多的话反而会降低系统的性能。
建议通过主键查询,通过unique约束的字段进行查询,效率是比较高的。
索引的创建与删除
在mysql当中,怎么查看一个SQL语句是否使用了索引进行检索?
通过使用EXPLAIN关键字:
如果type=ref则说明使用了索引。
索引的失效
索引有失效的时候,什么时候索引失效呢?
失效的第1种情况:
例如:select *from emp where ename like ‘%T’;
这样ename 上即使添加了索引,也不会走索引,为什么?
原因是因为模糊匹配当中以"%“开头了,没有办法去索引了。
所以应尽量避免模糊查询的时候以”%"开始。这是一种优化的手段/策略。
失效的第2种情况:
使用or的时候会失效,如果使用or那么要求or两边的条件字段都要有索引,才会走索引,如果其中一边没有索引,那么另一个字段上的索引也会失效。所以这就是为什么不建议使用or的原因。像这种情况便可以使用前面所介绍的UNION关键字进行连接两个条件的查询,这样拥有索引的字段便不会失效。
失效的第3种情况:
失效的第4种情况:
失效的第5种情况:
当然还有很多索引失效的情况,目前先了解这么多,如果日后在实际开发中遇到了再另做总结。
索引的分类
- 单一索引:一个字段上添加索引。
- 复合索引:两个字段或者更多的字段上添加索引。
- 主键索引:主键上添加索引(主键约束,就是一个主键索引)。
- 唯一性索引:具有unique约束的字段上添加索引。
- 全文索引:全文索引,只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引。(关于全文索引已经在本篇前面进行了介绍)
……
注意:唯一性比较弱的字段上添加索引用处不大。
视图
视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
使用视图
视图的主要作用(方便,简化开发,利于维护)
重要的是知道视图仅仅是用来查看存储在别处的数据的一种设施。视图本身不包含数据,因此它们返回的数据是从其他表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据。而如果对视图进行增删改的话,其对应的表中的数据也会被修改。
视图的一些常见应用:
视图的规则和限制:
事务
事务处理(transaction processing)可以用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行成功,要么完全不执行。
一个事务其实就是一个完整的业务逻辑,是一个最小的工作单元,要么同时成功,要么同时失败,不可再分。
只有DML语句才会有事务这一说,其它语句和事务无关!!!
insert、delete、update
只有以上的三个语句和事务有关系,其它都没有关系。因为只有以上的三个语句是数据库表中数据进行增、删、改的。只要你的操作一旦涉及到数据的增、删、改,那么就一定要考虑安全问题。数据安全第一位!
事务控制的实现原理
InnoDB存储引擎:提供一组用来记录事务性活动的日志文件。
在事务的执行过程中,每一条DML的操作都会记录到"事务性活动的日志文件"中。在事务的执行过程中,我们可以提交事务,也可以回滚事务。
提交事务:
清空事务性活动的日志文件,将数据全部彻底持久化到数据库表中。提交事务标志着,事务的结束。并且是一种全部成功的结束。
回滚事务:
将之前所有的DML操作全部撤销,并且清空事务性活动的日志文件,回滚事务标志着,事务的结束。并且是一种全部失败的结束。
事务提交与事务回滚
提交事务:commit;语句
回滚事务:rollback;语句(回滚永远都是只能回滚到上一次的提交点!)
事务对应的英文单词:transaction
mysql在默认情况下支持自动提交事务,即每执行一条DML语句便提交一次。
通过start transaction;命令开启事务,即可关闭mysql的自动提交机制。
事务的4个特性
隔离性说明
事务和事务之间的隔离级别有以下四个:
- 读未提交: read uncommitted(最低隔离级别)《没有提交就读到了》
事务A可以读取到事务B未提交的数据。
这种隔离级别存在的问题就是:脏读现象(Dirty Read)
我们称读到了脏数据。
这种隔离级别一般都是理论上的,大多数的数据库隔离级别都是二档起步! - 读已提交:read committed《提交之后才能读到》
事务A只能读取到事务B提交之后的数据。
这种隔离级别解决了脏读的现象。
但是这种隔离级别存在不可重复读取数据的问题。即在事务开启后,第一次读取的数据可能和第二次读取的数据不相等,这种现象称为不可重复读取。 - 可重复读:repeatable read《提交之后也读不到,永远读取的都是刚开启事务时的数据》
某一事务在某一时刻开启之后,不管是多久,每一次在事务中读取到的数据都是一致的(即永远读取的都是事务开始时的数据)。即使在这期间有另一事务将数据已经修改,并且提交了,当前事务读取到的数据还是没有发生改变,这就是可重复读。
它解决了不可重复读取数据的问题。
但是可能存在幻读的问题。 - 序列化/串行化: serializable(最高隔离级别)
这是最高隔离级别,效率最低。解决了所有的问题。这种隔离级别表示事务排队,不能并发!
游标
主要作用:可以将查询出来的数据,进行自定义显示数量。主要用于存储过程(函数)中。
触发器
主要用于对DML(insert、updata、delete)语句进行响应。