mysql基本操作
4、SQL查询语句
SELECT * FROM trade; // 从trade中查询所有信息
SELECT trade.driver_id FROM bcos.trade; // 使用完全限定的列名和表名,bcos为数据库的名称
SELECT COUNT(*) FROM trade; // 统计trade表中的数据数量
SELECT SUM(id) FROM trade; // 对trade中的id进行求和
SELECT order_id, driver_id FROM trade; // 从trade中查询trade_id和driver_id两个列
SELECT DISTINCT order_id FROM trade; // 从trade中查询没有重复的trade_id,DISTINCT必须放在列名的前面
SELECT order_id FROM trade LIMIT 3; // 从trade中查询trade_id并返回前3行数据(不多于3行)
SELECT order_id FROM trade LIMIT 2, 3;// 查询从第三行开始的前3行数据
SELECT order_id FROM trade LIMIT 3 OFFSET 2; // 等价上面的
5、排序检索数据
SELECT driver_id FROM trade ORDER BY id; // 从trade表中查询driver_id,并根据id进行排序
SELECT driver_id, order_id FROM trade ORDER BY id, driver_id; // 检索driver_id、order_id并先按照id进行排序,然后再按照(id相同)driver_id进行排序
// 从trade表中查询driver_id,并根据id进行降序排列,升序ASC是默认的,即不指定DESC就是默认的
// 升序排列。通常使用DESC和LIMIT的组合能够找出列中最高或最低的值
SELECT driver_id FROM trade ORDER BY id DESC;
6、过滤数据
WHERE子句操作符:
<> 不等于
BETWEEN 在指定的两个值之间
// 将值与串型值进行比较时需要限定引号
SELECT driver_id FROM trade WHERE order_id = 11111; // 查找order_id为11111的driver_id,在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后,否则将会产生错误
SELECT driver_id FROM trade WHERE order_id BETWEEN 11111 AND 55555; // 查询driver_id在11111和55555之间的所有值,11111和55555包括在内
SELECT driver_name FROM trade WHERE driver_id IS NULL; // 从trade表中查找driver_id为空的driver_name,而不是查找driver_id为0的
7、数据过滤
SELECT * FROM trade WHERE order_id >= 12134 AND driver_id <= 12345; // 在trade表中查找满足order_id>= 12134并且driver_id<=12345的所有信息
SELECT * FROM trade WHERE order_id >= 12134 OR driver_id <= 12345; // 在trade表中查找满足order_id>= 12134或者driver_id<=12345的所有信息
// WHERE允许任意数目的AND和OR操作符的组合查询,但是AND的优先级是高于OR的,所以在使用时需要加括号对OR操作符进行优先级的限定
// IN的语句里面是不能使用LIMIT的
SELECT * FROM trade WHERE order_id IN(12134, 12345); // 与下面的OR是等价的
SELECT * FROM trade WHERE order_id = 12134 OR driver_id = 12345; // 在trade表中查找满足order_id = 12134或者driver_id = 12345的信息
SELECT * FROM trade WHERE order_id NOT IN(12134, 12345); // 在trade表中查找除了order_id = 12134、driver_id = 12345以外的所有信息
8、使用通配符进行过滤
上述介绍的所有操作符都是针对已知值进行过滤的,可以使用通配符可搜索一些包含特定文本的数据。
LIKE操作符:为了在搜索子句中使用通配符,必须使用LIKE操作符。
// 最常使用的通配符是百分号(%),在搜索串中,%表示任何字符出现任意次数,0、1或多次。下划线 _ 的用途和%一样,但是下划线只能匹配一个字符。
SELECT * FROM trade WHERE driver_name LIKE "du%"; // 从trade中查找所有trade_name以du开头的数据,%告诉MySQL接受du之后的任意字符,不管它有多少字符
SELECT * FROM trade WHERE driver_name LIKE "%xin%"; // 从trade中查找所有trade_name含有du的数据
通配符使用技巧:MySQL的通配符是很有用的,但是这种功能是有代价的:通配符搜索的处理一般要比之前普通操作符的搜索时间更长。
9、用正则表达式进行搜索
基本字符匹配:REGEXP后所跟的东西作为正则表达式处理
SELECT * FROM trade WHERE driver_id REGEXP '10000' ORDER BY id; // 与文字正文10000匹配,类似于LIKE,但是LIKE匹配的是整个串,而REGEXP匹配子串。
SELECT * FROM trade WHERE driver_id REGEXP '10000|20000' ORDER BY id; // | 为正则表达式的OR操作符,表示匹配其中之一,10000和20000都匹配并返回,可给出两个以上的OR条件
SELECT * FROM trade WHERE driver_id REGEXP '[123] TON' // [123]定义一组字符,意思是匹配1或2或3,所以1 ton、2 ton都会被匹配到;也可对字符集进行否定,[^123]意为匹配除了1、2、3外的任何东西;此外[0-9]就是集合[0123456789]的简化,[a-z]表示匹配任意字母字符;
特殊字符匹配:. 匹配任意字符,为了匹配特殊字符需要使用\ \作为前导,\ \ . 表示查找. ,\ \ - 表示查找- 。这种处理就是所谓的转义。此外 \ \ 也用来引用元字符(具有特殊含义的字符)。
大多数正则表达式实现使用的都是单个反斜杠转义特殊字符,但是MySQL要求使用的是两个反斜杠,因为MySQL自己解释一个,正则表达式库需要解释一个。
匹配字符类:为了方便工作,可以使用预定义的字符集,称为字符类,如下所示:
[:alnum:] 任意字母和数字,等价于[a-zA-Z0-9]
[:alpha:] 任意字符,等价于[a-zA-Z]
[:blank:] 空格和制表,等价于[\\t]
[:cntrl:] ASCII控制字符,0到31和127
[:digit:] 任意数字,等价于[0-9]
[:graph:] 与[:print:]相同,但是不包含空格
[:print:] 任意可打印字符
[:lower:] 任意小写字母,等价于[a-z]
[:punct:] 既不在[:alnum:]又不在[:cntrl:]中的任意字符
[:space:] 包括空格在内的任意空白字符,等价于[\\f\\n\\r\\t\\v]
[:upper:] 任意大写字母,等价于[A-Z]
[:xdigit:] 任意十六进制数字,等价于[a-fA-F0-9]
匹配多个实例: 更强大的字符匹配,
* 0个或多个匹配
. 匹配任意字符
? 0个或1个匹配,等价于{0, 1}
+ 1个或多个匹配,等价于{1, }
{n} 指定数目的匹配
{n, } 不少于指定数目的匹配
{n, m} 匹配数目的范围,m不超过225
例子:
SELECT prod_name
FROM products
WHERE prod_name REGEXP '\\([0-9] sticks?\\)'
ORDER BY prod_name;
// 查找结果为 TNT (1 stick)
// TNT (5 sticks)
// 解释说明:\\( 匹配的是( , [0-9]匹配任意数字,(结果中为1、5),sticks? 匹配stick和sticks,因为s后面的?使s成为可选,因为?匹配它前面的任何字符的0次或1次的出现,\\) 匹配 )。
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[[:digit:]]{4}'
ORDER BY prod_name;
// 输出结果为 JetPack 1000
// JetPack 2000
// 解释说明:[:digit:]匹配任意数字,因而它是数字的一个集合,{4}确切的要求它前面的字符(任意数字)出现4次,所以[[:digit:]]{4}匹配连在一起的任意4位数字,等价于'[0-9][0-9][0-9][0-9]'。
定位符:以上所有的例子都是匹配一个串中任意位置的文本,为了匹配特定位置的文本,需要使用定位符,如下所示:
^ 文本的开始
$ 文本的结尾
[[:<:]] 词的开始
[[:>:]] 词的结尾
例子:
SELECT prod_name
FROM products
WHERE prod_name REGEXP '^[0-9\\.]'
ORDER BY prod_name;
// 输出结果为: .5 ton anvil
// 1 ton anvil
// 2 ton anvil
// 解释:^ 匹配串的开始,因为^[0-9\\.]只在 . 或任意数字为串中第一个字符才匹配它们
10、创建计算字段
计算字段:计算字段并不实际存在于数据库表中,计算字段是运行时语句内创建的。
拼接字段:将值联结到一起构成单个值,解决办法就是把两个列拼接起来,在MySQL的语句中,可使用Concat()函数类拼接两个列。(多数DBMS使用+或||来完成拼接的,MySQL则使用Concat())
SELECT Concat(vend_name, '(', vend_country, ')')
FROM vendors
ORDER BY vend_name;
// 结果为 ACME(USA)
// Anvils R Us (USA)
// Jet Set (England)
// 解释:Concat()拼接串,即把多个串连接在一起形成一个较长的串,需要一个或多个指定的串,各个串之间用逗号分隔。
此外,可使用RTrim()删除值右边的所有空格,LTrim()删除值左边的所有空格。
上述获取的拼接字段是有名字的,只是一个值,这样以来,该值是无法用于客户机应用中的,因为客户端没有办法来引用,所以这时候就可以使用别名,用AS 关键字赋予。
SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') AS vend_title
FROM vendors
ORDER BY vend_name;
// 查询结果: vend_title
// Anvils R Us (USA)
// Jet Set (England)
// AS指示SQL创建一个包含指定计算的名为vend_title的计算字段,从输出来看,结果与以前的的相同,但是现在列名为vend_title,任何客户端都可以按名引用这个列,就像它是一个实际的表列一样。
执行算术计算:计算字段的另一种常见用途就是对检索出的数据进行算术计算,
SELECT prod_id,quantity,item_price,quantity*item_price AS expanded_price
FROM orderitems
WHERE order_num = 20005;
// prod_id quantity item_price expanded_price
// ANV01 10 5.99 59.9
// ANV02 3 9.99 29.97
// 输出中显示的expanded_price列为一个计算字段,此字段为quantity*item_price计算所得,客户端可以使用这个新计算列,就像使用其他列一样。
如何测试计算?提供了测试和试验函数与计算的一个很好的方法, 可以省略FROM子句以便简单地访问和处理表达式。例如, 3*2 ;返回6, Trim'123';123。 Now()将利用Now()函数返回当前日期和时间。
11、使用数据处理函数
文本处理函数:
SELECT vend_name, Upper(vend_name) AS vend_name_upcase
FROM vendors
ORDER BY vend_name;
// 输出结果为: vend_name vend_name_upcase
// Jet Set JET SET
// Jouets Et JOUETS ET
// 正如所见,Upper()将文本转换为大写,
常见文本处理函数:
Left() 返回串左边的字符
Length() 返回串的长度
Locate() 找出串的一个子串
Lower() 将串转换为小写
LTrim() 去掉左边的空格
Right() 返回串右边的字符
RTrim() 去掉串右边的空格
Soundex() 返回与串发音相似的,对串进行发音比较而不是字母比较
SubString() 返回子串的字符
Upper() 将串转换为大写
日期和时间处理函数:日期和时间采用相应的数据类型和特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。常用日期和时间处理函数如下所示,
AddDate() 增加一个日期(天、周等)
AddTime() 增加一个时间(时、分等)
CurDate() 返回当前日期
CurTime() 返回当前时间
Date() 返回日期时间的日期部分
DateDiff() 计算两个日期之差
Date_Add() 高度灵活的日期运算函数
Date_Format() 返回一个格式化的日期或时间串
Day() 返回一个日期的天数部分
DayOfWeek() 对于一个日期,返回对应的星期几
Hour() 返回一个时间的小时部分
Minute() 返回一个时间的分钟部分
Month() 返回一个日期的月份部分
Now() 返回当前日期和时间
Second() 返回一个时间的秒部分
Time() 返回一个日期时间的时间部分
Year() 返回一个日期的年份部分
例子:
SELECT * FROM trade WHERE Date(order_date) = '2005-09-01'; // 表示获取这9月1号这一天的数据
SELECT * FROM trade WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30'; // 表示获取2005年09月下的所有数据,等价于
SELECT * FROM trade WHERE Year(order_date) = 2005 AND Month(order_date) = 9; // Year()函数从order_time返回年份,Month()函数返回月份
数值处理函数: 数值处理函数仅处理数值数据,这些函数一般主要用于代数、三角或几何运算。常用数值处理函数:
Abs() 返回一个数的绝对值
Cos() 返回一个角度的余弦
Exp() 返回一个数的指数值
Mod() 返回除操作的余数
Pi() 返回圆周率
Rand() 返回一个随机数
Sin() 返回一个角度的正弦
Sqrt() 返回一个数的平方根
Tan() 返回一个角度的正切
12、汇总数据
聚集函数: 通常经常需要汇总数据,而不用把它们实际检索出来,
AVG() 返回某列的平均值,忽略列值为NULL的行
COUNT() 返回某列的行数
MAX() 返回某列的最大值,用于文本数据时,如果数据按相应的列排列,则MAX()返回最后
MIN() 返回某列的最小值
SUM() 返回某列值之和
COUNT(*) AS num_items,MIN(prod_price) AS price_min, MAX(prod_price) AS price_max,AVG(prod_price) AS price_avg FROM products;
// 输出结果为
num_items price_min price_max price_avg
12 3 9 8.23
13、分组数据
分组允许把数据分为多个逻辑组,以便能对每个组进行聚集计算。
创建分组: 分组是在语句的GROUP BY子句中建立的,如果分组列中具有NULL值,则NULL将作为一个分组返回,如果列中有多行NULL值,它们将分为一组;(但凡使用到GROUP BY的,前面一定要有聚合函数SUM/MIN/MAX/AVG/COUNT)
SELECT vend_id, COUNT(*) AS num_prods
FROM products
GROUP BY vend_id;
// vend_id num_prods
1001 3
1002 2
1003 7
// GROUP BY子句指示MySQL按vend_id排序并分组数据,这导致对每个vend_id而不是整个表计算num_prods一次,我们可以看到供应商1001有3个商品,1002有两个
GROUP BY原理解读:https://blog.csdn.net/baiyan3212/article/details/87970489
过滤分组: 除了能用GROUP BY分组数据外,MySQL还允许过滤分组,规定包括哪些分组、排除哪些分组。WHERE过滤指定的是行而不是分组,HAVING非常类似于WHERE,以上所有类型的WHERE子句都可以用HAVING来替代,唯一的差别是WHERE过滤行,而HAVING过滤分组(同时HAVING后面也需要跟上条件子句,这个条件子句一定是聚合函数)。当GROUP BY后面跟多个字段X、Y时,可以将X、Y看成一个整体,即将具有相同X和相同Y的分组在一起。
所以GROUP BY也可用来过滤重复的数据,与DISTINCT效果一样,不同的是:
- 当对系统的性能高并发、数据量大时使用GROUP BY;
- 当对系统的性能不高时、使用数量少时,两者均可用;
- 尽量使用GROUP BY。
SELECT cust_id, COUNT(*) AS orders
FROM orders
GROUP BY cust_id
HAVING COUNT(*) >= 2
// cust_id orders
// 10001 2
// HAVING 子句过滤COUNT(*) >= 2(两个以上的订单)的那些分组,(丢弃小于2的,返回大于等于2的)
此外WHERE和HAVING还可以一起使用,在WHERE过滤行的基础上使用HAVING过滤分组。同时在GROUP BY后面加上WITH ROLLUP可以实现在分组统计数据基础上再进行相同的统计。(可以实现汇总数据等方面的要求。)
SELECT vend_id, COUNT(*) AS num_prods
FROM products
WHERE prod_price >= 10
GROUP BY vend_id
HAVING COUNT(*) >= 2;
// vend_id num_prods
// 1003 4
// 1005 2
// 首先WHERE子句规定了选择所有prod_price至少为10的行,然后使用GROUP BY按照vend_id分组数据,最后使用HAVING子句选择计数为2或2以上的分组。如果没有WHERE子句,将会多检索出内容。
分组和排序: GROUP BY和ORDER BY
SELECT order_num, SUM(quantity * item_price) AS order_total
FROM orderitems
GROUP BY order_num
HAVING SUM(quantity * item_price) >= 50;
// order_num order_total
// 20005 149.87
// 20006 55.00
// 20007 1000.00
// 20008 125.00
// 为了按总计订单价格排序输出,需要添加ORDER_BY子句
SELECT order_num, SUM(quantity * item_price) AS order_total
FROM orderitems
GROUP BY order_num
HAVING SUM(quantity * item_price) >= 50
ORDER BY order_total;
// 20006 55.00
// 20008 125.00
// 20005 149.87
// 20007 1000.00
子句顺序: 下表表示在语句中使用时必须遵循的次序:
要返回的列或表达式
FROM 从中检索数据的表
WHERE 行级过滤,WHERE 应该在 GROUP BY 之前使用
GROUP BY 分组说明
HAVING 组级过滤,HAVING 应该在 GROUP BY 之后使用
ORDER BY 输出排序顺序
LIMIT 要检索的行数
写的顺序: select ... from... where.... group by... having... order by.. limit...
执行顺序: from... where...group by... having.... select... order by... limit...
MYSQL查询执行过程:
- 客户端发送一条查询语句到服务器;
- 服务器首先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果,否则进入下一个阶段;
- 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划;
- MYSQL根据优化器生成的执行计划,调用存储引擎的API来执行查询;
- 将结果返回给客户端。
14、使用子查询
子查询: 即嵌套在其他查询中的查询。
利用子查询进行过滤: 把一条语句返回的结果用于另一条语句的WHERE子句。
例子:
SELECT order_num
FROM orderitems
WHERE prod_id = 'TNT2';
// prod_num
// 20005
// 20007 对于prod_id为TNT2的所有订单物品,检索其order_num列,下一步查询具有订单20005和20007的客户ID,利用IN
SELECT cust_id
FROM orders
WHERE order_num IN(20005, 20007);
// cust_id
// 10001
// 10004, 那么把上面返回订单号的查询便为子查询组合两个查询,语句为
SELECT cust_id
FROM orders
WHERE order_num IN( order_num FROM orderitems WHERE prod_id = 'TNT2')
// 结果为
// cust_id
// 10001
// 10004,
// 在语句中,子查询总是从内向外处理。在处理上面的语句时,MySQL实际上执行了两个操作,首先执行
// order_num FROM orderitems WHERE prod_id = 'TNT2'; 此查询返回两个订单号,然后这两个值以IN操作符要求的逗号分隔的格式传递给外部查询的WHERE子句,外部查询变成: cust_id FROM orders WHERE order_num IN(20005, 20007);
对于能嵌入子查询的数目是没有限制的,不过在实际使用时还是避免嵌入太多的子查询,以免降低查询的性能。
作为计算字段使用子查询: 使用子查询的另一种方法是创建计算字段。
假如需要显示customers表中每个客户的订单总数,订单与相应的客户ID存储在orders表中。为了执行这个操作,需要遵循以下的步骤:1、从customers表中检索客户列表;2、对于检索出的每个客户,统计其在orders表中的订单数目。
SELECT COUNT(*) AS orders
FROM orders
WHERE cust_id = 10001; // 对客户为10001的订单进行计数,那么为了对每个客户执行COUNT(*)计算,应该将COUNT(*)作为一个子查询
SELECT cust_name, cust_state, ( COUNT(*)
FROM orders
WHERE orders.cust_id = customers.cust_id) AS orders
FROM customers
ORDER BY cust_name;
// 整条语句对customers表中每个客户返回3列,cust_name、cust_state和orders,orders是一个计算字段,它是由圆括号中的子查询建立的,并且在WHERE子句中使用完全限定列名去比较orders表中的cust_id与当前正从customers表中检索的cust_id。
15、联结表
联结: SQL最强大的功能之一就是能在数据检索查询的执行中联结(join)表。
关系表:关系表的设计就是要保证把信息分解成多个表,一类数据一个表,各表通过某些常用的值(即关系设计中的关系)互相关联。
比如建立的两个表:一个存储供应商信息,另一个存储产品信息。vendors表包含所有供应商信息,每个供应商占一行,每个供应商应具有唯一的标识,此标识称为主键(primary key),可以是供应商ID或任何其他唯一值。products表只存储产品信息,它除了存储供应商ID(vendors表的主键)外不存储其他供应商信息。那么vendors表的主键又叫做products的外键,它将vendors表与products表关联,利用供应商ID能从vendors表中找出相应供应商的详细信息。
关系数据可以有效地存储和方便地处理,因此关系数据库的可伸缩性 远比非关系数据库要好。 可伸缩性:能够适应不断增加的工作量而不失败。
用来在一条语句中关联表,将存储在多个表中的数据检索出来称之为联结。
创建联结:
SELECT vend_name, prod_name, prod_price
FROM vendors, products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;
// FROM子句与以前的语句不一样,这条语句的FROM子句列出了两个表,分别是vendors和products,它们就是这条语句联结的两个表的名字。这两个表用WHERE子句正确联结,WHERE子句指示MySQL匹配vendors表中的vend_id和products表中vend_id,一定要使用完全限定列名。
WHERE子句对建立联结是非常重要的。
笛卡尔积:由没有联结条件的表关系返回的结果为笛卡尔积,检索出的行的数目将是第一个表中行数乘以第二个表中的行数。(没有使用WHERE子句建立联结) 如果FROM的子句是两个表用逗号隔开,表示对两个表做一个笛卡尔积。
SELECT *
FROM vendors, products;
// 没有使用WHERE子句建立联结,返回的结果是笛卡尔积数量的结果,即vendors的行数乘以products的行数
SELECT vend_name, prod_name, prod_price
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id; // 表vendors和products之间的关系是FROM子句的组成部分,以INNER JOIN指定。在使用这种语法时,联结的条件使用特定的ON子句而不是WHERE子句给出。
子查询可以转换成联结的相同查询:
SELECT cust_name, cust_contract
FROM customers
WHERE cust_id IN ( cust_id
FROM orders
WHERE order_num IN ( order_num
FROM orderitems
WHERE prod_id = 'TNT2'));
等价于:
SELECT cust_name, cust_contract
FROM customers, orders, orderitems
WHERES customers.cust_id = orders.cust_id
AND orderitems.order_num = orders.order_num
AND prod_id = 'TNT2'; // 使用两个联结,2个WHERE子句条件,前两个关联联结中的表,后一个过滤产品TNT2的数据。
16、创建高级联结
使用表别名: 使用Concat()给列名和计算字段起别名,还允许给表起别名,可以缩短SQL语句并且允许在单条语句中多次使用相同的表。
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 = 'TNT2'
// FROM子句中3个表都具有别名,在后来WHERE子句中使用别名,简化表名;给表起的别名不仅能用于WHERE,还可以用于的列表、ORDER BY子句以及语句的其他部分。
使用不同类型的联结:
自联结:假设发现某物品(其ID为DTNTR)存在问题,因此想知道生产该物品的供应商生产的其他物品是否也存在这些问题。因此该查询要求首先找到生产ID为DTNTR的物品的供应商,然后找出这个供应商生产的其他物品。一种解决方法为:
SELECT prod_id, prod_name
FROM products
WHERE vend_id = ( vend_id
FROM products
WHERE prod_id = 'DTNTR'); // 该查询使用了子查询,很好的解决了问题,
// 使用联结的相同查询:
SELECT p1.prod_id, p1.prod_name
FROM products AS p1, products AS p2
WHERE p1.vend_id = p2.vend_id
AND p2.prod_id = 'DTNTR'; // 查询的结果与上面的子查询结果是相同的
// 因为需要使用products表两次,所以给表起了两个别名,在查询的时候需要指定表的别名。
自然联结:排除多次出现,使每个列只返回一次。
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 = 'FB';
// 在这个例子中,通配符只对第一个表使用,所有其他列明确列出,所以没有重复的列被检索出来。
左联结(LEFT JOIN):根据ON后面给出的两个表的条件将两个表联结起来,(此时左表为主表),从笛卡尔积的角度来讲,就是先从笛卡尔积中挑出ON子句成立的记录,然后加上左表中剩余的记录,若ON后面没有成立的记录,也会返回左表的记录;右联结与左联结类似,从笛卡尔积中挑出ON子句成立的记录,然后加上右表中剩余的记录。
外连接(OUTER JOIN)/ 全连接(FULL JOIN): 就是求两个表的并集,从笛卡尔的角度将就是先从笛卡尔积中挑出ON子句条件成立的记录,然后加上左表剩余的记录,最后再加上右表中剩余的记录。MYSQL不支持OUTER JOIN,但是可以对左连接、右连接的结果做UNION操作来实现。
内联结 (INNER JOIN):就是求两个表的交集,从笛卡尔积中挑出ON子句成立的条件。
例子:LeetCodeT175--组合两个表
表1: Person
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| PersonId | int |
| FirstName | varchar |
| LastName | varchar |
+-------------+---------+
PersonId 是上表主键
表2: Address
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| AddressId | int |
| PersonId | int |
| City | varchar |
| State | varchar |
+-------------+---------+
AddressId 是上表主键
编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:
FirstName, LastName, City, State
解题思路:数据库在通过连接两张或多张表来返回记录时,都会生成一张中间的临时表,然后再将这张表返回给用户。在使用 LEFT JOIN时,ON和WHERE的区别为:
- ON条件是在生成临时表时使用的条件,它不管ON中的条件是否为真,都会返回左边表中的记录;
- WHERE条件是在临时表生成好后,再对临时表进行过滤的条件,这时已经没有LEFT JOIN的含义(必须返回左边表的记录)了,条件不为真的就得全部过滤掉。
本题中,要求无论Person中是否有地址信息都需要返回,也就是说Person表中的信息是必须得有的,所以使用左连接来求解。
SELECT p.FirstName, p.LastName, a.City, a.state
FROM Person AS p LEFT JOIN Address AS a
ON a.PersonId = p.PersonId
使用带聚集函数的联结:聚集函数用来汇总数据。虽然目前聚集函数的所有例子只是从单个表汇总数据,但是这些聚集函数也可以与联结一起使用。
// 需求:检索所有客户及每个客户所有下的订单数
SELECT customers.cust_name, 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;
// 语句使用INNER JOIN将customers和orders表互相关联,GROUP BY子句按客户分组数据,因此调用COUNT(orders.order_num)对每个客户的订单计数,将它作为num_ord返回。
使用联结和联结条件:
- 注意所使用的联结类型。一般我们使用内部联结,但使用外部联结也是有效的。
- 保证使用正确的联结条件,否则将返回不正确的结果。
- 应该总是能够提供联结条件,否则会得出笛卡尔积。
- 在一个联结表中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做事合法的,一般也很有用,但应该在一起测试它们之前,分别测试每个联结。这将使故障排除更为简单。
17、组合查询
组合查询: 多数SQL查询包含从一个或多个表中返回数据的单条语句。MySQL也允许执行多个查询(多条语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。有两种基本情况需要使用组合查询:
- 在单个查询中从不同的表返回类似结构的数据;
- 对单个表执行多个查询,按单个查询返回数据。
创建组合查询:使用UNION操作符来组合数条SQL查询,可给出多条语句,将它们的结果组合成单个结果集。
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)
ORDER BY vend_id, prod_price;
// 这条语句UNION关键字执行MySQL执行两条语句,把输出组合成单个查询结果集并分别按照vend_id、
// prod_price将结果排序,其中相同的结果是合并的,如果使用的是UNION ALL的话,将不会取消重复的列。
// 等价于
SELECT vend_id, prod_id, prod_price
FROM products
WHERE prod_price <= 5
OR vend_id IN(1001, 1002);
UNION规则:UNION必须由两条或两条以上的语句组成,语句之间用关键字UNION分割;UNION中的每个查询必须包含相同的列、表达式或聚集函数;列数据类型必须兼容。
18、全文本搜索
理解全文本搜索:前面提到的LIKE关键字利用通配操作符匹配文本(和部分文本),能够查找包含特殊值或部分值的行;同时还可使用正则表达式编写所需行的非常复杂的匹配模式。
虽然这些搜索机制非常有用,但是存在几个重要的限制:
- 性能:通配符和正则表达式匹配通常要求MySQL尝试匹配表中所有行(而且这些搜索极少使用表索引)。因此,由于被搜索行数不断增加,这些搜索可能非常耗时。
- 明确控制,使用通配符和正则表达式匹配,很难(而且并不总是能)明确地控制匹配什么和不匹配什么。例如,指定一个词必须匹配,一个词必须不匹配,而一个词仅在第一个词确实匹配的情况下或者才可以不匹配。
- 智能化的结果:虽然基于通配符和正则表达式的搜索提供了非常灵活的搜索,但是它们都不能提供一种智能化的选择结果的方法。例如,一个特殊词的搜索将返回包含该词的所有行,而不区分包含单个匹配的行和包含多个匹配的行(按照可能是更好的匹配来排列它们)
所有这些限制都可用全文本搜索来解决,在使用全文本搜索时,MySQL不需要分别查看每个行,不需要分别分析和处理每个词。
使用全文本搜索:
// 启用全文本搜索
CREATE TABLE productnotes
(
note_id int NOT NULL AUTO_INCREMENT,
prod_id char(10) NOT NULL,
note_date datatime NOT NULL,
note_text text NULL,
PRIMARY KEY(note_id),
FULLTEXT(note_text)
) ENGINE=MyISAM;
// 为了进行全文本搜索,名为note_text的列要加上FULLTEXT()进行标注,MySQL会根据FULLTEXT(note_text)的指示对它进行索引,如果需要也可以指定多个列。定义之后,MySQL自动维护该索引。在增加、更新或删除行时,索引随之自动更新。
在索引之后,使用 两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列,Against()指定要使用的搜索表达式。
SELECT node_text
FROM productnotes
WHERE Match(note_text) Against('rabbit'); // 以文本匹配的良好程度排序数据,也就是说,包含词rabbit作为第3个词的行的等级是高于作为第20个词的行高。
// WHERE note_text LIKE '%rabbit%'; 以不特别有用的顺序返回数据
// 此语句检索单个列note_text,由于WHERE子句,一个全文本搜索被执行。Match(note_text)指示MySQL针对指定的列进行搜索,Against('rabbit')指定词rabbit作为搜索文本。
由于数据是索引的,全文本搜索还是比较快的。
使用查询扩展:查询扩展设法放款所返回的全文本搜索结果的范围。假如想找到所有提到anvils的注释,现在只有一个注释包含词anvils,但是还想找到可能与搜索有关的所有其他行,即使它们不包含词anvils。
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('anvils' WITH QUERY EXPANSION);
// 查询扩展会极大地增加了返回的行数,
全文本布尔操作:
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('heavy -rope*' IN BOOLEAN MODE);
// 返回的结果匹配词heavy,但是 -rope*明确地指示MySQL排除包含rope*(任何以rope开始、包含rope的词)的行。
全文本布尔操作符说明如下:
+ 包含,词必须出现
- 排除,词必须不出现
> 包含,而且增加等级值
< 包含,且减少等级值
() 把词组成子表达式,允许这些子表达式作为一个组被包含、排除、排列等
~ 取消一个词的排序值
* 词尾的通配符
"" 定义一个短语(与单个词的列表不一样,它匹配整个短语以便包含或排除这个短语)
19、插入数据
数据插入:插入包含插入完整的行、行的一部分、多行和某些查询的结果。
INSERT INTO trade
VALUES(NULL,
'DUXINHUI',
6,
"CA",
"Los");
// INSERT语句一般不会产生输出,此例子插入一个新交易到trade表,存储的值需要在VALUES子句中给出,对于每个列必须提供一个值,如果某个列没有值应该使用NULL。更安全的操作是在表名trade后面加上要插入列的名字。
除此还可以一次插入多个行,使用多条INSERT语句一次提交它们即可。
还可以插入检索出的数据,
INSERT INTO trade(id,
name,
age,
country,
info)
id, name, age, country, info FROM trade_info;
// 从表trade_info中查询的数据直接插入trade表中,也就是将trade_info表中的所有数据都导入了trade表中。
20、更新和删除数据
为了更新(修改)表中的数据,可使用UPDATE语句。
UPDATE customers
SET cust_email = 'qwe@yeah.net'
cust_name = 'qwe'
WHERE cust_id = 123;
// 在customers表中更新cust_id为123的cust_email; SET命令用来将新值赋给被更新的列;WHERE告诉MySQL要更新的列,如果没有则会更新整个表中的cust_email。
此外在UPDATE中还可以使用子查询。如果用UPDATE语句更新多行时,出一个错误时整个UPDATE操作会被取消,IGNORE关键字会使即使发生错误时也继续进行更新。
UPDATE trade
SET name = NULL
WHERE id = 123; // 可删除某个列的值
删除数据:
DELETE FROM trade
WHERE id = 123;
// 在trade表中删除id为123的行的数据
21、创建和操纵表
CREATE TABLE productnotes IF NOT EXISTS
(
note_id int NOT NULL AUTO_INCREMENT,
prod_id char(10) NOT NULL,
note_date datatime NOT NULL,
note_text text NULL,
note_num int NOT NULL DEFAULT 1,
PRIMARY KEY(note_id),
FULLTEXT(note_text)
) ENGINE=MyISAM; //
// 列与列之间用逗号隔开,主键必须唯一;AUTO_INCREMENT表示自增,本列每当增加一行时自动增量。每个表只允许一个AUTO_INCREMENT列,而且必须被索引(如通过使它成为主键);
// last_insert_id()可以获取表中最后一个AUTO_INCREMENT的值。
// 如果在插入时没有指定值的列还可以使用默认值,在创建表的时候使用DEFAULT指定
NGINE=MyISAM 指定数据库的内部引擎:
- InnoDB是一个可靠的实务处理引擎,它不支持全文本搜索;
- MEMORY在功能上等同于MyISAM,但是由于数据存储在内存中,速度很快;
- MyISAM是一个性能极高的引擎,支持全文本搜索,但不支持实务处理。
ALTER TABLE trade
ADD price int;
// 使用ALTER语句给trade表的结构进行修改,这个表示必须存在的,否则将会出现错误。DROP表示删除某个列;同时,ALTER最常用的还是定义外键,使用关键字FOREIGN KEY。
DROP TABLE trade; // 表示删除整个表而不是其内容
RENAME TABLE trade TO trade1; // 表示将表trade重命名为trade1
22、**使用视图
视图: 视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
SELECT cust_name, cust_contract
FROM customers, orders, orderitems
WHERE customers.cust_id = orders.cust_id
AND orderitems.order_num = orders.order_num
AND prod_id = 'TNT2';
// 此查询用来检索订购了某个特定产品的客户,任何需要这个数据的人都必须理解相关表的结构,并且知道如何创建查询和对表进行联结。为了检索其他产品的相同数据,必须修改最后的WHERE子句。现在假如可以把整个查询包装成一个名为productcustomers的虚拟表,则可以轻松地检索出相同的数据:
SELECT cust_name, cust_contract
FROM procustcustomers
WHERE prod_id = 'TNT2';
// 这就是视图的作用,productcustomers就是一个视图,作为视图,它不包含表中应该有的任何列或数据,它包含的是一个SQL查询(与上面用以正确联结表的相同的查询)。
视图的常见应用:
- 重用SQL语句;
- 简化复杂的SQL操作,在编写查询后,可以方便的重用它而不必知道它的基本查询细节。
- 使用表的组成部分而不是整个表;
- 保护数据,可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;
- 更改数据格式和表示,视图可返回与底层表的表示和格式不同的数据。
在视图创建之后,可以用与表基本相同的方式利用它们。可以对视图执行操作,过滤和排序数据,将视图联结到其他视图或表,甚至能添加和更新数据。
性能问题:因为视图不包括数据(视图只是包含了对表的查询语句),所以每次使用时,都必须处理查询执行时所需要的任何一个检索。如果使用了多个联结和过滤创建了复杂的视图或者嵌套了视图,性能可能会下降的很厉害。因此在部署使用了大量的视图的应用前应该进行测试。
视图的规则和限制:
- 视图的名称必须唯一,不能与其他视图或者表重名;
- 对于可以创建的视图没有数量的限制;
- 为了创建视图,必须具有足够的访问权限;
- 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造一个视图;
- ORDER BY可以用在视图中,但如果从该视图中检索数据中也含有ORDER BY,那么视图中的ORDER BY将被覆盖;
- 视图不能索引,也不能有关联的触发器或默认值;
- 视图可以和表一起使用。
使用视图:
创建视图:
- 视图用CREATE VIEW语句创建;
- 使用SHOW CREATE VIEW viewname;来查看创建视图的语句;
- 用DROP删除视图,语法为DROP VIEW viewname;
- 更新视图时可以先用DROP再用CREATE,也可以直接使用CREATE OR REPLACE VIEW。如果要更新的视图不存在,则第2条更新语句会创建一个视图;如果要更新的视图存在,则第2条更新语句会替换原有视图。
使用视图简化复杂的联结:
CREATE VIEW productcustomers AS
cust_name, cust_contract
FROM customers, orders, orderitems
WHERS customers.cust_id = orders.cust_id
AND orderitems.order_num = orders.order_num;
// 这条语句创建了一个名为productcustomers的视图,它联结了三个表,以返回已订购了任意产品的所有客户的列表。如果执行了 * FROM productcustomers,将列出订购了任意产品的客户。可以看到视图可简化复杂的联结,利用视图可一次性编写基础的SQL,然后根据需要多次使用。
使用视图重新格式化检索出的数据:
CREATE VIEW vendorlocations AS
Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') AS vend_title
FROM vendors
ORDER BY vend_name;
// 第2行到第4行是上面组合计算列中返回供应商和位置的语句(第10章),
使用视图过滤不想要的数据:
视图对于普通的WHERE子句也很有用,例如,可以定义customeremaillist视图,它过滤没有电子邮件地址的客户。
CREATE VIEW customeremaillist AS
cust_id, cust_name, cust_email
FROM customers
WHERE cust_email IS NOT NULL;
// 这分视图过滤了那些没有电子邮件地址的用户,去除了那些cust_email列中具有NULL值的那些行,使他们不被检索出来。
使用视图与计算字段:
SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM orderitems
WHERE order_num = 20005; // 计算order_num为20005的每种物品的总价格,将其转换成视图, 可以查看所有物品的总价格
CREATE VIEW orderitemsexpanded AS
prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM orderitems;
更新视图:
通常视图是可以更新的,即可以使用INSERT、UPDATE和DELETE操作,更新一个视图其实就是更新其基表,因为视图本身就是一些SQL语句,不是一个实际 存在的表。
不能对视图进行更新操作的情况如下:
- 分组(使用GROUP BY和HAVING)
- 联结
- 子查询
- 并
- 聚集函数(Min(), Count()、Sum()等)
- DISTINCT
- 导出(计算)列
视图是虚拟的表,它们包含的不是数据而是根据需要检索数据的查询。视图提供了一种MySQL的语句层次的封装,可用来简化数据处理以及重新格式化基础数据或保护基础数据。
子查询: 关键字ANY、ALL、IN、EXISTS:
主表数据量小而次表数据量非常大时,使用EXISTS的查询效率高;主表数据量非常大而次表数据量非常小时使用IN,因为IN会把子句中的查询作为结果缓存下来,然后对主查询中的每个记录进行查询;当两个表差不多时,使用IN和EXISTS都一样。
- IN: 表示值是否存在子查询结果集中
SELECT * FROM tb_book WHERE row_no IN (SELECT row_no FROM tb_row);
- EXISTS:内层查询语句查询到满足记录的条件时,返回true,否则返回false;
SELECT * FROM tb_bool WHERE EXISTS row_no (SELECT row_no FROM tb_row WHERE row_no >= 12);
- ANY:只要满足内层查询语句返回的结果中的任意一个:
- ALL: 满足内层查询语句返回的结果中的所有。
23、使用存储过程
存储过程:简单来说就是为了以后的使用而保存的一条或多条MySQL语句的集合。可以将其视为批文件,虽然它们的作用不仅限于批处理。
存储过程简单来说就是简单、安全和高性能:
- 通过把处理封装在容易使用的单元中,简化复杂的操作;
- 由于不要求反复建立一系列处理步骤, 保证了数据的完整性。多人开发时,能够防止错误,保证了数据的一致性;
- 简化对变动的管理;如果表名、列名或业务逻辑有变化,只需要更改存储过程的代码,使用的人员甚至不需要知道这些变化;延伸就是安全性
- 提高性能,因为使用存储过程比使用单独的SQL语句要快;
- 存在一些只能用单个请求中的SQL元素和特性,存储过程可以使用它们编写功能更强更灵活的代码;
- 一般来说,存储过程的编写比基本SQL语句更复杂,编写存储过程需要更高的技能,更丰富的经验;
使用存储过程:
-
执行存储过程:
CALL productpricing(@pricelow, @pricehigh, @priceaverage); // MySQL称存储过程的执行为调用,因此MySQL执行存储过程的语句为CALL。CALL接收存储过程的名字,以及需要传递给它的任意参数。此CALL子句中,执行名为productpricing的存储过程,它计算并返回产品的最低、最高和平均价格。
-
创建存储过程:
CREATE PROCEDURE productpricing() BEGIN Avg(prod_price) AS priceaverage FROM products; END; // 创建一个名为productpricing的存储过程,使用关键字PROCEDURE;如果存储过程接受参数,它们将在()中列举出来;此存储过程没有参数,但()仍需要;BEGIN和END语句用来限定存储过程体,过程体本身仅是一个简单的语句;该段代码创建一个新的存储过程productpricing,没有返回数据。 CALL prodcutpricing(); // 执行刚创建的存储过程并显示返回的结果。存储过程实际上是一种函数,所以存储过程名后面需要有()符号,即使不传递参数。 DROP PROCEDURE productpricing; // 删除创建过程,注意没有();如果创建过程不存在,则将产生一个错误,所以可以使用DROP PROCEDURE IF EXISTS。
-
使用参数:productpricing只是一个简单的存储过程,它简单地显示语句的结果。一般情况下存储过程并不显示结果,而是把结果返回给你指定的变量。
CREATE PROCEDURE productpricing( OUT pl DECIMAL(8, 2), OUT ph DECIMAL(8, 2), OUT pa DECIMAL(8, 2) ) BEGIN Min(prod_price) INTO pl FROM products; Max(prod_price) INTO ph FROM products; Avg(prod_price) INTO pa FROM products; END; // 此存储过程接受三个参数:pl存储产品最低价格,ph存储产品最高价格,pa存储产品平均价格;每个过程必须具有指定的类型,这里使用十进制值。关键字OUT指出相应的参数用来从存储过程传出一个值(返回给调用者)。存储过程的代码位于BEGIN和END之间,是一系列的语句,用来检索值,然后保存到相应的变量(通过指定INTO关键字)。 CALL productpricing(@pricelow, @pricehigh, @priceaverage); // 由于存储过程要求3个参数,因此必须正好传递3个参数。所以调用时也要传入三个变量,必须以@开始。 @priceaverage, @pricelow, @pricehigh; 将打印出变量的值。 CREATE PROCEDURE ordertotal( IN onumber INT, OUT ototal DECIMAL(8, 2) ) BEGIN Sum(item_price*quantity) FROM orderitems WHERE order_num = onumber INTO total; END; // 此存储过程ordertotal接受订单号并返回该订单的合计。onumber定义为IN,因为订单号被传入存储过程;ototal定义为OUT,因为要从存储过程返回合计。语句使用这两个参数,WHERE子句使用onumber选择正确的行,INTO使用ototal存储计算出来的合计。 CALL ordertotal(20009, @total); @total; 即可对存储过程进行调用 // 为了显示用来创建一个存储过程的CREATE语句,使用SHOW CREATE PROCEDURE语句; SHOW CREATE PROCEDURE ordertotal;
24、使用游标
游标:有时需要在检索出来的行中前进或后退一行或多行,这就是使用游标的原因。游标是一个存储在MySQL服务器上的数据库查询,它不是一条语句,而是被该语句检索出来的结果集,在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。MySQL游标只能用于存储过程(和函数)。
使用游标:
CREATE PROCEDURE processorders()
BEGIN
DECLARE ordernumbers CURSOR
FOR
order_num FROM orders;
END;
// 这个存储过程并没有做很多事情,DECLARE语句用来定义和命名游标,这里为ordernumbers,存储过程处理完成后,游标就消失,使用时必须打开游标
OPEN ordernumbers;
CLOSE ordernumbers; // 关闭游标
25、使用触发器
触发器: 触发器是MySQL响应以下任意语句(DELETE、INSERT、UPDATE)而自动执行的一条MySQL语句(或位于BEGIN和END语句之间的一组语句。)
创建触发器时需要给出4条信息:唯一的触发器名;触发器关联的表;触发器应该响应的活动(DELETE、INSERT或UPDATE);触发器何时执行(处理之前或之后)。
CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW 'Product added';
// 使用CREATE TRIGGER创建名为newproduct的触发器,AFTER INSERT表示此触发器将在INSERT语句成功执行后执行。FOR EACH ROW表示对每个插入行执行。在该例子中,文本Product added将对每个插入的行显示一次。
DROP TRIGGER newproduct; 将删除触发器。
使用触发器:
INSERT 触发器:在INSERT触发器内,可引用一个名为NEW的虚拟表,访问被插入的行;在BEFORE INSERT触发器中,NEW中的值也可以被更新(允许更改被插入的值);对于AUTO_INCREMENT列,NEW在INSERT执行之前包含0,在INSERT执行之后包含新的自动生成值。
CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW NEW.order_num;
// 此代码创建一个名为neworder的触发器,按照AFTER INSERT ON orders执行。在插入一个新订单到orders表时,MySQL生成一个订单号并保存到order_num中。触发器从NEW.order_num取得这个值并返回它。此触发器必须按照AFTER INSERT执行,因为在BEFORE INSERT语句执行之前,新order_num还未生成。
INSERT INTO orders(order_date, order_id)
VALUES(Now(), 10001)
// 输出结果为 order_num
10001
DELETE触发器:在DELETE触发器代码内,可以引用一个名为OLD的虚拟表,访问被删除的行;OLD内的值全都是只读的,不能更新;
CREATE TRIGGER deleteorder BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
INSERT INTO archive_others(order_num, order_date, cust_id)
VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;
// 在任意订单被删除前将执行此触发器,它使用一条INSERT语句将OLD中的值(要被删除的订单)保存到一个名为archive_others的存档表中。
UPDATE触发器:在UPDATE触发器代码中,可以引用一个名为OLD的虚拟表访问以前(UPDATE语句前)的值,引用一个名为NEW的虚拟表访问新更新的值;在BEFORE UPDATE触发器中,NEW中的值可能也被更新(允许更改将要用于UPDATE语句中的值);OLD中的值全都是只读的,不能更新。
CREATE TRIGGER updatevendor BEFORE UPDATE ON vendors
FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state);
// 任何数据净化都需要在UPDATE语句之前进行,此例中,每次更新一个行,NEW.vend_state中的值都用Upper(NEW.vend_state)替换。
26、使用索引
索引是快速搜索的关键,在数据库中,对字段建立索引可以大大提高查询速度。
假如创建一个表时没有使用索引,在对数据库中的数据进行查询时,MySQL会扫描所有记录;而如果在某列上建立了索引,MySQL无须任何扫描,即可准确找到该记录。
普通索引:
CREATE INDEX indexName ON trade(username(length));// 普通创建索引,如果是CHAR、VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须制定length,下同。
ALTER trade ADD INDEX [indexName] ON (username(length)); // 修改表结构
CREATE TABLE trade( // 创建表的时候直接指定
ID INT NOT NULL,
username VARCHAR(15) NOT NULL,
INDEX [indexName] (username(length))
);
DROP INDEX [indexName] ON trade; // 删除索引
唯一索引:
CREATE UNIQUE INDEX indexName ON trade(username(length));// 普通创建索引,
ALTER trade ADD UNIQUE [indexName] ON (username(length)); // 修改表结构
CREATE TABLE trade( // 创建表的时候直接指定
ID INT NOT NULL,
username VARCHAR(15) NOT NULL,
UNIQUE [indexName] (username(length))
);
主键索引:
它是一种特殊的索引,不允许有空值,一般是在建表的时候同时创建主键索引:
CREATE TABLE trade (
ID INT NOT NULL,
username VARCHAR(16) NOT NULL,
PRIMARY KEY(ID)
); // 一个表只能有一个主键
组合索引:
组合索引就是将一个表中的多个字段建到一个索引里。
CREATE TABLE trade(
ID INT NOT NULL,
username VARCHAR(16) NOT NULL,
city VARCHAR(50) NOT NULL,
age INT NOT NULL,
);
// 为了进一步榨取MySQL的效率,就考虑建立组合索引,就是将name、city和age建到一个索引中:
// 建表时,username长度为16,这里用10,因为一般情况下名字的长度不会超过10,这样就会加速索引查询速度,还会减少索引文件的大小,提高速度。
建立索引的时机:一般在WHERE和JOIN中出现的列需要建立索引,但也不完全如此,因为MySQL只对<, <=, =, >, >=, BETWEEN, IN以及某些时候的LIKE才会使用索引。
索引的不足之处:
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE,因为更新表时MySQL不就要保存数据,还要保存一下索引文件;
- 建立索引会占用磁盘空间的索引文件,一般情况这个问题不太严重,但是如果在一个大表上创建了多种组合索引,索引文件会膨胀的很快。
使用索引注意事项:
- 索引不会包含有NULL值的列,只要数据库中有一列含有NULL值,那么这一列对于复合索引就是无效的,所以在设计数据库时不要让字段的默认值为NULL;
- 使用短索引;
- 索引列排序,MySQL查询只使用一个索引,因此如果where子句中已经使用了所有的话,那么order by中的列是不会使用索引的。
- LIKE语句操作
- 不要在列上进行运算。
索引的原理:
27、管理事务处理
28、全球化和本地化
数据库表被用来存储和检索数据,不同的语言和字符集需要以不同的方式存储和检索,因此MySQL需要适应不同的字符集(不同的字母和字符),适应不同的排序和检索数据的方法。
- 字符集为字母和符号的集合;
- 编码为某个字符集成员的内部指示;
- 校对为规定字符如何比较的指令。
SHOW CHARACTER SET; // 显示所有可用的字符集以及每个字符集的描述和默认校对
SHOW COLLATION; // 显示所有可用的校对,以及它们适用的字符集。
可以在创建数据库时、创建表甚至每一列时设置字符集。
29、安全管理
访问控制:MySQL服务器的安全基础是用户应该对他们需要的数据具有适当的访问权,既不能多也不能少。
管理用户:MySQL用户账户和信息存储在名为mysql的MySQL数据库中,一般不需要直接访问mysql数据库和表。
CREATE USER ben IDENTIFIED BY 'pwasedfsa'; // IDENTIFIED BY 给出用户ben的密码
RENAME ben TO kangkang; // 对用户ben重新命名
DROP USER kangkang; // 删除用户账号
设置访问权限:
SHOW GRANTS FOR ben; // 显示ben的当前权限
GRANT ALL/INSERT ON crashcourse.* TO 'ben'@'%'; // 此GRANT账户允许用户在crashcourse数据库的所有表上使用,表明ben对于crashcourse数据库中的所有数据具有只读访问权限。
REVOKE ALL ON crashcourse.* TO ben; // 撤销刚刚授予的权限
GRANT和REVOKE可在几个层次上控制访问权限:
- 整个服务器,使用GRANT ALL和REVOKE ALL;
- 整个数据库,使用ON database.*;
- 特定的表,使用ON database.table;
- 特定的列
- 特定的存储过程
权限说明:
权限 说明
ALL 除GRANT OPTION外的所有权限
ALTER 使用ALTER TABLE
ALTER ROUTINE 使用ALTER/DROP PROCEDURE
CREATE 使用CREATE TABLE
CREATE ROUTINE 使用CREATE PROCEDURE
CREATE TEMPORARY ATBLES 使用CREATE TEMPORARY ATBLE
CREATE USER 使用CREATE/DROP/RENAME USER和REVOKE ALL PRIVILEGES
CREATE VIEW 使用CREATE VIEW
DELETE 使用DELETE
DROP 使用DROP TABLE
EXECUTE 使用CALL和存储过程
FILE 使用 INTO OUTFILE 和LOAD DATA INFILE
GRANT OPTION 使用GRANT和REVOKE
INDEX 使用CREATE/DROP INDEX
INSERT 使用INSERT
LOCK TABLES 使用LOCK TABLES
PROCESS 使用SHOW FULL PROCESSLIST
RELOAD 使用FLUSH
REPLICATION CLIENT 服务器位置的访问
REPLICATION SLAVE 由复制从属使用
使用
SHOW DATABASES 使用SHOW DATABASES
SHOW VIEW 使用SHOW CREATE VIEW
SHUTDOWN 使用mysqladmin shutdown(关闭mysql)
SUPER 使用CHANGE MASTER、KILL、LOGS、PURGE、MASTER和SET GLOBAL,还允许 mysqladmin调试登陆
UPDAT 使用UPDATE
USAGE 无访问权限
30、数据库维护
备份数据:
31、改善性能
32、数据类型
串数据类型
MySQL接受定长的字符串和非定长的,定长字符串需要在创建表时指定长度。MySQL处理定长列的速度远高于非定长的列,此外MySQL不允许对变长列进行索引。
数据类型 说明
CHAR 1-255个字符的定长串,长度必须在创建时指定,否则假定为CHAR(1)
ENUM 接受最多64K个串组成的一个预定义集合的某个串
LONGTEXT 与TEXT相同,但最大长度为4GB
MEDIUMTEXT 与TEXT相同,最大长度为16K
SET 接受最多64个串组成的一个预定义结合的零个或多个串
TEXT 最大长度为64K的边长文本
TINYTEXT 与TEXT相同,但最大长度为255字节
VARCHAR 长度可变,最多不超过255字节,如果在创建时指定为VARCHAR(n),则可存储0-n个字符的变长串
数值数据类型
数据类型 说明
BIT 位字段,1-64位
BIGINT 整数值,-2^63 ~ 2^63 - 1
INT/INTEGER 整数值,-2^31 ~ 2^31 - 1
MEDIUMINT 整数值,有符号的范围是-8388608到8388607,无符号的范围是0到16777215。 一位大小为3个字节
SMALLINT 整数值,有符号的范围是-2^15 (-32,768) 到 2^15 - 1 (32,767) 的整型数据,无符号的范围是0到65535
TINYINT 有符号的范围是-128 - 127,无符号的范围是 从 0 到 255 的整型数据。一位大小为 1 字节
BOOLEAN/BOOL 布尔标志,或者为0或为1,主要用于开关标志
DECIMAL/DEC 精度可变的浮点值
DOUBLE 双精度浮点值
FLOAT 单精度浮点值
REAL 4字节的浮点值
MySQL中没有专门存储货币的数据类型,一般情况下使用DECIMAL(8, 2)。
日期和时间数据类型
DATE 表示1000-01-01~9999-12-31的日期,格式为YYYY-MM-DD
DATETIME DATE和TIME的组合
TIMESTAMP 功能和DATETIME相同,但范围更小
TIME 格式为HH:MM:SS
YEAR 用2位数字表示,范围是1970~2069年,用4位数字表示,范围是1901~2155年。
二进制数据类型
BLOB 最大长度为64KB
MEDIUMBLOB 最大长度为16MB
LONGBLOB 最大长度为4GB
TINYBLOB 最大长度为255字节
更改口令,可使用SET PASSWORD语句
SET PASSWORD FOR ben = Password('safsdf'); 新口令必须传递到Password()函数进行加密
如果不指定用户名时,SET PASSWORD更新当前登录用户的口令。
修改数据库密码
进入mysql配置文件:在[mysqld]下添加skip-grant-tables,保存即可。
重启mysql
输入mysql -uroot -p 一直回车进入
然后use mysql;
update user set authentication_string=password("新密码") where user='root';
flush privileges;
即可
update user set authentication_string=password("qazxswert") where user='root';
flush privileges;
新版数据库需要使用authentication_string
实现远程连接数据库 :
use mysql;
grant all privileges on *.* to root@'10.10.23.%' identified by "password";
// 允许10.10.23.%系列主机访问
flush privileges;
// mysql:8.+修改远程连接
授权root用户可以远程登陆 GRANT ALL ON *.* TO 'root'@'%';
修改加密规则:ALTER USER 'root'@'localhost' IDENTIFIED BY 'yourpassword' PASSWORD EXPIRE NEVER;
更新root用户密码:ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'yourpassword';
刷新权限 FLUSH PRIVILEGES;
grant all privileges on . to root@'%' identified by "qazxswert";