MySQL JOIN 用法示例详解

本文将图文并茂的讲解 MySQL 的查询时 JOIN 的用法。这里用到的两个示例表及示例数据如下:

CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `user` VALUES (1,'xiaoming@qq.com','小明'),(2,'xiaowang@qq.com','小王'),(6,'xiangzhang@qq.com','小张'),(7,'xiaoli@qq.com','小李');

CREATE TABLE `user_info` (
  `user_id` int(10) NOT NULL,
  `age` int(10) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `user_info` VALUES (1,22,'小明'),(2,21,'小王'),(3,33,'小郑');

插入数据库后数据如下:

user_datauser_info_data

MySQL 官网上介绍 JOIN 一共有以下几种形式,接下来笔者将一一介绍:

table_reference [INNER | CROSS] JOIN table_factor [join_condition]
  | table_reference STRAIGHT_JOIN table_factor
  | table_reference STRAIGHT_JOIN table_factor ON conditional_expr
  | table_reference {LEFT|RIGHT} [OUTER] JOIN table_reference join_condition
  | table_reference NATURAL [{LEFT|RIGHT} [OUTER]] JOIN table_factor

JOIN、CROSS JOIN、INNER JOIN

MySQL 官网说:In MySQL, JOIN, CROSS JOIN, and INNER JOIN are syntactic equivalents (they can replace each other). JOIN, CROSS JOIN, 和 INNER JOIN 是等价的,可以相互替换。下面的例子不再出现 CROSS JOIN 和 INNER JOIN。

直接使用 JOIN 连接两个表查询得到结果为:

snapshot1

可以看到,两表结果进行了笛卡尔积,最终查出 12 行记录。这是没带任何条件的查询,这条语句等价于:

SELECT * FROM user, user_info

MySQL 官网如是说:

INNER JOIN and , (comma) are semantically equivalent in the absence of a join condition: both produce a Cartesian product between the specified tables (that is, each and every row in the first table is joined to each and every row in the second table).

说的是,在没有条件的情况下,INNER JOIN 和 ,(逗号)是等价的,都返回两张表的笛卡尔积。
我们来试试带条件的 JOIN 查询:

snapshot2
这个结果很清晰,连接的条件是 user.id=user_info.user_id。结果返回两行记录。

USING

我们来看看 USING 的用法:

snapshot3

USING(name) 等价于 ON user.name=user_info.name,但是大家请注意,这里有一点点区别,使用 USING 得到的结果只有一个 name 字段,且 name 字段在结果最前面,而使用 ON 得到的结果有两个 name 字段。

我们再试试使用 USING 去查并不是两张表都有的字段:

snapshot4

提示错误,因为 user_info 表里并没有 id 字段。

JOIN 与 LEFT JOIN 和 RIGHT JOIN

snapshot6

在 LEFT JOIN 里面,如果右边的表里没有匹配左边表的记录,则右边表所有字段为 NULL;同理,在 RIGHT JOIN 里面,如果左边的表里没有匹配右边表的记录,则左边表所有字段为 NULL;而 JOIN 只查询出两张表都存在的数据。

利用 LEFT JOIN 可以查出左边表存在而右边表不存在的记录,如下图:

snapshot7

STRAIGHT_JOIN

STRAIGHT_JOIN 和 JOIN 一样,除了前者可以保证左边的表先查。STRAIGHT_JOIN 可以用在防止 JOIN 优化器将表的顺序搞错。

NATURAL

NATURAL JOIN 等价于使用 USING,它会 USING 所有两张表里都包含的字段:

snapshot8

OUTER

至于这货,笔者也没搞明白是干什么的。官网给出的实例讲的是用在 ODBC 里,笔者没看明白。

上面所有的示例都只演示了两张表 JOIN,可不要以为只能两张表进行 JOIN 查询。一下是一些 SQL 示例:

SELECT * FROM t1, t2, t3;
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);

ps:大家有没有发现 wordpress 写作时,编辑器用得很不爽啊,比如,插入代码后想回到正常编辑必须切换到文本模式下,在 </pre> 标签后先打几个字符然后在回可视化模式正常写作。还有,每次发文章都要跑到后台来,没有直接用 markdown 舒服啊,直接本地写好,提交上去,多方便。

MySQL UNION 用法

有这样两张表 post 和 postmeta。

表 post 结构如下

CREATE TABLE `post` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(200) CHARACTER SET utf8 NOT NULL,
  `content` text CHARACTER SET utf8 NOT NULL,
  PRIMARY KEY (`id`)
)

表 postmeta 结构如下

CREATE TABLE `postmeta` (
  `meta_id` int(11) NOT NULL AUTO_INCREMENT,
  `post_id` int(11) NOT NULL,
  `meta_key` text NOT NULL,
  `meta_value` text NOT NULL,
  PRIMARY KEY (`meta_id`)
)
INSERT INTO `postmeta` (`meta_id`, `post_id`, `meta_key`, `meta_value`) VALUES
(1, 1, 'count_views_all', '666'),
(2, 1, 'count_views_day-20130117', '44'),
(3, 1, 'count_views_week-201301', '343'),
(4, 2, 'count_views_all', '3453'),
(5, 3, 'count_views_all', '34553'),
(6, 4, 'count_views_all', '553'),
(7, 3, 'count_views_day-20130117', ''),
(8, 4, 'count_views_day-20130117', '35'),
(9, 5, 'count_views_day-20130111', '5'),
(10, 5, 'count_views_day-20130112', '12'),
(11, 5, 'count_views_day-20130113', '34'),
(12, 5, 'count_views_day-20130114', '45'),
(13, 5, 'count_views_day-20130115', '21'),
(14, 5, 'count_views_day-20130116', '23'),
(15, 5, 'count_views_day-20130117', '65');

想获取所有文章,并且按全部阅读量排序,理所当然,我写出了这样的查询语句:

SELECT `post`.*,`views_count` FROM `post`
JOIN
SELECT `meta_value` AS `views_count` WHERE `meta_key`='count_views_all' FROM `postmeta`
ON `post`.`id`=`postmeta`.`post_id`
ORDER BY `views_count` DESC

运行,提示 SQL 语法错误。查看 MySQL 官方文档,总结出 JOIN 两边必须是表(有错还希望看官您指出),因此,我加了一个子句。如下,OK。

SELECT `post`.*,`views_count` FROM `post`
JOIN
(SELECT `post_id`,`meta_value` AS `views_count` FROM `postmeta` WHERE `meta_key`='count_views_all') AS `meta`
ON `post`.`id`=`meta`.`post_id`
ORDER BY (`views_count`+0) DESC