MySQL常用SQL语句语法(二)
时间:2009-06-11 来源:liyf0371
13.2. 数据操作语句
13.2.1. DELETE语法 13.2.2. DO语法 13.2.3. HANDLER语法 13.2.4. INSERT语法 13.2.5. LOAD DATA INFILE语法 13.2.6. REPLACE语法 13.2.7. SELECT语法 13.2.8. Subquery语法 13.2.9. TRUNCATE语法 13.2.10. UPDATE语法13.2.1. DELETE语法
单表语法:
DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name
[WHERE where_definition]
[ORDER BY ...]
[LIMIT row_count]
多表语法:
DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
tbl_name[.*] [, tbl_name[.*] ...]
FROM table_references
[WHERE where_definition]
或:
DELETE [LOW_PRIORITY] [QUICK] [IGNORE]
FROM tbl_name[.*] [, tbl_name[.*] ...]
USING table_references
[WHERE where_definition]
tbl_name中有些行满足由where_definition给定的条件。DELETE用于删除这些行,并返回被删除的记录的数目。
如果您编写的DELETE语句中没有WHERE子句,则所有的行都被删除。当您不想知道被删除的行的数目时,有一个更快的方法,即使用TRUNCATE TABLE。请参见13.2.9节,“TRUNCATE语法”。
如果您删除的行中包括用于AUTO_INCREMENT列的最大值,则该值被重新用于BDB表,但是不会被用于MyISAM表或InnoDB表。如果您在AUTOCOMMIT模式下使用DELETE FROM tbl_name(不含WHERE子句)删除表中的所有行,则对于所有的表类型(除InnoDB和MyISAM外),序列重新编排。对于InnoDB表,此项操作有一些例外,在15.2.6.3节,“AUTO_INCREMENT列如何在InnoDB中运行”中进行了讨论。
对于MyISAM和BDB表,您可以把AUTO_INCREMENT次级列指定到一个多列关键字中。在这种情况下,从序列的顶端被删除的值被再次使用,甚至对于MyISAM表也如此。请参见3.6.9节,“使用AUTO_INCREMENT”。
DELETE语句支持以下修饰符:
· 如果您指定LOW_PRIORITY,则DELETE的执行被延迟,直到没有其它客户端读取本表时再执行。
· 对于MyISAM表,如果您使用QUICK关键词,则在删除过程中,存储引擎不会合并索引端结点,这样可以加快部分种类的删除操作的速度。
· 在删除行的过程中,IGNORE关键词会使MySQL忽略所有的错误。(在分析阶段遇到的错误会以常规方式处理。)由于使用本选项而被忽略的错误会作为警告返回。
删除操作的速度会受到一些因素的影响,这些因素在7.2.18节,“DELETE语句的速度”中进行了讨论。
在MyISAM表中,被删除的记录被保留在一个带链接的清单中,后续的INSERT操作会重新使用旧的记录位置。要重新使用未使用的空间并减小文件的尺寸,则使用OPTIMIZE TABLE语句或myisamchk应用程序重新编排表。OPTIMIZE TABLE更简便,但是myisamchk速度更快。请参见13.5.2.5节,“OPTIMIZE TABLE语法”和第7章:优化。
QUICK修饰符会影响到在删除操作中索引端结点是否合并。当用于被删除的行的索引值被来自后插入的行的相近的索引值代替时,DELETE QUICK最为适用。在此情况下,被删除的值留下来的空穴被重新使用。
未充满的索引块跨越某一个范围的索引值,会再次发生新的插入。当被删除的值导致出现未充满的索引块时,DELETE QUICK没有作用。在此情况下,使用QUICK会导致未利用的索引中出现废弃空间。下面是此种情况的举例说明:
1. 创建一个表,表中包含已编索引的AUTO_INCREMENT列。
2. 在表中插入很多记录。每次插入会产生一个索引值,此索引值被添加到索引的高端处。
3. 使用DELETE QUICK从列的低端处删除一组记录。
在此情况下,与被删除的索引值相关的索引块变成未充满的状态,但是,由于使用了QUICK,这些索引块不会与其它索引块合并。当插入新值时,这些索引块仍为未充满的状态,原因是新记录不含有在被删除的范围内的索引值。另外,即使您此后使用DELETE时不包含QUICK,这些索引块也仍是未充满的,除非被删除的索引值中有一部分碰巧位于这些未充满的块的之中,或与这些块相邻。在这些情况下,如果要重新利用未使用的索引空间,需使用OPTIMIZE TABLE。
如果您打算从一个表中删除许多行,使用DELETE QUICK再加上OPTIMIZE TABLE可以加快速度。这样做可以重新建立索引,而不是进行大量的索引块合并操作。
用于DELETE的MySQL唯一的LIMIT row_count选项用于告知服务器在控制命令被返回到客户端前被删除的行的最大值。本选项用于确保一个DELETE语句不会占用过多的时间。您可以只重复DELETE语句,直到相关行的数目少于LIMIT值为止。
如果DELETE语句包括一个ORDER BY子句,则各行按照子句中指定的顺序进行删除。此子句只在与LIMIT联用是才起作用。例如,以下子句用于查找与WHERE子句对应的行,使用timestamp_column进行分类,并删除第一(最旧的)行:
DELETE FROM somelog
WHERE user = 'jcole'
ORDER BY timestamp_column
LIMIT 1;
您可以在一个DELETE语句中指定多个表,根据多个表中的特定条件,从一个表或多个表中删除行。不过,您不能在一个多表DELETE语句中使用ORDER BY或LIMIT。
table_references部分列出了包含在联合中的表。此语法在13.2.7.1节,“JOIN语法”中进行了说明。
对于第一个语法,只删除列于FROM子句之前的表中的对应的行。对于第二个语法,只删除列于FROM子句之中(在USING子句之前)的表中的对应的行。作用是,您可以同时删除许多个表中的行,并使用其它的表进行搜索:
DELETE t1, t2 FROM t1, t2, t3 WHERE t1.id=t2.id AND t2.id=t3.id;
或:
DELETE FROM t1, t2 USING t1, t2, t3 WHERE t1.id=t2.id AND t2.id=t3.id;
当搜索待删除的行时,这些语句使用所有三个表,但是只从表t1和表t2中删除对应的行。
以上例子显示了使用逗号操作符的内部联合,但是多表DELETE语句可以使用SELECT语句中允许的所有类型的联合,比如LEFT JOIN。
本语法允许在名称后面加.*,以便与Access相容。
如果您使用的多表DELETE语句包括InnoDB表,并且这些表受外键的限制,则MySQL优化程序会对表进行处理,改变原来的从属关系。在这种情况下,该语句出现错误并返回到前面的步骤。要避免此错误,您应该从单一表中删除,并依靠InnoDB提供的ON DELETE功能,对其它表进行相应的修改。
注释:当引用表名称时,您必须使用别名(如果已给定):
DELETE t1 FROM test AS t1, test2 WHERE ...
进行多表删除时支持跨数据库删除,但是在此情况下,您在引用表时不能使用别名。举例说明:
DELETE test1.tmp1, test2.tmp2 FROM test1.tmp1, test2.tmp2 WHERE ...
目前,您不能从一个表中删除,同时又在子查询中从同一个表中选择。
13.2.2. DO语法
DO expr [, expr] ...DO用于执行表达式,但是不返回任何结果。DO是SELECT expr的简化表达方式。DO有一个优势,就是如果您不太关心结果的话,DO的速度稍快。
DO主要用于执行有副作用的函数,比如RELEASE_LOCK()。
13.2.3. HANDLER语法
HANDLER tbl_name OPEN [ AS alias ] HANDLER tbl_name READ index_name { = | >= | <= | < } (value1,value2,...) [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT ... ] HANDLER tbl_name CLOSE
HANDLER语句提供通往表存储引擎接口的直接通道。HANDLER可以用于MyISAM和InnoDB表。
HANDLER...OPEN语句用于打开一个表,通过后续的HANDLER...READ语句建立读取表的通道。本表目标不会被其它线程共享,也不会关闭,直到线程调用HANDLER...CLOSE或线程中止时为止。如果您使用一个别名打开表,则使用其它HANDLER语句进一步参阅表是必须使用此别名,而不能使用表名。
如果被指定的索引满足给定的值并且符合了WHERE条件,则第一个HANDLER...READ语法取出一行。如果您有一个多列索引,则指定索引列值为一个用逗号隔开的清单。既可以为索引中的所有列指定值,也可以为索引列的最左边的前缀指定值。假设一个索引包括三个列,名称为col_a, col_b,和col_c,并按此顺序排列。HANDLER语句可以为索引中的所有三个列指定值,或者为一个最左边前缀中的各列指定值。举例说明:
HANDLER ... index_name = (col_a_val,col_b_val,col_c_val) ...
HANDLER ... index_name = (col_a_val,col_b_val) ...
HANDLER ... index_name = (col_a_val) ...
第二个HANDLER...READ语法按索引的顺序从表中取出一行。索引的顺序符合WHERE条件。
第三个HANDLER...READ语法按自然行的顺序从表中取出一行。自然行的顺序符合WHERE条件。当想要对整个表进行扫描时,此语句比HANDLER tbl_name READ index_name更快。自然行的顺序指的是行存储在MyISAM表数据文件的顺序。本语句也适用于InnoDB表,但是因为没有独立的数据文件,所以没有这类概念。
不使用LIMIT子句时,所有形式的HANDLER...READ语句均只取出一行。 如果要返回多个行,应加入一个LIMIT子句。本语句于SELECT语句的语法一样。请参见13.2.7节,“SELECT语法”。
HANDLER...CLOSE用于关闭使用HANDLER...OPEN打开的表。
注释:要使用HANDLER接口来查阅一个表的PRIMARY KEY,应使用带引号的识别符`PRIMARY`:
HANDLER tbl_name READ `PRIMARY` > (...);
HANDLER是比较低级别的语句。例如,它不能提供一致性。也就是说,HANDLER...OPEN不能为表做快照,也不能锁定表。这意味着,当一个HANDLER...OPEN语句被编写后,表数据可以被更改(用此线程或用其它线程),并且这些更改只会部分地出现在HANDLER...NEXT或HANDLER...PREV扫描中。
使用HANDLER接口代替常规的SELECT语句有多个原因:
· HANDLER比SELECT更快:
o 一个指定的存储引擎管理程序目标为了HANDLER...OPEN进行整序。该目标被重新用于该表的后续的HANDLER语句;不需要对每个语句进行重新初始化。
o 涉及的分析较少。
o 没有优化程序或查询校验开销。
o 在两个管理程序请求之间,不需要锁定表。
o 管理程序接口不需要提供外观一致的数据(例如,允许无条理的读取),所以存储引擎可以使用优化,而SELECT通常不允许使用优化。
· 有些应用程序使用与ISAM近似的接口与MySQL连接。使用HANDLER可以更容易地与这些应用程序连接。
· HANDLER允许您采用一种特殊的方式进出数据库。而使用SELECT时难以采用(或不可能采用)这种方式。有些应用程序可以提供一个交互式的用户接口与数据库连接。当与这些应用程序同时使用时,用HANDLER接口观看数据更加自然。
13.2.4. INSERT语法
13.2.4.1. INSERT ... SELECT语法 13.2.4.2. INSERT DELAYED语法INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
VALUES ({expr | DEFAULT},...),(...),...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]
或:
INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name
SET col_name={expr | DEFAULT}, ...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]
或:
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]
INSERT用于向一个已有的表中插入新行。INSERT...VALUES和INSERT...SET形式的语句根据明确指定的值插入行。INSERT...SELECT形式的语句插入从其它表中选出的行。在13.2.4.1节,“INSERT ... SELECT语法”中对INSERT...SELECT进行了进一步的讨论。
行应被插入到tbl_name表中。可以按以下方法指定列。本语句向这些列提供值。
· 列名称清单或SET子句明确的指示了列。
· 如果您不为INSERT...VALUES或INSERT...SELECT指定列的清单,则表中每列的值必须在VALUES清单中提供,或由SELECT提供。如果您不知道表中各列的顺序,则使用DESCRIBE tbl_name查询。
列值可以采用多种方法给定:
· 如果不是在严格模式下运行,则所有没有明确给定值的列都被设置为默认值(明确的或隐含的)。例如,如果您指定了一个列清单,但此清单没有对表中所有的列进行命名,则未命名的各列被设置为默认值。默认值的赋值在13.1.5节,“CREATE TABLE语法”中进行了说明。也可参见1.8.6.2节,“对无效数据的约束”。
有时候,您需要对所有没有默认值的列明确地指定值。如果您希望,在没有明确指定值时,INSERT语句可以生成错误信息,则您应该使用STRICT模式。请参见5.3.2节,“SQL服务器模式”。
· 使用关键词DEFAULT,明确地把列设置为默认值。这样,编写向所有列赋值的INSERT语句时可以更容易,因为使用DEFAULT可以避免编写出不完整的、未包含全部列值的VALUES清单。如果不使用DEFUALT,您必须编写一个列名称清单,与VALUES清单中的每个值对应。
您还可以使用DEFAULT(col_name)作为一种更通用的形式,在表达式中使用,用于生成一个列的默认值。
· 如果列清单和VALUES清单均为空清单,则INSERT会创建一个行,每个列都被设置为默认值:
· mysql> INSERT INTO tbl_name () VALUES();
在STRICT模式中,如果有一列没有默认值,则会出现错误。或者,MySQL会对所有没有明确定义默认值的列使用隐含的默认值。
· 您可以指定一个表达式expr来提供一个列值。如果表达式的类型与列值不匹配,这样做会造成类型转化。并且,给定值的转化会导致不同的插入值,插入何值由列类型而定。例如,向一个INT, FLOAT, DECIMAL(10,6)或YEAR列插入字符串'1999.0e-2',插入值分别是1999,19.9921,19.992100和1999。存储在INT和YEAR列中的值为1999的原因是,在从字符串到整数的转化中,只把字符串的前面部分看作有效的整数或年份。对于浮点列和固定点列,在从字符串到浮点的转化中,把整个字符串均看作有效的浮点值。
表达式expr可以引用在值清单中已设置的所有列。例如,您可以这么操作,因为用于col2的值引用了col1,而col1已经被赋值:
mysql> INSERT INTO tbl_name (col1,col2) VALUES(15,col1*2);
但是以下语句不合法,因为用于col1的值引用了col2,而col2在col1之后被赋值:
mysql> INSERT INTO tbl_name (col1,col2) VALUES(col2*2,15);
有一种例外情况,那就是含有AUTO_INCREMENT值的列。因为AUTO_INCREMENT值在其它值赋值之后被生成,所以任何在赋值时对AUTO_INCREMENT列的引用都会返回0。
INSERT语句支持下列修改符:
· 如果您使用DELAYED关键字,则服务器会把待插入的行放到一个缓冲器中,而发送INSERT DELAYED语句的客户端会继续运行。如果表正在被使用,则服务器会保留这些行。当表空闲时,服务器开始插入行,并定期检查是否有新的读取请求。如果有新的读取请求,则被延迟的行被延缓执行,直到表再次空闲时为止。请参见13.2.4.2节,“INSERT DELAYED语法”。
· 如果您使用LOW_PRIORITY关键词,则INSERT的执行被延迟,直到没有其它客户端从表中读取为止。当原有客户端正在读取时,有些客户端刚开始读取。这些客户端也被包括在内。此时,INSERT LOW_PRIORITY语句等候。因此,在读取量很大的情况下,发出INSERT LOW_PRIORITY语句的客户端有可能需要等待很长一段时间(甚至是永远等待下去)。(这与INSERT DELAYED形成对比,INSERT DELAYED立刻让客户端继续执行。请参见13.2.4.2节,“INSERT DELAYED语法”。)注意LOW_PRIORITY通常不应用于MyISAM表,因为这么做会取消同时进行的插入。请参见15.1节,“MyISAM存储引擎”。
· 如果您指定了HIGH_PRIORITY,同时服务器采用--low-priority-updates选项启动,则HIGH_PRIORITY将覆盖--low-priority-updates选项。这么做还会导致同时进行的插入被取消。
· 使用mysql_affected_rows() C API函数,可以获得用于INSERT的受影响行的值。请参见25.2.3.1节,“mysql_affected_rows()”。
· 如果您在一个INSERT语句中使用IGNORE关键词,在执行语句时出现的错误被当作警告处理。例如,没有使用IGNORE时,如果一个行复制了原有的UNIQUE索引或PRIMARY KEY值,会导致出现重复关键字错误,语句执行失败。使用IGNORE时,该行仍然未被插入,但是不会出现错误。IGNORE未被指定时,如果数据转化引发错误,则会使语句执行失败。使用IGNORE后,无效数据被调整到最接近的值,并被插入;此时,生成警告,但是语句执行不会失败。您可以使用mysql_info() C API函数测定有多少行被插入到表中。
如果您指定了ON DUPLICATE KEY UPDATE,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则执行旧行UPDATE。例如,如果列a被定义为UNIQUE,并且包含值1,则以下两个语句具有相同的效果:
mysql> INSERT INTO table (a,b,c) VALUES (1,2,3)
-> ON DUPLICATE KEY UPDATE c=c+1;
mysql> UPDATE table SET c=c+1 WHERE a=1;
如果行作为新记录被插入,则受影响行的值为1;如果原有的记录被更新,则受影响行的值为2。
注释:如果列b也是唯一列,则INSERT与此UPDATE语句相当:
mysql> UPDATE table SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;
如果a=1 OR b=2与多个行向匹配,则只有一个行被更新。通常,您应该尽量避免对带有多个唯一关键字的表使用ON DUPLICATE KEY子句。
您可以在UPDATE子句中使用VALUES(col_name)函数从INSERT...UPDATE语句的INSERT部分引用列值。换句话说,如果没有发生重复关键字冲突,则UPDATE子句中的VALUES(col_name)可以引用被插入的col_name的值。本函数特别适用于多行插入。VALUES()函数只在INSERT...UPDATE语句中有意义,其它时候会返回NULL。
示例:
mysql> INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)
-> ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);
本语句与以下两个语句作用相同:
mysql> INSERT INTO table (a,b,c) VALUES (1,2,3)
-> ON DUPLICATE KEY UPDATE c=3;
mysql> INSERT INTO table (a,b,c) VALUES (4,5,6)
-> ON DUPLICATE KEY UPDATE c=9;
当您使用ON DUPLICATE KEY UPDATE时,DELAYED选项被忽略。
您可以使用SQL LAST_INSERT_ID()函数查找用于AUTO_INCREMENT列的值。从C API的内部,使用mysql_insert_id()函数。不过,您应该注意,两个函数的作用并不总是相同的。在12.9.3节,“信息函数”和25.2.3.36节,“mysql_insert_id()”中进一步讨论了与AUTO_INCREMENT列有关的INSERT语句的作用。
如果您使用INSERT...VALUES语句时采用了多个值清单或INSERT...SELECT,则该语句按以下格式返回一个信息字符串:
Records: 100 Duplicates: 0 Warnings: 0
记录指示了经过语句处理的行的数目。(因为重复数目可以不是零,所以该数目不一定是实际被插入的行的数目。)重复数目指的是不能被插入的行的数目,因为这些行会复制部分原有的唯一索引值。警告指的是插入有错误或有问题的列值的次数。在以下情况下会出现警告:
· 向一个已定义为NOT NULL的列中插入NULL。对于一个多行INSERT语句或INSERT INTO...SELECT语句,根据列数据的类型,列被设置为隐含的默认值。对于数字类型,默认值为0;对于字符串类型,默认值为空字符串('');对于日期和时间类型,默认值为“zero”值。对INSERT INTO...SELECT语句的处理方法与对多行插入的处理方法一样,因为服务器不能检测来自SELECT的结果,不能判断是否返回单一行。(对于单一行INSERT,当NULL被插入一个NOT NULL列时,不会出现警告,而是出现错误,并且语句运行失败。)
· 数字列的值被设置在列的值范围之外。此值被修改为未最接近的值范围端点。
· 向一个数字列赋予一个例如'10.34 a'的值。尾部的非数字文本被删节,其余的数字部分被插入,如果字符串值没有前导的数字部分,则该列被设置为0。
· 向一个字符串列(CHAR, VARCHAR, TEXT或BLOB)中插入的字符串超过了列的最大长度。此值被删节到列的最大长度。
· 向日期或时间列中插入的值对于该列的类型是不合法的。根据列的类型,该列被设置到相应的零值。
如果您正在使用C API,则可以通过调用mysql_info()函数获取信息字符串。请参见25.2.3.34节,“mysql_info()”。
13.2.4.1. INSERT ... SELECT语法
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]
使用INSERT...SELECT,您可以快速地从一个或多个表中向一个表中插入多个行。
示例:
INSERT INTO tbl_temp2 (fld_id)
SELECT tbl_temp1.fld_order_id
FROM tbl_temp1 WHERE tbl_temp1.fld_order_id > 100;
使用INSERT...SELECT语句时会出现以下情况:
· 明确地指定IGNORE,用于忽略会导致重复关键字错误的记录。
· 不要同时使用DELAYED和INSERT...SELECT。
· INSERT语句的目标表会显示在查询的SELECT部分的FROM子句中。(在有些旧版本的MySQL中不会出现这种情况。)
· AUTO_INCREMENT列照常运行。
· 为了确保二进制日志可以被用于再次创建原表,MySQL不允许在INSERT...SELECT运行期间同时进行插入操作。
· 目前,您不能在向一个表插入的同时,又在一个子查询中从同一个表中选择。
在ON DUPLICATE KEY UPDATE的值部分中,只要您不使用SELECT部分中的GROUP BY,您就可以引用在其它表中的列。有一个副作用是,您必须使值部分中的非唯一列的名称符合要求。
您可以使用REPLACE替代INSERT,来覆盖旧行。对于包含唯一关键字值,并复制了旧行的新行,在进行处理时,REPLACE可以作为INSERT IGNORE的同类子句:新行被用于替换旧行,而不是被丢弃。
13.2.4.2. INSERT DELAYED语法
INSERT DELAYED ...
用于INSERT语句的DELAYED选项是MySQL相对于标准SQL的扩展。如果您的客户端不能等待INSERT完成,则这个选项是非常有用的。当您使用MySQL进行日志编写时,这是非常常见的问题。您也可以定期运行SELECT和UPDATE语句,这些语句花费的时间较长。
当一个客户端使用INSERT DELAYED时,会立刻从服务器处得到一个确定。并且行被排入队列,当表没有被其它线程使用时,此行被插入。
使用INSERT DELAYED的另一个重要的好处是,来自许多客户端的插入被集中在一起,并被编写入一个块。这比执行许多独立的插入要快很多。
使用DELAYED时有一些限制:
· INSERT DELAYED仅适用于MyISAM, MEMORY和ARCHIVE表。对于MyISAM表,如果在数据文件的中间没有空闲的块,则支持同时采用SELECT和INSERT语句。在这些情况下,基本不需要对MyISAM使用INSERT DELAYED。请参见15.1节,“MyISAM存储引擎”, 15.4节,“MEMORY (HEAP)存储引擎”和15.8节,“ARCHIVE存储引擎”。
· INSERT DELAYED应该仅用于指定值清单的INSERT语句。服务器忽略用于INSERT DELAYED...SELECT语句的DELAYED。
· 服务器忽略用于INSERT DELAYED...ON DUPLICATE UPDATE语句的DELAYED。
· 因为在行被插入前,语句立刻返回,所以您不能使用LAST_INSERT_ID()来获取AUTO_INCREMENT值。AUTO_INCREMENT值可能由语句生成。
· 对于SELECT语句,DELAYED行不可见,直到这些行确实被插入了为止。
· DELAYED在从属复制服务器中被忽略了,因为DELAYED不会在从属服务器中产生与主服务器不一样的数据。
注意,目前在队列中的各行只保存在存储器中,直到它们被插入到表中为止。这意味着,如果您强行中止了mysqld(例如,使用kill -9)或者如果mysqld意外停止,则所有没有被写入磁盘的行都会丢失。
以下详细描述了当您对INSERT或REPLACE使用DELAYED选项时会发生什么情况。在这些描述中,“线程”指的是已接受了一个INSERT DELAYED语句的线程,“管理程序”指的是为某个特定的表处理所有INSERT DELAYED语句的线程。
· 当一个线程对一个表执行DELAYED语句时,会创建出一个管理程序线程(如果原来不存在),对用于本表的所有DELAYED语句进行处理。
· 线程会检查是否管理程序以前已获取了DELAYED锁定;如果没有获取,则告知管理程序线程进行此项操作。即使其它线程对表有READ或WRITE锁定,也可以获得DELAYED锁定。但是管理程序会等待所有的ALTER TABLE锁定或FLUSH TABLE锁定,以确保表的结构是最新的。
· 线程执行INSERT语句,但不是把行写入表中,而是把最终行的拷贝放入一个由管理程序线程管理的队列中。线程会提示出现语法错误,这些错误会被报告到客户端中。
· 因为在插入操作之前,INSERT返回已经完成,所以客户端不能从服务器处获取重复记录的数目,也不能获取生成的行的AUTO_INCREMENT值。(如果您使用C API,则出于同样的原因,mysql_info()函数不会返回任何有意义的东西。)
· 当行被插入表中时,二进制日志被管理程序线程更新。在多行插入情况下,当第一行被插入时,二进制日志被更新。
· 每次delayed_insert_limit行被编写时,管理程序会检查是否有SELECT语句仍然未执行。如果有,则会在继续运行前,让这些语句先执行。
· 当管理程序的队列中没有多余的行时,表被解锁。如果在delayed_insert_timeout时间内,没有接收到新的INSERT DELAYED语句,则管理程序中止。
· 如果在某个特定的管理程序队列中,有超过delayed_queue_size的行未被执行,则申请INSERT DELAYED的线程会等待,直到队列中出现空间为止。这么做可以确保mysqld不会把所有的存储器都用于被延迟的存储队列。
· 管理程序线程会显示在MySQL进程清单中,其命令列中包含delayed_insert。如果您执行一个FLUSH TABLES语句或使用KILL thread_id进行删除,则会删除此线程。不过,在退出前,线程会首先把所有排入队列的行存储到表中。在这期间,该线程不会从其它线程处接受任何新的INSERT语句。如果您在此之后执行一个INSERT DELAYED语句,则会创建出一个新的管理程序线程。
注意,如果有一个INSERT DELAYED管理程序正在运行,则这意味着INSERT DELAYED语句比常规的INSERT语句具有更高的优先权。其它更新语句必须等待,直到INSERT DELAYED语句队列都运行完毕,或者管理程序线程被中止(使用KILL thread_id),或者执行了一个FLUSH TABLES时为止。
· 以下状态变量提供了有关INSERT DELAYED语句的信息:
状态变量 |
意义 |
Delayed_insert_threads |
管理程序线程的数目 |
Delayed_writes |
使用INSERT DELAYED写入的行的数目 |
Not_flushed_delayed_rows |
等待被写入的行的数目 |
· 您可以通过发送一个SHOW STATUS语句,或者执行一个mysqladmin extended-status命令,来阅览这些变量。
注意,当没有使用表时,INSERT DELAYED比常规的INSERT要慢。对于服务器来说,为每个含有延迟行的表操纵一个独立的线程,也是一个额外的系统开销。这意味着只有当您确认您需要时,才应使用INSERT DELAYED。
13.2.5. LOAD DATA INFILE语法
LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name.txt'
[REPLACE | IGNORE]
INTO TABLE tbl_name
[FIELDS
[TERMINATED BY 'string']
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char' ]
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
[IGNORE number LINES]
[(col_name_or_user_var,...)]
[SET col_name = expr,...)]
LOAD DATA INFILE语句用于高速地从一个文本文件中读取行,并装入一个表中。文件名称必须为一个文字字符串。
要了解有关INSERT和LOAD DATA INFILE的效率的对比和有关LOAD DATA INFILE加速的更多信息,请参见7.2.16节,“INSERT语句的速度”。
由character_set_database系统变量指示的字符集被用于解释文件中的信息。SET NAMES和character_set_client的设置不会影响对输入的解释。
注意,目前不能载入UCS2数据文件。
您也可以通过使用mysqlimport应用程序载入数据文件;通过向服务器发送一个LOAD DATA INFILE语句实现此功能。--local选项用于使mysqlimport从客户主机中读取数据文件。如果客户端和服务器支持压缩协议,则您可以指定—compress选项提高在慢速网络中的性能。请参见8.10节,“mysqlimport:数据导入程序。
如果您使用LOW_PRIORITY,则LOAD DATA语句的执行被延迟,直到没有其它的客户端从表中读取为止。
如果一个MyISAM表满足同时插入的条件(即该表在中间有空闲块),并且您对这个MyISAM表指定了CONCURRENT,则当LOAD DATA正在执行时,其它线程会从表中重新获取数据。即使没有其它线程在同时使用本表格,使用本选项也会略微影响LOAD DATA的性能。
如果指定了LOCAL,则被认为与连接的客户端有关:
· 如果指定了LOCAL,则文件会被客户主机上的客户端读取,并被发送到服务器。文件会被给予一个完整的路径名称,以指定确切的位置。如果给定的是一个相对的路径名称,则此名称会被理解为相对于启动客户端时所在的目录。
· 如果LOCAL没有被指定,则文件必须位于服务器主机上,并且被服务器直接读取。
当在服务器主机上为文件定位时,服务器使用以下规则:
· 如果给定了一个绝对的路径名称,则服务器使用此路径名称。
· 如果给定了带有一个或多个引导组件的相对路径名称,则服务器会搜索相对于服务器数据目录的文件。
· 如果给定了一个不带引导组件的文件名称,则服务器会在默认数据库的数据库目录中寻找文件。
注意,这些规则意味着名为./myfile.txt的文件会从服务器数据目录中被读取,而名为myfile.txt的同样的文件会从默认数据库的数据库目录中读取。例如,下面的LOAD DATA语句会从db1数据库目录中读取文件data.txt,因为db1是当前数据库。即使语句明确把文件载入到db2数据库中的表里,也会从db1目录中读取。
mysql> USE db1;
mysql> LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;
注意,使用正斜杠指定Windows路径名称,而不是使用反斜杠。如果您使用反斜杠,您必须使用两个。
出于安全原因,当读取位于服务器中的文本文件时,文件必须位于数据库目录中,或者是全体可读的。另外,要对服务器文件使用LOAD DATA INFILE,您必须拥有FILE权限。
见5.7.3节,“MySQL提供的权限”。
与让服务器直接读取文件相比,使用LOCAL速度略慢,这是因为文件的内容必须通过客户端发送到服务器上。不过,您不需要FILE权限来载入本地文件。
只有当您的服务器和您的客户端都许可时,LOCAL才可运行。例如,如果使用—local-infile=0启动mysqld,则LOCAL不运行。请参见5.6.4节,“LOAD DATA LOCAL安全问题”。
如果您需要LOAD DATA来从一个管道中读取,您可以使用以下方法(此处我们把/目录清单载入一个表格):
mkfifo /mysql/db/x/x
chmod 666 /mysql/db/x/x
find / -ls > /mysql/db/x/x
mysql -e "LOAD DATA INFILE 'x' INTO TABLE x" x
有些输入记录把原有的记录复制到唯一关键字值上。REPLACE和IGNORE关键字用于控制这些输入记录的操作。
如果您指定了REPLACE,则输入行会替换原有行(换句话说,与原有行一样,对一个主索引或唯一索引具有相同值的行)。请参见13.2.6节,“REPLACE语法”。
如果您指定IGNORE,则把原有行复制到唯一关键字值的输入行被跳过。如果您这两个选项都不指定,则运行情况根据LOCAL关键词是否被指定而定。不使用LOCAL时,当出现重复关键字值时,会发生错误,并且剩下的文本文件被忽略。使用LOCAL时,默认的运行情况和IGNORE被指定时的情况相同;这是因为在运行中间,服务器没有办法中止文件的传输。
如果您希望在载入运行过程中忽略外键的限制,您可以在执行LOAD DATA前发送一个SET FOREIGN_KEY_CHECKS=0语句。
如果您对一个空的MyISAM表使用LOAD DATA INFILE,则所有的非唯一索引会被创建在一个独立批中(对于REPAIR TABLE)。当您有许多索引时,这通常会使LOAD DATA INFILE大大加快。通常,LOAD DATA INFILE的速度会非常快,但是在某些极端情况下,您可以在把文件载入到表中之前使用ALTER TABLE...DISABLE KEYS关闭LOAD DATA INFILE,或者在载入文件之后使用ALTER TABLE...ENABLE KEYS再次创建索引,使创建索引的速度更快。请参见7.2.16节,“INSERT语句的速度”。
LOAD DATA INFILE是SELECT...INTO OUTFILE的补语。(见13.2.7节,“SELECT语法”。)要从一个表中把数据写入一个文件中,应使用SELECT...INTO OUTFILE。要读取文件,放回到表中,应使用LOAD DATA INFILE。FIELDS和LINES子句的语法对于两个语句是一样的。两个子句都是自选的,但是如果两个都被指定了,FIELDS必须位于LINES的前面。
如果您指定了一个FIELDS子句,则每个亚子句(TERMINATED BY, [OPTIONALLY] ENCLOSED BY和ESCAPED BY)也是自选的。不过,您必须指定其中至少一个。
如果您不指定FIELDS子句,则默认值为假设您写下如下语句时的值:
FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\'
如果您不指定LINES子句,则默认值为假设您写下如下语句时的值:
LINES TERMINATED BY '\n' STARTING BY ''
换句话说,当读取输入值时,默认值会使LOAD DATA INFILE按如下方式运行:
· 在新行处寻找行的边界。
· 不会跳过任何行前缀。
· 在制表符处把行分解为字段。
· 不希望字段被包含在任何引号字符之中。
· 出现制表符、新行、或在‘\’前有‘\’时,理解为作为字段值一部分的文字字符。
相反的,当编写输出值时,默认值会使SELECT...INTO OUTFILE按如下方式运行:
· 在字段之间写入制表符。
· 不把字段包含在任何引号字符中。
· 当字段值中出现制表符、新行或‘\’时,使用‘\’进行转义。
· 在行的末端写入新行。
注意,要写入FIELDS ESCAPED BY ‘\\’,您必须为待读取的值指定两个反斜杠,作为一个单反斜杠使用。
注释:如果您已经在Windows系统中生成了文本文件,您可能必须使用LINES TERMINATED BY ‘\r\n’来正确地读取文件,因为Windows程序通常使用两个字符作为一个行终止符。部分程序,比如WordPad,当编写文件时,可能会使用\r作为行终止符。要读取这样的文件,应使用LINES TERMINATED BY ‘\r’。
如果所有您希望读入的行都含有一个您希望忽略的共用前缀,则您可以使用'prefix_string'来跳过前缀(和前缀前的字符)。如果某行不包括前缀,则整个行被跳过。注释:prefix_string会出现在一行的中间。
示例:
mysql> LOAD DATA INFILE '/tmp/test.txt'
-> INTO TABLE test LINES STARTING BY "xxx";
使用此语句,您可以读入包含有如下内容的文件:
xxx"row",1
something xxx"row",2
并只得到数据("row",1)和("row",2)。
IGNORE number LINES选项可以被用于在文件的开始处忽略行。例如,您可以使用IGNORE 1 LINES来跳过一个包含列名称的起始标题行:
mysql> LOAD DATA INFILE '/tmp/test.txt'
-> INTO TABLE test IGNORE 1 LINES;
当您联合使用SELECT...INTO OUTFILE和LOAD DATA INFILE来从一个数据库中把数据写入一个文件中,然后再读取文件,返回到数据库中时,用于两个语句的field-和line-handling选项必须匹配。否则,LOAD DATA INFILE不会正确地理解文件的内容。假设您使用SELECT...INTO OUTFILE来编写一个的文件,字段由逗号分隔:
mysql> SELECT * INTO OUTFILE 'data.txt'
-> FIELDS TERMINATED BY ','
-> FROM table2;
要读取由逗号分隔的文件并返回,则正确的语句应该是:
mysql> LOAD DATA INFILE 'data.txt' INTO TABLE table2
-> FIELDS TERMINATED BY ',';
如果您尝试使用以下所示的语句读入文件,则不会运行,因为该语句命令LOAD DATA INFILE寻找位于字段之间的制表符:
mysql> LOAD DATA INFILE 'data.txt' INTO TABLE table2
-> FIELDS TERMINATED BY '\t';
结果很可能是,每个输入行被理解为一个单一字段。
LOAD DATA INFILE也可以被用于读取从外源中获取的文件。例如,一个dBASE格式的文件具有以逗号分隔并且包含在双引号中的字段。如果文件中的各行以新行为结尾,则此处所示的语句描述了您可以用于载入文件的field-和line-handling选项:
mysql> LOAD DATA INFILE 'data.txt' INTO TABLE tbl_name
-> FIELDS TERMINATED BY ',' ENCLOSED BY '"'
-> LINES TERMINATED BY '\n';
所有field-或line-handling选项都可以指定一个空字符串('')。如果字符串不是空的,则FIELDS [OPTIONALLY] ENCLOSED BY和FIELDS ESCAPED BY值必须为单一字符。FIELDS TERMINATED BY, LINES STARTING BY和LINES TERMINATED BY值可以超过一个字符。例如,要编写由回车/换行成对字符作为结尾的行,或读取包含这类行的文件,则应指定一个LINES TERMINATED BY ‘\r\n’子句。
如果jokes被由%%组成的行分隔,要读取包含jokes的文件,您可以这么操作:
mysql> CREATE TABLE jokes
-> (a INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> joke TEXT NOT NULL);
mysql> LOAD DATA INFILE '/tmp/jokes.txt' INTO TABLE jokes
-> FIELDS TERMINATED BY ''
-> LINES TERMINATED BY '\n%%\n' (joke);
FIELDS [OPTIONALLY] ENCLOSED BY用于控制字段的引号。对于(SELECT...INTO OUTFILE),如果您忽略了词语OPTIONALLY,则所有的字段都被包含在ENCLOSED BY字符串中。此处展示了此类输出的一个示例(使用逗号作为字段分隔符):
"1","a string","100.20"
"2","a string containing a , comma","102.20"
"3","a string containing a \" quote","102.20"
"4","a string containing a \", quote and comma","102.20"
如果您指定了OPTINALLY,则ENCLOSED BY字符只被用于包含具有字符串数据类型(比如CHAR, BINARY, TEXT或ENUM)的列中的值:
1,"a string",100.20
2,"a string containing a , comma",102.20
3,"a string containing a \" quote",102.20
4,"a string containing a \", quote and comma",102.20
注意,如果在字段值内出现ENCLOSED BY字符,则通过使用ESCAPED BY字符作为前缀,对ENCLOSED BY字符进行转义。另外,要注意,如果您指定了一个空的ESCAPED BY值,则可能会生成不能被LOAD DATA INFILE正确读取的输出值。例如,如果转义符为空字符,则刚显示的先前输出值应显示如下。请观察,第四行中的第二个字段在引号后面包含一个逗号,该引号(错误地)显示出来,作为字段的结尾:
1,"a string",100.20
2,"a string containing a , comma",102.20
3,"a string containing a " quote",102.20
4,"a string containing a ", quote and comma",102.20
对于输入值,ENCLOSED BY字符被从字段字的末尾剥离。(不论OPTIONALLY是否被指定都会剥离;OPTIONALLY对输入值的解释没有影响。)如果ENCLOSED BY字符前面带有ESCAPED BY字符,则被理解为当前字段值的一部分。
如果字段以ENCLOSED BY字符为开始,当出现这类字符时,只有后面接着字段或行TERMINATED BY序列时,这类字符被认为是一个字段值的结尾。为了避免意思不明确,当在一个字段值中出现ENCLOSED BY字符时,此字符可以重复书写,并被理解为单一的字符。例如,如果指定了ENCLOSED BY '"',则按照以下方法操作引号:
"The ""BIG"" boss" -> The "BIG" boss
The "BIG" boss -> The "BIG" boss
The ""BIG"" boss -> The ""BIG"" boss
FIELDS ESCAPED BY用于控制如何写入或读取特殊字符。如果FIELDS ESCAPED BY字符不是空字符,则可以在输出中用于对以下字符加前缀:
· FIELDS ESCAPED BY字符
· FIELDS [OPTIONALLY] ENCLOSED BY字符
· FIELDS TERMINATED BY和LINES TERMINATED BY值的第一个字符
· ASCII 0(在转义符之后编写的字符实际上是ASCII‘0’,而不是一个值为0的字节)
如果FIELDS ESCAPED BY字符为空字符,则没有字符被转义,并且NULL被作为NULL输出,而不是\N。去指定一个空的转义符不是一个好办法,特别是如果数据的字段值包含任何刚给定的清单中的字符时,更不能这么做。
对于输入值,如果FIELDS ESCAPED BY字符不是空字符,则出现这种字符时会被剥离,然后以下字符被作为字段值的一部分。例外情况是,被转义的‘0’或‘N’(例如,\0或\N,此时转义符为‘\’)。这些序列被理解为ASCII NUL(一个零值字节)和NULL。用于NULL处理的规则在本节的后部进行说明。
要了解有关‘\’-escape语法的更多信息,请参见9.1节,“文字值”。
在特定情况下,field-和line-handling选项相互影响:
· 如果LINES TERMINATED BY是空字符串,并且FIELDS TERMINATED BY不是空字符串,则各行以FIELDS TERMINATED BY作为结尾。
· 如果FIELDS TERMINATED BY和FIELDS ENCLOSED BY值均为空值(''),则使用固定行(无分隔)格式。使用固定行格式时,在字段之间不使用分隔符(但是您仍然可以有行终止符)。列值使用列的显示宽度进行写入和读取。例如,如果某列被定义为INT(7),则使用7字符字段写入列值。输出时,通过读取7个字符获取列值。
LINES TERMINATED BY仍然用于分隔行。如果某行不包含所有字段,则其余的各列被设置到默认值。如果您没有行终止符,您应该把终止符设置为''。在此情况下,文本文件必须包含每行的所有字段。
固定行格式也会影响NULL值的操作,这将在以后进行介绍。注意,如果您正在使用一个多字节字符集,则固定规格格式不会运行。
根据正在使用中的FIELDS和LINES选项的不同,NULL值的操作有所变化:
· 对于默认的FIELDS和LINES值,NULL被作为\N的字段值编写,用于输出;\N字段值被作为NULL读取,用于输入(假设ESCAPED BY字符为‘\’)。
· 如果FIELDS ENCLOSED BY不是空值,则包含以文字词语NULL为值的字段被作为NULL值读取。这与被FIELDS ENCLOSED BY字符包围的词语NULL不同。该词语被作为字符串'NULL'读取。
· 如果FIELDS ESCAPED BY是空值,则NULL被作为词语NULL写入。
· 采用固定行格式时(当FIELDS TERMINATED BY和FIELDS ENCLOSED BY均为空值时采用),NULL被作为一个空字符串写入。注意,这会导致在被写入文件时,表中的NULL值和空字符串均无法辨别,这是因为两者都被作为空字符串写入。如果您需要在读取文件并返回时能够分辨两者,则您不应使用固定行格式。
LOAD DATA INFILE不支持有些情况:
· 固定规格行(FIELDS TERMINATED BY和FIELDS ENCLOSED BY均为空值)和BLOB或TEXT列。
· 如果您指定了一个分隔符,并且该分隔符与其它的前缀一样,则LOAD DATA INFILE不能正确地理解输入值。例如,下面的FIELDS子句会导致问题:
· FIELDS TERMINATED BY '"' ENCLOSED BY '"'
· 如果FIELDS ESCAPED BY为空值,则包含FIELDS ENCLOSED BY或LINES TERMINATED BY的字段值后面再接FIELDS TERMINATED BY值会导致LOAD DATA INFILE过早地停止读取一个字段或行。出现这种情况的原因是LOAD DATA INFILE不能正确地决定字段或行值在哪里结束。
以下的例子载入了persondata表中的所有列:
mysql> LOAD DATA INFILE 'persondata.txt' INTO TABLE persondata;
默认情况下,如果在LOAD DATA INFILE语句的末尾处没有设列清单时,则输入行预计会包含一个字段,用于表中的每个列。如果您只想载入一个表的部分列,则应指定一个列清单:
mysql> LOAD DATA INFILE 'persondata.txt'
-> INTO TABLE persondata (col1,col2,...);
如果输入文件中各字段的顺序与表中各列的顺序不同,您也必须指定一个列清单。否则,MySQL不能把输入字段和表中的列匹配起来。
列清单可以包含列名称或用户变量。支持SET子句。这使您可以把输入值赋予用户变量,然后在把结果赋予列之前,对这些值进行变换。
SET子句中的用户变量可以采用多种方式使用。以下例子使用数据文件中的第一列,直接用于t1.column1的值。在用户变量被用于t2.column2值之前,把第二列赋予用户变量。该变量从属于一个分割运行。
LOAD DATA INFILE 'file.txt'
INTO TABLE t1
(column1, @var1)
SET column2 = @var1/100;
SET子句可以被用于提供不是来源于输入文件的值。以下语句把column3设置为当前的日期和时间:
LOAD DATA INFILE 'file.txt'
INTO TABLE t1
(column1, column2)
SET column3 = CURRENT_TIMESTAMP;
您也可以通过把输入值赋予一个用户变量,同时不把变量赋予表中的列,来丢弃此输入值:
LOAD DATA INFILE 'file.txt'
INTO TABLE t1
(column1, @dummy, column2, @dummy, column3);
列/变量清单和SET子句的使用受到以下限定:
· 在SET子句中的赋值应只含有位于赋值操作符的左侧的列名称。
· 您可以在SET赋值的右侧使用子查询。如果子查询可以返回一个值,并且此值将被赋予到一个列中,则此子查询只能是标量子查询。另外,您不能使用子查询从一个正在被载入的表中选择。
· 对于于列/变量清单或SET子句,被IGNORE子句忽略的行不被处理。
· 当载入采用固定行格式的数据时,不能使用用户变量,因为用户变量没有显示宽度。
当处理一个输入行时,LOAD DATA会依据列/变量清单和SET子句,把行拆分成字段,并使用值。然后,得到的行被插入表中。如果有用于表的BEFORE INSERT或AFTER INSERT触发器,则在插入行之前和插入行之后分别启动触发器。
如果一个输入行含有过多的字段,则多余的字段被忽略,并且警告的数量增加。
如果一个输入行含有的字段过少,则输入字段缺失的表中的列被设置为默认值。默认值赋值在13.1.5节,“CREATE TABLE语法”中进行了说明。
如果字段值缺失,则对一个空字段值会被按不同方式理解:
· 对于字符串类型,列被设置为空字符串。
· 对于数字类型,列被设置为0。
· 对于日期和时间类型,列被设置为该类型相应的“zero”。请参见11.3节,“日期和时间类型”。
如果您明确地把一个空字符串赋予一个INSERT或UPDATE语句中的字符串类型、数字类型或日期或时间类型,则产生的这些值相同。
只有在两种情况下TIMESTAMP列被设置为当前日期和时间。一种情况时当列有一个NULL值(也就是\N)时;另一种情况是(仅对于第一个TIMESTAMP列),当一个字段清单被指定时,TIMESTAMP列会从字段清单中被略去。
LOAD DATA INFILE把所有的输入值当作字符串,所以您不能按照使用INSERT语句的方式使用ENUM或SET列的数字值。所有的ENUM和SET值必须被指定为字符串。
当LOAD DATA INFILE语句结束时,会按以下格式返回一个信息字符串:
Records: 1 Deleted: 0 Skipped: 0 Warnings: 0
如果您正在使用C API,您可以通过调用mysql_info()函数获取有关语句的信息。请参见25.2.3.34节,“mysql_info()”。
当值通过INSERT语句被插入时或出现相同情况时,会发生警告(见13.2.4节,“INSERT语法”)。例外情况是,当输入行中字段过多或过少时,LOAD DATA INFILE也生成警告。这些警告并不存储;警告的数量只用于指示运行是否良好。
您可以使用SHOW WARNINGS来得到第一批max_error_count警告的清单,作为有关运行错误的信息。请参见13.5.4.22节,“SHOW WARNINGS语法”。
13.2.6. REPLACE语法
REPLACE [LOW_PRIORITY | DELAYED]
[INTO] tbl_name [(col_name,...)]
VALUES ({expr | DEFAULT},...),(...),...
或:
REPLACE [LOW_PRIORITY | DELAYED]
[INTO] tbl_name
SET col_name={expr | DEFAULT}, ...
或:
REPLACE [LOW_PRIORITY | DELAYED]
[INTO] tbl_name [(col_name,...)]
SELECT ...
REPLACE的运行与INSERT很相像。只有一点除外,如果表中的一个旧记录与一个用于PRIMARY KEY或一个UNIQUE索引的新记录具有相同的值,则在新记录被插入之前,旧记录被删除。请参见13.2.4节,“INSERT语法”。
注意,除非表有一个PRIMARY KEY或UNIQUE索引,否则,使用一个REPLACE语句没有意义。该语句会与INSERT相同,因为没有索引被用于确定是否新行复制了其它的行。
所有列的值均取自在REPLACE语句中被指定的值。所有缺失的列被设置为各自的默认值,这和INSERT一样。您不能从当前行中引用值,也不能在新行中使用值。如果您使用一个例如“SET col_name = col_name + 1”的赋值,则对位于右侧的列名称的引用会被作为DEFAULT(col_name)处理。因此,该赋值相当于SET col_name = DEFAULT(col_name) + 1。
为了能够使用REPLACE,您必须同时拥有表的INSERT和DELETE权限。
REPLACE语句会返回一个数,来指示受影响的行的数目。该数是被删除和被插入的行数的和。如果对于一个单行REPLACE该数为1,则一行被插入,同时没有行被删除。如果该数大于1,则在新行被插入前,有一个或多个旧行被删除。如果表包含多个唯一索引,并且新行复制了在不同的唯一索引中的不同旧行的值,则有可能是一个单一行替换了多个旧行。
受影响的行数可以容易地确定是否REPLACE只添加了一行,或者是否REPLACE也替换了其它行:检查该数是否为1(添加)或更大(替换)。
如果您正在使用C API,则可以使用mysql_affected_rows()函数获得受影响的行数。
目前,您不能在一个子查询中,向一个表中更换,同时从同一个表中选择。
以下是所用算法的更详细的说明(该算法也用于LOAD DATA...REPLACE):
1. 尝试把新行插入到表中
2. 当因为对于主键或唯一关键字出现重复关键字错误而造成插入失败时:
a. 从表中删除含有重复关键字值的冲突行
b. 再次尝试把新行插入到表中
13.2.7. SELECT语法
13.2.7.1. JOIN语法 13.2.7.2. UNION语法SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr, ...
[INTO OUTFILE 'file_name' export_options
| INTO DUMPFILE 'file_name']
[FROM table_references
[WHERE where_definition]
[GROUP BY {col_name | expr | position}
[ASC | DESC], ... [WITH ROLLUP]]
[HAVING where_definition]
[ORDER BY {col_name | expr | position}
[ASC | DESC] , ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[FOR UPDATE | LOCK IN SHARE MODE]]
SELECT用于恢复从一个或多个表中选择的行,并可以加入UNION语句和子查询。请参见13.2.7.2节,“UNION语法
”和13.2.8节,“Subquery语法”。
· 每个select_expr都指示一个您想要恢复的列。
· table_references指示行从哪个表或哪些表中被恢复。在13.2.7.1节,“JOIN语法”中对该语法进行了说明。
· where_definition包括关键词WHERE,后面接一个表达式。该表达式指示被选择的行必须满足的条件。
有的行在计算时未引用任何表。SELECT也可以用于恢复这类行。
举例说明:
mysql> SELECT 1 + 1;
-> 2
所有被使用的子句必须按语法说明中显示的顺序严格地排序。例如,一个HAVING子句必须位于GROUP BY子句之后,并位于ORDER BY子句之前。
· 使用AS alias_name可以为select_expr给定一个别名。此别名用作表达式的列名,可以用于GROUP BY、ORDER BY或HAVING子句。例如:
· mysql> SELECT CONCAT(last_name,', ',first_name) AS full_name
· -> FROM mytable ORDER BY full_name;
在为select_expr给定别名时,AS关键词是自选的。前面的例子可以这样编写:
mysql> SELECT CONCAT(last_name,', ',first_name) full_name
-> FROM mytable ORDER BY full_name;
因为AS是自选的,如果您忘记在两个select_expr表达式之间加逗号,则会出现一个小问题:MySQL会把第二个表达式理解为一个别名。例如,在以下语句中,columnb被作为别名对待:
mysql> SELECT columna columnb FROM mytable;
因此,使用AS明确地指定列的别名,把它作为习惯,是一个良好的操作规范。
· 在一个WHERE子句中使用列别名是不允许的,因为当执行WHERE子句时,列值可能还没有被确定。请参见A.5.4节,“与列别名有关的问题”。
· FROM table_references子句指示行从哪些表中被恢复。如果您命名的表多于一个,则您在进行一个联合操作。要了解有关联合语法的说明,请参见13.2.7.1节,“JOIN语法”。对于每一个被指定的表,您可以自选地指定一个别名。
· tbl_name [[AS] alias]
· [{USE|IGNORE|FORCE} INDEX (key_list)]
使用USE INDEX、IGNORE INDEX、FORCE INDEX可以向优化符提示如何选择索引。这部分内容在13.2.7.1节,“JOIN语法”中进行了讨论。
您可以使用SET max_seeks_for_key=value作为一种替代方法,来促使MySQL优先采用关键字扫描,替代表扫描。
· 您可以把当前数据库中的一个表作为tbl_name(在当前数据库中)引用,或作为db_name.tbl_name引用,来明确地指定一个数据库。您可以把一列作为col_name, tbl_name.col_name引用或作为db_name.tbl_name.col_name引用。您不需要对一个列引用指定一个tbl_name或db_name.tbl_name前缀,除非此引用意义不明确。意义不明确时,要求指定明确的列引用格式。有关示例见9.2节,“数据库、表、索引、列和别名”。
· 在没有表被引用的情况下,允许您指定DUAL作为一个假的表名。
· mysql> SELECT 1 + 1 FROM DUAL;
· -> 2
有些服务器要求一个FROM子句。DUAL仅用于与这些服务器兼容。如果没有表被引用,则MySQL不要求该子句,前面的语句可以按以下方法编写:
mysql> SELECT 1 + 1;
-> 2
· 使用tbl_name AS alias_name或tbl_name alias_name可以为一个表引用起别名:
· mysql> SELECT t1.name, t2.salary FROM employee AS t1, info AS t2
· -> WHERE t1.name = t2.name;
· mysql> SELECT t1.name, t2.salary FROM employee t1, info t2
· -> WHERE t1.name = t2.name;
· 在WHERE子句中,您可以使用MySQL支持的所有函数,不过总计(总结)函数除外。请参见第12章:函数和操作符。
· 被选择的用于输出的列可以使用列名称、列别名或列位置被引用到ORDER BY和GROUP BY子句中。列位置为整数,从1开始:
· mysql> SELECT college, region, seed FROM tournament
· -> ORDER BY region, seed;
· mysql> SELECT college, region AS r, seed AS s FROM tournament
· -> ORDER BY r, s;
· mysql> SELECT college, region, seed FROM tournament
· -> ORDER BY 2, 3;
要以相反的顺序进行分类,应把DESC(降序)关键字添加到ORDER BY子句中的列名称中。默认值为升序;该值可以使用ASC关键词明确地指定。
不建议使用列位置,因为该语法已经从SQL标准中删除。
· 如果您使用GROUP BY,则输出行根据GROUP BY列进行分类,如同您对相同的列进行了ORDER BY。MySQL对GROUP BY进行了扩展,因此您可以在各列(在子句中进行命名)的后面指定ASC和DESC:
· SELECT a, COUNT(b) FROM test_table GROUP BY a DESC
· MySQL对GROUP BY的使用进行了扩展,允许选择在GROUP BY子句中没有被提到的字段。如果您没有得到预期的结果,请阅读GROUP BY的说明,请参见12.10节,“与GROUP BY子句同时使用的函数和修改程序”。
· GROUP BY允许一个WITH ROLLUP修饰符。请参见12.10.2节,“GROUP BY修改程序”。
· HAVING子句基本上是最后使用,只位于被发送给客户端的条目之前,没有进行优化。(LIMIT用于HAVING之后。)
SQL标准要求HAVING必须引用GROUP BY子句中的列或用于总计函数中的列。不过,MySQL支持对此工作性质的扩展,并允许HAVING因为SELECT清单中的列和外部子查询中的列。
如果HAVING子句引用了一个意义不明确的列,则会出现警告。在下面的语句中,col2意义不明确,因为它既作为别名使用,又作为列名使用:
mysql> SELECT COUNT(col1) AS col2 FROM t GROUP BY col2 HAVING col2 = 2;
标准SQL工作性质具有优先权,因此如果一个HAVING列名既被用于GROUP BY,又被用作输出列清单中的起了别名的列,则优先权被给予GROUP BY列中的列。
· HAVING不能用于应被用于WHERE子句的条目。例如,不能编写如下语句:
· mysql> SELECT col_name FROM tbl_name HAVING col_name > 0;
而应这么编写:
mysql> SELECT col_name FROM tbl_name WHERE col_name > 0;
· HAVING子句可以引用总计函数,而WHERE子句不能引用:
· mysql> SELECT user, MAX(salary) FROM users
· -> GROUP BY user HAVING MAX(salary)>10;
(在有些较早版本的MySQL中,本语句不运行。)
· LIMIT子句可以被用于限制被SELECT语句返回的行数。LIMIT取一个或两个数字自变量,自变量必须是非负的整数常数(当使用已预备的语句时除外)。
使用两个自变量时,第一个自变量指定返回的第一行的偏移量,第二个自变量指定返回的行数的最大值。初始行的偏移量为0(不是1):
mysql> SELECT * FROM tbl LIMIT 5,10; # Retrieve rows 6-15
为了与PostgreSQL兼容,MySQL也支持LIMIT row_count OFFSET offset语法。
如果要恢复从某个偏移量到结果集合的末端之间的所有的行,您可以对第二个参数是使用比较大的数。本语句可以恢复从第96行到最后的所有行:
mysql> SELECT * FROM tbl LIMIT 95,18446744073709551615;
使用1个自变量时,该值指定从结果集合的开头返回的行数:
mysql> SELECT * FROM tbl LIMIT 5; # Retrieve first 5 rows
换句话说,LIMIT n与LIMIT 0,n等价。
对于已预备的语句,您可以使用位置保持符。以下语句将从tb1表中返回一行:
mysql> SET @a=1;
mysql> PREPARE STMT FROM "SELECT * FROM tbl LIMIT ?";
mysql> EXECUTE STMT USING @a;
以下语句将从tb1表中返回第二到第六行:
mysql> SET @skip=1; SET @numrows=5;
mysql> PREPARE STMT FROM "SELECT * FROM tbl LIMIT ?, ?";
mysql> EXECUTE STMT USING @skip, @numrows;
· SELECT...INTO OUTFILE 'file_name'形式的SELECT可以把被选择的行写入一个文件中。该文件被创建到服务器主机上,因此您必须拥有FILE权限,才能使用此语法。file_name不能是一个原有的文件。原有文件会阻止例如“/etc/passwd”的文件和数据库表被销毁。
SELECT...INTO OUTFILE语句的主要作用是让您可以非常快速地把一个表转储到服务器机器上。如果您想要在服务器主机之外的部分客户主机上创建结果文件,您不能使用SELECT...INTO OUTFILE。在这种情况下,您应该在客户主机上使用比如“mysql –e "SELECT ..." > file_name”的命令,来生成文件。
SELECT...INTO OUTFILE是LOAD DATA INFILE的补语;用于语句的exort_options部分的语法包括部分FIELDS和LINES子句,这些子句与LOAD DATA INFILE语句同时使用。请参见13.2.5节,“LOAD DATA INFILE语法”。
FIELDS ESCAPED BY用于控制如何写入特殊字符。如果FIELDS ESCAPED BY字符不是空字符,则被用于在输出中对以下字符设前缀:
o FIELDS ESCAPED BY字符
o FIELDS [OPTIONALLY] ENCLOSED BY字符
o FIELDS TERMINATED BY和LINES TERMINATED BY值的第一个字符
o ASCII 0(在编写时接在转义符后面的是ASCII ‘0’,而不是一个零值字节)
如果FIELDS ESCAPED BY字符是空字符,则没有字符被转义,并且NULL被作为NULL输出,而不是作为\N输出。指定一个空的转义符不是一个好的主意。特别是当您的数据中的字段值包含刚被给予的清单中的字符时,更是如此。
其原因是您必须对所有FIELDS TERMINATED BY, ENCLOSED BY, ESCAPED BY或LINES TERMINATED BY字符进行转义,才能可靠地读取文件并返回。ASCII NUL被转义,以便更容易地使用调页程序观看。
生成的文件不必符合SQL语法,所以没有其它的字符需要被转义。
在下面的例子中,生成一个文件,各值用逗号隔开。这种格式可以被许多程序使用。
SELECT a,b,a+b INTO OUTFILE '/tmp/result.text'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
FROM test_table;
· 如果您使用INTO DUMPFILE代替INTO OUTFILE,则MySQL只把一行写入到文件中,不对任何列或行进行终止,也不执行任何转义处理。如果您想要把一个BLOB值存储到文件中,则这个语句是有用的。
· 注释:任何由INTO OUTFILE或INTO DUMPFILE创建的文件都可以被服务器主机上的所有用户编写。原因是,MySQL服务器不能创建这样的文件,即文件的所有者不是该文件运行时所属的用户(任何时候,您都不能出于此原因或出于其它原因把mysqld作为根段运行)。该文件必须是全局可写的,这样您就可以操作其中的内容。
· 有的过程应在结果集合内处理数据。PROCEDURE子句用于对这些过程进行命名。要了解示例,请参见27.3.1节,“步骤分析”。
· 存储引擎使用页面或行锁。如果您对存储引擎使用FOR UPDATE,则受到查询检验的行会被进行写锁定,直到当前事务结束为止。使用LOCK IN SHARE MODE可以设置一个共享锁。共享锁可以防止其它事务更新或删除被检验的行。请参见15.2.10.5节,“锁定读取SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE”。
在SELECT关键词的后面,您可以使用许多选项。这些选项可以影响语句的运行。
ALL, DISTINCT和DISTINCTROW选项指定是否重复行应被返回。如果这些选项没有被给定,则默认值为ALL(所有的匹配行被返回)。DISTINCT和DISTINCTROW是同义词,用于指定结果集合中的重复行应被删除。
HIGH_PRIORITY, STRAIGHT_JOIN和以SQL_为开头的选项是MySQL相对于标准SQL的扩展。
· HIGH_PRIORITY给予SELECT更高的优先权,高于用于更新表的语句。您应该只对查询使用HIGH_PRIORITY。查询速度非常快,而且立刻被执行。SELECT HIGH_PRIORITY查询在表被锁定用于读取时被发出。即使有一个新的语句正在等待表变为空闲,查询也会运行。
HIGH_PRIORITY不能和SELECT语句同时使用。SELECT语句是UNION的一部分。
· STRAIGHT_JOIN用于促使优化符把表联合在一起,顺序按照这些表在FROM子句中排列的顺序。如果优化符联合表时顺序不佳,您可以使用STRAIGHT_JOIN来加快查询的速度。请参见7.2.1节,“EXPLAIN语法(获取关于SELECT的信息)”。STRAIGHT_JOIN也可以被用于table_references清单中。请参见13.2.7.1节,“JOIN语法”。
· SQL_BIG_RESULT可以与GROUP BY或DISTINCT同时使用,来告知优化符结果集合有很多行。在这种情况下,MySQL直接使用以磁盘为基础的临时表(如果需要的话)。在这种情况下,MySQL还会优先进行分类,不优先使用临时表。临时表对于GROUP BY组分带有关键字。
· SQL_BUFFER_RESULT促使结果被放入一个临时表中。这可以帮助MySQL提前解开表锁定,在需要花费较长时间的情况下,也可以帮助把结果集合发送到客户端中。
· SQL_SMALL_RESULT可以与GROUP BY或DISTINCT同时使用,来告知优化符结果集合是较小的。在此情况下,MySAL使用快速临时表来储存生成的表,而不是使用分类。在MySQL 5.1中,通常不需要这样。
· SQL_CALC_FOUND_ROWS告知MySQL计算有多少行应位于结果集合中,不考虑任何LIMIT子句。行的数目可以使用SELECT FOUND_ROWS()恢复。请参见12.9.3节,“信息函数”。
· 如果您正在使用一个query_cache_type值,值为2或DEMAND,则SQL_CACHE告知MySQL把查询结果存储在查询缓存中。对于使用UNION的查询或子查询,本选项会影响查询中的所有SELECT。请参见5.13节,“MySQL查询高速缓冲”。
· SQL_NO_CACHE告知MySQL不要把查询结果存储在查询缓存中。请参见5.13节,“MySQL查询高速缓冲”。对于一个使用UNION或子查询的查询,本选项会影响查询中的SELECT。
13.2.7.1. JOIN语法
MySQL支持以下JOIN语法。这些语法用于SELECT语句的table_references部分和多表DELETE和UPDATE语句:
table_references: table_reference [, table_reference] … table_reference: table_factor | join_table table_factor: tbl_name [[AS] alias] [{USE|IGNORE|FORCE} INDEX (key_list)] | ( table_references ) | { OJ table_reference LEFT OUTER JOIN table_reference ON conditional_expr } join_table: table_reference [INNER | CROSS] JOIN table_factor [join_condition] | table_reference STRAIGHT_JOIN table_factor | table_reference STRAIGHT_JOIN table_factor ON condition | table_reference LEFT [OUTER] JOIN table_reference join_condition | table_reference NATURAL [LEFT [OUTER]] JOIN table_factor | table_reference RIGHT [OUTER] JOIN table_reference join_condition | table_reference NATURAL [RIGHT [OUTER]] JOIN table_factor join_condition: ON conditional_expr | USING (column_list)
一个表引用还被称为一个联合表达式。
与SQL标准相比,table_factor的语法被扩展了。SQL标准只接受table_reference,而不是圆括号内的一系列条目。
如果我们把一系列table_reference条目中的每个逗号都看作相当于一个内部联合,则这是一个稳妥的扩展。例如:
SELECT * FROM t1 LEFT JOIN (t2, t3, t4)
ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
相当于:
SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)
ON (t2.a=t1.a AND t3.b=t1.b AND t4.c=t1.c)
在MySQL中,CROSS JOIN从语法上说与INNER JOIN等同(两者可以互相替换。在标准SQL中,两者是不等同的。INNER JOIN与ON子句同时使用,CROSS JOIN以其它方式使用。
通常,在只含有内部联合运行的联合表达式中,圆括号可以被忽略。MySQL也支持嵌套的联合(见7.2.10节,“MySQL如何优化嵌套Join”)。
通常,您不应对ON部分有任何条件。ON部分用于限定在结果集合中您想要哪些行。但是,您应在WHERE子句中指定这些条件。这条规则有一些例外。
在前面的清单中显示的{ OJ ... LEFT OUTER JOIN ...}语法的目的只是为了保持与ODBC的兼容性。语法中的花括号应按字面书写;该括号不是中间语法。中间语法用于语法描述的其它地方。
· 表引用可以使用tbl_name AS alias_name或tbl_name alias_name指定别名:
· mysql> SELECT t1.name, t2.salary FROM employee AS t1, info AS t2
· -> WHERE t1.name = t2.name;
· mysql> SELECT t1.name, t2.salary FROM employee t1, info t2
· -> WHERE t1.name = t2.name;
· ON条件句是可以被用于WHERE子句的格式的任何条件表达式。
· 如果对于在LEFT JOIN中的ON或USING部分中的右表没有匹配的记录,则所有列被设置为NULL的一个行被用于右表。如果一个表在其它表中没有对应部分,您可以使用这种方法在这种表中查找记录:
· mysql> SELECT table1.* FROM table1
· -> LEFT JOIN table2 ON table1.id=table2.id
· -> WHERE table2.id IS NULL;
本例查找在table1中含有一个id值的所有行。同时,在table2中没有此id值(即,table1中的所有行在table2中没有对应的行)。本例假设table2.id被定义为NOT NULL。请参见7.2.9节,“MySQL如何优化LEFT JOIN和RIGHT JOIN”。
· USING(column_list)子句用于为一系列的列进行命名。这些列必须同时在两个表中存在。如果表a和表b都包含列c1, c2和c3,则以下联合会对比来自两个表的对应的列:
· a LEFT JOIN b USING (c1,c2,c3)
· 两个表的NATURAL [LEFT] JOIN被定义为与INNER JOIN语义相同,或与使用USING子句的LEFT JOIN语义相同。USING子句用于为同时存在于两个表中的所有列进行命名。
· INNER JOIN和,(逗号)在无联合条件下是语义相同的:两者都可以对指定的表计算出笛卡儿乘积(也就是说,第一个表中的每一行被联合到第二个表中的每一行)。
· RIGHT JOIN的作用与LEFT JOIN的作用类似。要使代码可以在数据库内移植,建议您使用LEFT JOIN代替RIGHT JOIN。
· STRAIGHT_JOIN与JOIN相同。除了有一点不一样,左表会在右表之前被读取。STRAIGH_JOIN可以被用于这样的情况,即联合优化符以错误的顺序排列表。
您可以提供提示,当从一个表中恢复信息时,MySQL应使用哪个索引。通过指定USE INDEX(key_list),您可以告知MySQL只使用一个索引来查找表中的行。另一种语法IGNORE INDEX(key_list)可以被用于告知MySQL不要使用某些特定的索引。如果EXPLAIN显示MySQL正在使用来自索引清单中的错误索引时,这些提示会有用处。
您也可以使用FORCE INDEX,其作用接近USE INDEX(key_list),不过增加了一项作用,一次表扫描被假设为代价很高。换句话说,只有当无法使用一个给定的索引来查找表中的行时,才使用表扫描。
USE KEY、IGNORE KEY和FORCE KEY是USE INDEX、IGNORE INDEX和FORCE INDEX的同义词。
注释:当MySQL决定如何在表中查找行并决定如何进行联合时,使用USE INDEX、IGNORE INDEX和FORCE INDEX只会影响使用哪些索引。当分解一个ORDER BY或GROUP BY时,这些语句不会影响某个索引是否被使用。
部分的联合示例:
mysql> SELECT * FROM table1,table2 WHERE table1.id=table2.id;
mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id;
mysql> SELECT * FROM table1 LEFT JOIN table2 USING (id);
mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id
-> LEFT JOIN table3 ON table2.id=table3.id;
mysql> SELECT * FROM table1 USE INDEX (key1,key2)
-> WHERE key1=1 AND key2=2 AND key3=3;
mysql> SELECT * FROM table1 IGNORE INDEX (key3)
-> WHERE key1=1 AND key2=2 AND key3=3;
见7.2.9节,“MySQL如何优化LEFT JOIN和RIGHT JOIN”。
注释:自然联合和使用USING的联合,包括外部联合变量,依据SQL:2003标准被处理。这些变更时MySQL与标准SQL更加相符。不过,对于有些联合,这些变更会导致不同的输出列。另外,有些查询在旧版本(5.0.12以前)工作正常,但也必须重新编写,以符合此标准。对于有关当前联合处理和旧版本中的联合处理的效果的对比,以下列表提供了更详细的信息。
· NATURAL联合或USING联合的列会与旧版本不同。特别是,不再出现冗余的输出列,用于SELECT *扩展的列的顺序会与以前不同。
示例:
CREATE TABLE t1 (i INT, j INT);
CREATE TABLE t2 (k INT, j INT);
INSERT INTO t1 VALUES(1,1);
INSERT INTO t2 VALUES(1,1);
SELECT * FROM t1 NATURAL JOIN t2;
SELECT * FROM t1 JOIN t2 USING (j);
对于旧版本,语句会产生以下输出:
+------+------+------+------+
| i | j | k | j |
+------+------+------+------+
| 1 | 1 | 1 | 1 |
+------+------+------+------+
+------+------+------+------+
| i | j | k | j |
+------+------+------+------+
| 1 | 1 | 1 | 1 |
+------+------+------+------+
在第一个SELECT语句中,列i同时出现在两个表中,为一个联合列,所以,依据标准SQL,该列在输出中只出现一次。与此类似,在第二个SELECT语句中,列j在USING子句中被命名,应在输出中只出现一次。但是,在两种情况下,冗余的列均没被消除。另外,依据标准SQL,列的顺序不正确。
现在,语句产生如下输出:
+------+------+------+
| j | i | k |
+------+------+------+
| 1 | 1 | 1 |
+------+------+------+
+------+------+------+
| j | i | k |
+------+------+------+
| 1 | 1 | 1 |
+------+------+------+
冗余的列被消除,并且依据标准SQL,列的顺序是正确的:
o 第一,两表共有的列,按在第一个表中的顺序排列
o 第二,第一个表中特有的列,按该表中的顺序排列
o 第三,第二个表中特有的列,按该表中的顺序排列
· 对多方式自然联合的估算会不同。方式要求重新编写查询。假设您有三个表t1(a,b), t2(c,b)和t3(a,c),每个表有一行:t1(1,2), t2(10,2)和t3(7,10)。同时,假设这三个表具有NATURAL JOIN:
· SELECT … FROM t1 NATURAL JOIN t2 NATURAL JOIN t3;
在旧版本中,第二个联合的左操作数被认为是t2,然而它应该为嵌套联合(t1 NATURAL JOIN t2)。结果,对t3的列进行检查时,只检查其在t2中的共有列。如果t3与t1有共有列,这些列不被用作equi-join列。因此,在旧版本的MySQL中,前面的查询被转换为下面的equi-join:
SELECT … FROM t1, t2, t3
WHERE t1.b = t2.b AND t2.c = t3.c;
此联合又省略了一个equi-join谓语(t1.a = t3.a)。结果是,该联合产生一个行,而不是空结果。正确的等价查询如下:
SELECT … FROM t1, t2, t3
WHERE t1.b = t2.b AND t2.c = t3.c AND t1.a = t3.a;
如果您要求在当前版本的MySQL中获得和旧版本中相同的查询结果,应把自然联合改写为第一个equi-join。
· 在旧版本中,逗号操作符(,)和JOIN均有相同的优先权,所以联合表达式t1, t2 JOIN t3被理解为((t1, t2) JOIN t3)。现在,JOIN有更高的优先权,所以表达式被理解为(t1, (t2 JOIN t3))。这个变更会影响使用ON子句的语句,因为该子句只参阅联合操作数中的列。优先权的变更改变了对什么是操作数的理解。
示例:
CREATE TABLE t1 (i1 INT, j1 INT);
CREATE TABLE t2 (i2 INT, j2 INT);
CREATE TABLE t3 (i3 INT, j3 INT);
INSERT INTO t1 VALUES(1,1);
INSERT INTO t2 VALUES(1,1);
INSERT INTO t3 VALUES(1,1);
SELECT * FROM t1, t2 JOIN t3 ON (t1.i1 = t3.i3);
在旧版本中,SELECT是合法的,因为t1, t2被隐含地归为(t1,t2)。现在,JOIN取得了优先权,因此用于ON子句的操作数是t2和t3。因为t1.i1不是任何一个操作数中的列,所以结果是出现在'on clause'中有未知列't1.i1'的错误。要使联合可以被处理,用使用圆括号把前两个表明确地归为一组,这样用于ON子句的操作数为(t1,t2)和t3:
SELECT * FROM (t1, t2) JOIN t3 ON (t1.i1 = t3.i3);
本变更也适用于INNER JOIN,CROSS JOIN,LEFT JOIN和RIGHT JOIN。
· 在旧版本中,ON子句可以参阅在其右边命名的表中的列。现在,ON子句只能参阅操作数。
示例:
CREATE TABLE t1 (i1 INT);
CREATE TABLE t2 (i2 INT);
CREATE TABLE t3 (i3 INT);
SELECT * FROM t1 JOIN t2 ON (i1 = i3) JOIN t3;
在旧版本中,SELECT语句是合法的。现在该语句会运行失败,出现在'on clause'中未知列'i3'的错误。这是因为i3是t3中的一个表,而t3不是ON子句中的操作数。本语句应进行如下改写:
SELECT * FROM t1 JOIN t2 JOIN t3 ON (i1 = i3);
· 在旧版本中,一个USING子句可以被改写为一个ON子句。ON子句对比了相应的列。例如,以下两个子句具有相同的语义:
· a LEFT JOIN b USING (c1,c2,c3)
· a LEFT JOIN b ON a.c1=b.c1 AND a.c2=b.c2 AND a.c3=b.c3
现在,这两个子句不再是一样的:
o 在决定哪些行满足联合条件时,两个联合保持语义相同。
o 在决定哪些列显示SELECT *扩展时,两个联合的语义不相同。USING联合选择对应列中的合并值,而ON联合选择所有表中的所有列。对于前面的USING联合,SELECT *选择这些值:
o COALESCE(a.c1,b.c1), COALESCE(a.c2,b.c2), COALESCE(a.c3,b.c3)
对于ON联合,SELECT *选择这些值:
a.c1, a.c2, a.c3, b.c1, b.c2, b.c3
使用内部联合时,COALESCE(a.c1,b.c1)与a.c1或b.c1相同,因为两列将具有相同的值。使用外部联合时(比如LEFT JOIN),两列中有一列可以为NULL。该列将会从结果中被忽略。
13.2.7.2. UNION语法
SELECT ...
UNION [ALL | DISTINCT]
SELECT ...
[UNION [ALL | DISTINCT]
SELECT ...]
UNION用于把来自许多SELECT语句的结果组合到一个结果集合中。
列于每个SELECT语句的对应位置的被选择的列应具有相同的类型。(例如,被第一个语句选择的第一列应和被其它语句选择的第一列具有相同的类型。)在第一个SELECT语句中被使用的列名称也被用于结果的列名称。
SELECT语句为常规的选择语句,但是受到如下的限定:
· 只有最后一个SELECT语句可以使用INTO OUTFILE。
· HIGH_PRIORITY不能与作为UNION一部分的SELECT语句同时使用。如果您对第一个SELECT指定了HIGH_PRIORITY,则不会起作用。如果您对其它后续的SELECT语句指定了HIGH_PRIORITY,则会产生语法错误。
如果您对UNION不使用关键词ALL,则所有返回的行都是唯一的,如同您已经对整个结果集合使用了DISTINCT。如果您指定了ALL,您会从所有用过的SELECT语句中得到所有匹配的行。
DISTINCT关键词是一个自选词,不起任何作用,但是根据SQL标准的要求,在语法中允许采用。(在MySQL中,DISTINCT代表一个共用体的默认工作性质。)
您可以在同一查询中混合UNION ALL和UNION DISTINCT。被混合的UNION类型按照这样的方式对待,即DISTICT共用体覆盖位于其左边的所有ALL共用体。DISTINCT共用体可以使用UNION DISTINCT明确地生成,或使用UNION(后面不加DISTINCT或ALL关键词)隐含地生成。
如果您想使用ORDER BY或LIMIT子句来对全部UNION结果进行分类或限制,则应对单个地SELECT语句加圆括号,并把ORDER BY或LIMIT放到最后一个的后面。以下例子同时使用了这两个子句:
(SELECT a FROM tbl_name WHERE a=10 AND B=1)
UNION
(SELECT a FROM tbl_name WHERE a=11 AND B=2)
ORDER BY a LIMIT 10;
这种ORDER BY不能使用包括表名称(也就是,采用tbl_name.col_name格式的名称)列引用。可以在第一个SELECT语句中提供一个列别名,并在ORDER BY中参阅别名,或使用列位置在ORDER BY中参阅列。(首选采用别名,因为不建议使用列位置。)
另外,如果带分类的一列有别名,则ORDER BY子句必须引用别名,而不能引用列名称。以下语句中的第一个语句必须运行,但是第二个会运行失败,出现在'order clause'中有未知列'a'的错误:
(SELECT a AS b FROM t) UNION (SELECT ...) ORDER BY b;
(SELECT a AS b FROM t) UNION (SELECT ...) ORDER BY a;
To apply ORDER BY or LIMIT to an individual SELECT, place the clause inside the parentheses that enclose the SELECT: 为了对单个SELECT使用ORDER BY或LIMIT,应把子句放入圆括号中。圆括号包含了SELECT:
(SELECT a FROM tbl_name WHERE a=10 AND B=1 ORDER BY a LIMIT 10)
UNION
(SELECT a FROM tbl_name WHERE a=11 AND B=2 ORDER BY a LIMIT 10);
圆括号中用于单个SELECT语句的ORDER BY只有当与LIMIT结合后,才起作用。否则,ORDER BY被优化去除。
UNION结果集合中的列的类型和长度考虑了被所有SELECT语句恢复的数值。例如,考虑如下语句:
mysql> SELECT REPEAT('a',1) UNION SELECT REPEAT('b',10);
+---------------+
| REPEAT('a',1) |
+---------------+
| a |
| bbbbbbbbbb |
+---------------+
(在部分早期版本的MySQL中,第二行已被删节到长度为1。)
13.2.8. Subquery语法
13.2.8.1. 子查询作为标量操作数 13.2.8.2. 使用子查询进行比较 13.2.8.3. 使用ANY, IN和SOME进行子查询 13.2.8.4. 使用ALL进行子查询 13.2.8.5. 行子查询 13.2.8.6. EXISTS和NOT EXISTS 13.2.8.7. 关联子查询 13.2.8.8. FROM子句中的子查询 13.2.8.9. 子查询错误 13.2.8.10. 优化子查询 13.2.8.11. 把子查询作为用于早期MySQL版本的联合进行改写子查询是另一个语句中的一个SELECT语句。
MySQL支持SQL标准要求的所有子查询格式和操作,也支持MySQL特有的几种特性。
以下是一个子查询的例子:
SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);
在本例中,SELECT * FROM t1...是外部查询(或外部语句),(SELECT column1 FROM t2)是子查询。我们可以说子查询嵌套在外部查询中。实际上,子查询也可以嵌套在其它子查询中,嵌套程度可以很深。子查询必须要位于圆括号中。
子查询的主要优势为:
· 子查询允许结构化的查询,这样就可以把一个语句的每个部分隔离开。
· 有些操作需要复杂的联合和关联。子查询提供了其它的方法来执行这些操作。
· 在许多人看来,子查询是可读的。实际上,子查询给予人们调用早期SQL“结构化查询语言”的原本的想法,这是子查询的创新。
以下是一个示例语句。该语句显示了有关子查询语法的要点。子查询语法由SQL标准指定并被MySQL支持。
DELETE FROM t1
WHERE s11 > ANY
(SELECT COUNT(*) /* no hint */ FROM t2
WHERE NOT EXISTS
(SELECT * FROM t3
WHERE ROW(5*t2.s1,77)=
(SELECT 50,11*s1 FROM t4 UNION SELECT 50,77 FROM
(SELECT * FROM t5) AS t5)));
一个子查询会返回一个标量(单一值)、一个行、一个列或一个表(一行或多行及一列或多列)。这些子查询被称为标量、列、行和表子查询。可返回一个特定种类结果的子查询经常只被用于特定的语境中,在后面各节中有说明。
有些语句可以使用子查询。对这些语句的类型基本没有限定。子查询可以包括普通SELECT可以包括的任何关键词或子句:DISTINCT, GROUP BY, ORDER BY, LIMIT, 联合, 索引提示, UNION结构化, 评注和函数等。
有一个限定是,一个子查询的外部语句必须是以下语句之一:SELECT, INSERT, UPDATE, DELETE, SET或DO。还有一个限定是,目前,您不能在一个子查询中修改一个表,又在同一个表中选择。这适用于DELETE, INSERT, REPLACE和UPDATE语句。在附录I:特性限制中给出了对子查询使用的更综合的讨论。
13.2.8.1. 子查询作为标量操作数
子查询最简单的形式是返回单一值的标量子查询。标量子查询是一个单一操作数。只要单一列值或文字是合法的,并且您希望子查询具有所有操作数都具有的特性,则您就可以使用子查询。操作数具有的特性包括:一个数据类型、一个长度、一个指示是否可以为NULL的标志等。举例说明:CREATE TABLE t1 (s1 INT, s2 CHAR(5) NOT NULL);
INSERT INTO t1 VALUES(100, 'abcde');
SELECT (SELECT s2 FROM t1);
在本SELECT中的子查询返回一个单一值('abcde')。该单一值的数据类型为CHAR,长度为5,字符集和整序与在CREATE TABLE时有效的默认值相同,并有一个指示符号,指示列中的值可以为NULL。实际上,基本上所有的子查询都为NULL。如果在本例中使用的表为空表,则子查询的值应为NULL。
在有些情况下,标量子查询不能使用。如果一个语句只允许一个文字值,您不能使用子查询。例如,LIMIT要求文字整数自变量,LOAD DATA要求一个文字字符串文件名。您不能使用子查询来提供这些值。
后面各节包括更简练的结构(SELECT column1 FROM t1)。当您在这些章节中观看例子时,请设想一下您自己的代码包含更多样、更复杂的结构。
举例说明,假设我们制作两个表:
CREATE TABLE t1 (s1 INT);
INSERT INTO t1 VALUES (1);
CREATE TABLE t2 (s1 INT);
INSERT INTO t2 VALUES (2);
然后执行一个SELECT:
SELECT (SELECT s1 FROM t2) FROM t1;
结果为2,因为t2中有一行包含s1,s1有一个值为2。
一个标量子查询可以为一个表达式的一部分。不要忘记圆括号。即使是子查询是一个为函数提供自变量的操作数时,也不要忘记圆括号。举例说明:
SELECT UPPER((SELECT s1 FROM t1)) FROM t2;
13.2.8.2. 使用子查询进行比较
子查询最常见的一种使用方式如下:
non_subquery_operand comparison_operator (subquery)
当comparison_operator是以下 操作符之一时:
= > < >= <= <>
例如:
... 'a' = (SELECT column1 FROM t1)
有时,子查询的合法位置只能在比较式的右侧,您可以发现,在有些旧的DBMSs中仍保持这一点。
以下是一个常见格式的子查询比较的例子。您不能使用联合进行此类比较。表t1中有些值与表t2中的最大值相同。该比较可以查找出所有这类值:
SELECT column1 FROM t1
WHERE column1 = (SELECT MAX(column2) FROM t2);
下面还有另一个例子,该例子也不可能使用联合,因为该例子涉及对其中一个表进行总计。表t1中的有些行含有的值会在给定的列中出现两次。该例子可以查找出所有这些行:
SELECT * FROM t1 AS t
WHERE 2 = (SELECT COUNT(*) FROM t1 WHERE t1.id = t.id);
对于采用这些操作符之一进行的比较,子查询必须返回一个标量。有一个例外,那就是=可以和行子查询同时使用。请参见13.2.8.5节,“行子查询”。
13.2.8.3. 使用ANY, IN和SOME进行子查询
语法:
operand comparison_operator ANY (subquery)
operand IN (subquery)
operand comparison_operator SOME (subquery)
ANY关键词必须后面接一个比较操作符。ANY关键词的意思是“对于在子查询返回的列中的任一数值,如果比较结果为TRUE的话,则返回TRUE”。例如:
SELECT s1 FROM t1 WHERE s1 > ANY (SELECT s1 FROM t2);
假设表t1中有一行包含(10)。如果表t2包含(21,14,7),则表达式为TRUE,因为t2中有一个值为7,该值小于10。如果表t2包含(20,10),或者如果表t2为空表,则表达式为FALSE。如果表t2包含(NULL, NULL, NULL),则表达式为UNKNOWN。
词语IN是=ANY的别名。因此,这两个语句是一样的:
SELECT s1 FROM t1 WHERE s1 = ANY (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 IN (SELECT s1 FROM t2);
不过,NOT IN不是<> ANY的别名,但是是<> ALL的别名。请参见13.2.8.4节,“使用ALL进行子查询”。
词语SOME是ANY的别名。因此,这两个语句是一样的:
SELECT s1 FROM t1 WHERE s1 <> ANY (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 <> SOME (SELECT s1 FROM t2);
使用词语SOME的机会很少,但是本例显示了为什么SOME是有用的。对于多数人来说,英语短语“a is not equal to any b”的意思是“没有一个b与a相等”,但是在SQL语法中不是这个意思。该语法的意思是“有部分b与a不相等”。使用<> SOME有助于确认每个人都理解该查询的真正含义。
13.2.8.4. 使用ALL进行子查询
语法:operand comparison_operator ALL (subquery)
词语ALL必须接在一个比较操作符的后面。ALL的意思是“对于子查询返回的列中的所有值,如果比较结果为TRUE,则返回TRUE。”例如:
SELECT s1 FROM t1 WHERE s1 > ALL (SELECT s1 FROM t2);
假设表1中有一行包含(10)。如果表t2包含(-5,0,+5),则表达式为TRUE,因为10比t2中的所有三个值都大。如果表t2包含(12,6,NULL,-100),则表达式为FALSE,因为表t2中有一个值12大于10。如果表t2包含(0,NULL,1),则表达式为unknown。
最后,如果表t2为空表,则结果为TRUE。因此,当表t2为空表时,以下语句为TRUE:
SELECT * FROM t1 WHERE 1 > ALL (SELECT s1 FROM t2);
但是,当表t2为空表时,本语句为NULL:
SELECT * FROM t1 WHERE 1 > (SELECT s1 FROM t2);
另外,当表t2为空表时,以下语句为NULL:
SELECT * FROM t1 WHERE 1 > ALL (SELECT MAX(s1) FROM t2);
通常,包含NULL值的表和空表为“边缘情况”。当编写子查询代码时,都要考虑您是否把这两种可能性计算在内。
NOT IN是<> ALL的别名。因此,以下两个语句是相同的:
SELECT s1 FROM t1 WHERE s1 <> ALL (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 NOT IN (SELECT s1 FROM t2);
13.2.8.5. 行子查询
对于本点的讨论属于标量或列子查询,即返回一个单一值或一列值的子查询。行子查询是一个能返回一个单一行的子查询变量,因此可以返回一个以上的列值。以下是两个例子:SELECT * FROM t1 WHERE (1,2) = (SELECT column1, column2 FROM t2);
SELECT * FROM t1 WHERE ROW(1,2) = (SELECT column1, column2 FROM t2);
如果在表t2的一个行中,column1=1并且column2=2,则查询结果均为TRUE。
表达式(1,2)和ROW(1,2)有时被称为行构造符。两者是等同的,在其它的语境中,也是合法的。例如,以下两个语句在语义上是等同的(但是目前只有第二个语句可以被优化):
SELECT * FROM t1 WHERE (column1,column2) = (1,1);
SELECT * FROM t1 WHERE column1 = 1 AND column2 = 1;
行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。例如,以下查询可以答复请求,“在表t1中查找同时也存在于表t2中的所有的行”:
SELECT column1,column2,column3
FROM t1
WHERE (column1,column2,column3) IN
(SELECT column1,column2,column3 FROM t2);
13.2.8.6. EXISTS和NOT EXISTS
如果一个子查询返回任何的行,则EXISTS subquery为FALSE。例如:SELECT column1 FROM t1 WHERE EXISTS (SELECT * FROM t2);
过去,EXISTS子查询以SELECT *为开始,但是可以以SELECT 5或SELECT column1或其它的为开始。MySQL在这类子查询中忽略了SELECT清单,因此没有区别。
对于前面的例子,如果t2包含任何行,即使是只含有NULL值的行,EXISTS条件也为TRUE。这实际上是一个不可能的例子,因为基本上所有的[NOT] EXISTS子查询均包含关联。以下是一些更现实的例子:
· 哪些种类的商店出现在一个或多个城市里?
· SELECT DISTINCT store_type FROM stores
· WHERE EXISTS (SELECT * FROM cities_stores
· WHERE cities_stores.store_type = stores.store_type);
· 哪些种类的商店没有出现在任何城市里?
· SELECT DISTINCT store_type FROM stores
· WHERE NOT EXISTS (SELECT * FROM cities_stores
· WHERE cities_stores.store_type = stores.store_type);
· 哪些种类的商店出现在所有城市里?
· SELECT DISTINCT store_type FROM stores s1
· WHERE NOT EXISTS (
· SELECT * FROM cities WHERE NOT EXISTS (
· SELECT * FROM cities_stores
· WHERE cities_stores.city = cities.city
· AND cities_stores.store_type = stores.store_type));
最后一个例子是一个双嵌套NOT EXISTS查询。也就是,该查询包含一个NOT EXISTS子句,该子句又包含在一个NOT EXISTS子句中。该查询正式地回答了这个问题,“是否有某个城市拥有没有列在Stores中的商店?”。可以比较容易的说,一个带嵌套的NOT EXISTS可以回答这样的问题,“是否对于所有的y,x都为TRUE?”
13.2.8.7. 关联子查询
相关联的子查询是一个包含对表的引用的子查询。该表也显示在外部查询中。例如:SELECT * FROM t1 WHERE column1 = ANY
(SELECT column1 FROM t2 WHERE t2.column2 = t1.column2);
注意,即使子查询的FROM子句不提及表t1,该子查询也会包含一个对t1中一列的引用。所以,MySQL看上去位于子查询的外部,并在外部查询中查找t1。
假设表t1包含一行,在此行中column1=5并且column2=6;同时,表t2包含一行,在此行中column1=5并且column2=7。简单的表达式... WHERE column1 = ANY (SELECT column1 FROM t2)会为TRUE。但是在本例中,在子查询中的WHERE子句为FALSE(因为(5,6)不等于(5,7)),所以子查询总体上为FALSE。
范围划分规则:MySQL从内到外进行评估。例如:
SELECT column1 FROM t1 AS x
WHERE x.column1 = (SELECT column1 FROM t2 AS x
WHERE x.column1 = (SELECT column1 FROM t3
WHERE x.column2 = t3.column1));
在本语句中,x.column2必须是表t2中的列,因为SELECT column1 FROM t2 AS x ...对t2进行了重命名。它不是表t1中的列,因为SELECT column1 FROM t1 ...是一个更靠外的外部查询。
对于HAVING或ORDER BY子句中的子查询,MySQL也会在外部选择清单中寻找列名称。
对于特定的情况,相关联的子查询被优化。例如:
val IN (SELECT key_val FROM tbl_name WHERE correlated_condition)
否则,这些子查询效率不高,可能速度会慢。把查询作为联合进行改写可能会改进效率。
相关联的子查询不能从外部查询中引用总计函数的结果。
13.2.8.8. FROM子句中的子查询
在SELECT语句的FROM子句中,子查询是合法的。实际的语法是:
SELECT ... FROM (subquery) [AS] name ...
[AS] name子句是强制性的,因为FROM子句中的每个表必须有一个名称。在子查询选择列表中的任何列都必须有唯一的名称。您可以在本手册中的其它地方找到对本语法的说明。在该处,所用的词语是“导出表”。
为了进行详细说明,假设您有如下一个表:
CREATE TABLE t1 (s1 INT, s2 CHAR(5), s3 FLOAT);
下面使用了示例表,解释了在FROM子句中如何使用子查询:
INSERT INTO t1 VALUES (1,'1',1.0);
INSERT INTO t1 VALUES (2,'2',2.0);
SELECT sb1,sb2,sb3
FROM (SELECT s1 AS sb1, s2 AS sb2, s3*2 AS sb3 FROM t1) AS sb
WHERE sb1 > 1;
结果:2, '2', 4.0。
下面是另一个例子:假设您想了解一个分类后的表的一组和的平均值。采用如下操作:
SELECT AVG(SUM(column1)) FROM t1 GROUP BY column1;
不过,本查询提供所需的信息:
SELECT AVG(sum_column1)
FROM (SELECT SUM(column1) AS sum_column1
FROM t1 GROUP BY column1) AS t1;
注意,在子查询中使用的列名称(sum_column1)被整理到外部查询中。
FROM子句中的子查询可以返回标量、列、行或表。FROM子句中的子查询不能为有关联的子查询。
即使对EXPLAIN语句(即建立临时导出表),FROM子句中的子查询也会被执行。这是因为在优化过程中,上一级的查询需要有关所有表的信息。
13.2.8.9. 子查询错误
以下错误只适用于子查询。本节把这些错误归在一起。
· 来自子查询的列的数目不正确
· ERROR 1241 (ER_OPERAND_COL)
· SQLSTATE = 21000
· Message = "Operand should contain 1 column(s)"
在出现以下情况时,发生此错误:
SELECT (SELECT column1, column2 FROM t2) FROM t1;
如果您的目的是进行比较,您可以使用能返回多个列的子查询。请参见13.2.8.5节,“行子查询”。不过,在其它的语境下,子查询必须为标量操作数。
· 来自子查询的行的数目不正确:
· ERROR 1242 (ER_SUBSELECT_NO_1_ROW)
· SQLSTATE = 21000
· Message = "Subquery returns more than 1 row"
如果在语句中,子查询返回的行多于一个,则发生此错误。请考虑以下例子:
SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);
如果SELECT column1 FROM t2只返回一行,则将执行以前的查询。如果子查询返回的行多于一个,则将出现错误1242。在这种情况下,该查询将被改写为:
SELECT * FROM t1 WHERE column1 = ANY (SELECT column1 FROM t2);
· 在子查询中表格使用不正确:
· Error 1093 (ER_UPDATE_TABLE_USED)
· SQLSTATE = HY000
· Message = "You can't specify target table 'x'
· for update in FROM clause"
在如下情况下,发生该错误:
UPDATE t1 SET column2 = (SELECT MAX(column1) FROM t1);
和SELECT语句一样,在UPDATE和DELETE语句中,子查询是合法的。所以您可以在UPDATE语句中使用子查询进行赋值。不过,您不能把同一个表(在本例中为表t1)既用于子查询的FROM子句,又用于更新目标。
对于事务存储引擎,子查询的错误会导致整个语句失效。对于非事务存储引擎,在遇到错误之前进行的数据修订会被保留。
13.2.8.10. 优化子查询
开发过程不断进展,所以从长远来看,没有一个可靠的优化技巧。有些技巧您可能会感兴趣,并原意采用:
· 有些子句会影响在子查询中的行的数量和顺序。使用这类子句。例如:
· SELECT * FROM t1 WHERE t1.column1 IN
· (SELECT column1 FROM t2 ORDER BY column1);
· SELECT * FROM t1 WHERE t1.column1 IN
· (SELECT DISTINCT column1 FROM t2);
· SELECT * FROM t1 WHERE EXISTS
· (SELECT * FROM t2 LIMIT 1);
· 用子查询替换联合。例如,试进行如下操作:
· SELECT DISTINCT column1 FROM t1 WHERE t1.column1 IN (
· SELECT column1 FROM t2);
代替如下操作:
SELECT DISTINCT t1.column1 FROM t1, t2
WHERE t1.column1 = t2.column1;
· 部分子查询可以被转换为联合,以便与不支持子查询的旧版本的MySQL相兼容。不过,在有些情况下,把子查询转化为联合可以提高效果。请参见13.2.8.11节,“把子查询作为用于早期MySQL版本的联合进行改写”。
· 把子句从子查询的外部转移到内部。例如,使用此查询:
· SELECT * FROM t1
· WHERE s1 IN (SELECT s1 FROM t1 UNION ALL SELECT s1 FROM t2);
代替此查询:
SELECT * FROM t1
WHERE s1 IN (SELECT s1 FROM t1) OR s1 IN (SELECT s1 FROM t2);
另一个例子是,使用此查询:
SELECT (SELECT column1 + 5 FROM t1) FROM t2;
代替此查询:
SELECT (SELECT column1 FROM t1) + 5 FROM t2;
· 使用行子查询,代替关联子查询。举例说明,使用此查询:
· SELECT * FROM t1
· WHERE (column1,column2) IN (SELECT column1,column2 FROM t2);
代替此查询:
SELECT * FROM t1
WHERE EXISTS (SELECT * FROM t2 WHERE t2.column1=t1.column1
AND t2.column2=t1.column2);
· Use NOT (a = ANY (...)) rather than a <> ALL (...).
· Use x = ANY (table containing (1,2)) rather than x=1 OR x=2.
· Use = ANY rather than EXISTS.
· 对于只返回一行的无关联子查询,IN的速度慢于=。举例说明,使用此查询:
· SELECT * FROM t1 WHERE t1.col_name
· = (SELECT a FROM t2 WHERE b = some_const);
代替此查询:
SELECT * FROM t1 WHERE t1.col_name
IN (SELECT a FROM t2 WHERE b = some_const);
使用这些技巧可以使程序更快或更慢。使用BENCHMARK()函数等MySQL工具,您可以了解到在您所处的情况下,哪些技巧会有帮助。
MySQL本身进行的部分优化包括:
· MySQL只执行一次无关联子查询。使用EXPLAIN确认给定的子查询确实是无关联的。
· MySQL改写IN, ALL, ANY和SOME子查询,目的是如果子查询中的select-list列已编制索引,则能发挥出此优势。
· MySQL使用index-lookup函数代替以下格式的子查询。EXPLAIN把此函数描述为特殊的联合类型(unique_subquery或index_subquery):
· ... IN (SELECT indexed_column FROM single_table ...)
· 当表达式中不包含NULL值或空集时,MySQL使用一个包含MIN()或MAX()的表达式,对以下格式的表达式进行扩展:
· value {ALL|ANY|SOME} {> | < | >= | <=} (non-correlated subquery)
例如,本WHERE子句:
WHERE 5 > ALL (SELECT x FROM t)
可以用优化符进行如下处理:
WHERE 5 > (SELECT MAX(x) FROM t)
在MySQL内部手册中有一章名为“MySQL如何转换子查询”,可以从http://dev.mysql.com/doc/获取。
13.2.8.11. 把子查询作为用于早期MySQL版本的联合进行改写
在较早版本的MySQL中(早于MySQL 4.1),只支持INSERT...SELECT和REPLACE...SELECT...格式的带嵌套的查询。虽然在MySQL 5.1中没有这种情况,但有时,仍然有其它的方法测试一组值的从属关系。并且,在有些情况下,不仅可以在没有子查询时对查询进行改写,而且有时使用这些方法比使用子查询效率更高。这些方法之一是IN()结构:举例说明,本查询:
SELECT * FROM t1 WHERE id IN (SELECT id FROM t2);
可以被改写为:
SELECT DISTINCT t1.* FROM t1, t2 WHERE t1.id=t2.id;
以下查询:
SELECT * FROM t1 WHERE id NOT IN (SELECT id FROM t2);
SELECT * FROM t1 WHERE NOT EXISTS (SELECT id FROM t2 WHERE t1.id=t2.id);
也可以使用IN()进行改写:
SELECT table1.* FROM table1 LEFT JOIN table2 ON table1.id=table2.id
WHERE table2.id IS NULL;
LEFT [OUTER] JOIN可以比对应的子查询更快,因为服务器可能对其进行更好的优化——这一点对于单独的MySQL服务器并不明确。在SQL-92之前,不存在外部联合,因此在做某些事情时,子查询是唯一的方法。现在,MySQL服务器和其它许多先进的数据库系统都能提供多种的外部联合类型。
MySQL支持multiple-table DELETE语句,该语句可以被用于高效地删除行。删除时依据来自一个表或同时来自多个表的信息。同时也支持Multiple-table UPDATE语句。
13.2.9. TRUNCATE语法
TRUNCATE [TABLE] tbl_name
TRUNCATE TABLE用于完全清空一个表。从逻辑上说,该语句与用于删除所有行的DELETE语句等同,但是在有些情况下,两者在使用上有所不同。
对于InnoDB表,如果有需要引用表的外键限制,则TRUNCATE TABLE被映射到DELETE上;否则使用快速删减(取消和重新创建表)。使用TRUNCATE TABLE重新设置AUTO_INCREMENT计数器,设置时不考虑是否有外键限制。
对于其它存储引擎,在MySQL 5.1中,TRUNCATE TABLE与DELETE FROM有以下几处不同:
· 删减操作会取消并重新创建表,这比一行一行的删除行要快很多。
· 删减操作不能保证对事务是安全的;在进行事务处理和表锁定的过程中尝试进行删减,会发生错误。
· 被删除的行的数目没有被返回。
· 只要表定义文件tbl_name.frm是合法的,则可以使用TRUNCATE TABLE把表重新创建为一个空表,即使数据或索引文件已经被破坏。
· 表管理程序不记得最后被使用的AUTO_INCREMENT值,但是会从头开始计数。即使对于MyISAM和InnoDB也是如此。MyISAM和InnoDB通常不再次使用序列值。
· 当被用于带分区的表时,TRUNCATE TABLE会保留分区;即,数据和索引文件被取消并重新创建,同时分区定义(.par)文件不受影响。
TRUNCATE TABLE是在MySQL中采用的一个Oracle SQL扩展。
13.2.10. UPDATE语法
Single-table语法:
UPDATE [LOW_PRIORITY] [IGNORE] tbl_name
SET col_name1=expr1 [, col_name2=expr2 ...]
[WHERE where_definition]
[ORDER BY ...]
[LIMIT row_count]
Multiple-table语法:
UPDATE [LOW_PRIORITY] [IGNORE] table_references
SET col_name1=expr1 [, col_name2=expr2 ...]
[WHERE where_definition]
UPDATE语法可以用新值更新原有表行中的各列。SET子句指示要修改哪些列和要给予哪些值。WHERE子句指定应更新哪些行。如果没有WHERE子句,则更新所有的行。如果指定了ORDER BY子句,则按照被指定的顺序对行进行更新。LIMIT子句用于给定一个限值,限制可以被更新的行的数目。
UPDATE语句支持以下修饰符:
· 如果您使用LOW_PRIORITY关键词,则UPDATE的执行被延迟了,直到没有其它的客户端从表中读取为止。
· 如果您使用IGNORE关键词,则即使在更新过程中出现错误,更新语句也不会中断。如果出现了重复关键字冲突,则这些行不会被更新。如果列被更新后,新值会导致数据转化错误,则这些行被更新为最接近的合法的值。
如果您在一个表达式中通过tbl_name访问一列,则UPDATE使用列中的当前值。例如,以下语句把年龄列设置为比当前值多一:
mysql> UPDATE persondata SET age=age+1;
UPDATE赋值被从左到右评估。例如,以下语句对年龄列加倍,然后再进行增加:
mysql> UPDATE persondata SET age=age*2, age=age+1;
如果您把一列设置为其当前含有的值,则MySQL会注意到这一点,但不会更新。
如果您把被已定义为NOT NULL的列更新为NULL,则该列被设置到与列类型对应的默认值,并且累加警告数。对于数字类型,默认值为0;对于字符串类型,默认值为空字符串('');对于日期和时间类型,默认值为“zero”值。
UPDATE会返回实际被改变的行的数目。Mysql_info() C API函数可以返回被匹配和被更新的行的数目,以及在UPDATE过程中产生的警告的数量。
您可以使用LIMIT row_count来限定UPDATE的范围。LIMIT子句是一个与行匹配的限定。只要发现可以满足WHERE子句的row_count行,则该语句中止,不论这些行是否被改变。
如果一个UPDATE语句包括一个ORDER BY子句,则按照由子句指定的顺序更新行。
您也可以执行包括多个表的UPDATE操作。table_references子句列出了在联合中包含的表。该语法在13.2.7.1节,“JOIN语法”中进行了说明。以下是一个例子:
UPDATE items,month SET items.price=month.price
WHERE items.id=month.id;
以上的例子显示出了使用逗号操作符的内部联合,但是multiple-table UPDATE语句可以使用在SELECT语句中允许的任何类型的联合,比如LEFT JOIN。
注释:您不能把ORDER BY或LIMIT与multiple-table UPDATE同时使用。
在一个被更改的multiple-table UPDATE中,有些列被引用。您只需要这些列的UPDATE权限。有些列被读取了,但是没被修改。您只需要这些列的SELECT权限。
如果您使用的multiple-table UPDATE语句中包含带有外键限制的InnoDB表,则MySQL优化符处理表的顺序可能与上下层级关系的顺序不同。在此情况下,语句无效并被 回滚。同时,更新一个单一表,并且依靠ON UPDATE功能。该功能由InnoDB提供,用于对其它表进行相应的修改。请参见15.2.6.4节,“FOREIGN KEY约束”。
目前,您不能在一个子查询中更新一个表,同时从同一个表中选择。