SQL查询如何从多个表中返回数据

我想知道以下几点:

  • 如何从我的数据库中的多个表中获取数据?
  • 有什么types的方法可以做到这一点?
  • 什么是联合和联合,它们又是如何不同的?
  • 我应该什么时候使用每一个与其他人相比?

我打算在我的(例如 – PHP)应用程序中使用它,但不希望针对数据库运行多个查询,在单个查询中有哪些选项需要从多个表中获取数据?

注意:我正在编写这个,因为我希望能够链接到我在PHP队列中经常遇到的许多问题上编写良好的指南,所以当我发布答案时,可以链接到更多细节。

答案涵盖以下内容:

  1. 第1部分 – 联合和联盟
  2. 第2部分 – 子查询
  3. 第3部分 – 技巧和有效的代码
  4. 第4部分 – 从子句中的子查询
  5. 第五部分 – 约翰的伎俩混合袋

第1部分 – 联合和联盟

这个答案包括:

  1. 第1部分
    • 使用内连接join两个或多个表格(请参阅维基百科条目了解更多信息)
    • 如何使用联合查询
    • 左外连接和右外连接(这个stackOverflow的答案是很好的描述types的连接)
    • 相交查询(以及如果你的数据库不支持它们,如何重现它们) – 这是SQL-Server的一个function( 参见info ), 也是我首先写这个东西的部分原因 。
  2. 第2部分
    • 子查询 – 它们是什么,它们可以在哪里使用以及要注意什么
    • 笛卡儿joinAKA – 哦,不幸的是!

有很多方法可以从数据库中的多个表中检索数据。 在这个答案中,我将使用ANSI-92连接语法。 这可能与其他一些使用较旧的ANSI-89语法的教程不同(如果您习惯了89,看起来可能更不直观 – 但我只能说是尝试一下),因为它容易了解何时查询开始变得越来越复杂。 为什么使用它? 有一个性能增益? 简短的答案是否定的,但是一旦习惯了就会更容易阅读。 使用这种语法读取由其他人编写的查询更容易。

我也将使用一个小Caryard的概念,它有一个数据库来跟踪它可用的汽车。 老板已经雇用了你作为他的IT计算机家伙,并希望你能够放下他所要求的数据。

我已经做了一些查找表,将被决赛桌使用。 这将给我们一个合理的工作模式。 首先,我将针对具有以下结构的示例数据库运行我的查询。 我会尝试着去思考在开始时所犯的常见错误,并解释他们所犯的错误,以及当然如何纠正错误。

第一张表只是一个颜色列表,以便我们知道我们在车场里有什么颜色。

mysql> create table colors(id int(3) not null auto_increment primary key, -> color varchar(15), paint varchar(10)); Query OK, 0 rows affected (0.01 sec) mysql> show columns from colors; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(3) | NO | PRI | NULL | auto_increment | | color | varchar(15) | YES | | NULL | | | paint | varchar(10) | YES | | NULL | | +-------+-------------+------+-----+---------+----------------+ 3 rows in set (0.01 sec) mysql> insert into colors (color, paint) values ('Red', 'Metallic'), -> ('Green', 'Gloss'), ('Blue', 'Metallic'), -> ('White' 'Gloss'), ('Black' 'Gloss'); Query OK, 5 rows affected (0.00 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> select * from colors; +----+-------+----------+ | id | color | paint | +----+-------+----------+ | 1 | Red | Metallic | | 2 | Green | Gloss | | 3 | Blue | Metallic | | 4 | White | Gloss | | 5 | Black | Gloss | +----+-------+----------+ 5 rows in set (0.00 sec) 

品牌表标识了caryard可能销售的车型的不同品牌。

 mysql> create table brands (id int(3) not null auto_increment primary key, -> brand varchar(15)); Query OK, 0 rows affected (0.01 sec) mysql> show columns from brands; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(3) | NO | PRI | NULL | auto_increment | | brand | varchar(15) | YES | | NULL | | +-------+-------------+------+-----+---------+----------------+ 2 rows in set (0.01 sec) mysql> insert into brands (brand) values ('Ford'), ('Toyota'), -> ('Nissan'), ('Smart'), ('BMW'); Query OK, 5 rows affected (0.00 sec) Records: 5 Duplicates: 0 Warnings: 0 mysql> select * from brands; +----+--------+ | id | brand | +----+--------+ | 1 | Ford | | 2 | Toyota | | 3 | Nissan | | 4 | Smart | | 5 | BMW | +----+--------+ 5 rows in set (0.00 sec) 

模型表将覆盖不同types的汽车,使用不同的汽车types而不是实际的汽车模型会更简单。

 mysql> create table models (id int(3) not null auto_increment primary key, -> model varchar(15)); Query OK, 0 rows affected (0.01 sec) mysql> show columns from models; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(3) | NO | PRI | NULL | auto_increment | | model | varchar(15) | YES | | NULL | | +-------+-------------+------+-----+---------+----------------+ 2 rows in set (0.00 sec) mysql> insert into models (model) values ('Sports'), ('Sedan'), ('4WD'), ('Luxury'); Query OK, 4 rows affected (0.00 sec) Records: 4 Duplicates: 0 Warnings: 0 mysql> select * from models; +----+--------+ | id | model | +----+--------+ | 1 | Sports | | 2 | Sedan | | 3 | 4WD | | 4 | Luxury | +----+--------+ 4 rows in set (0.00 sec) 

最后,把所有这些桌子捆绑在一起。 ID字段实际上是用于识别汽车的唯一批号。

 mysql> create table cars (id int(3) not null auto_increment primary key, -> color int(3), brand int(3), model int(3)); Query OK, 0 rows affected (0.01 sec) mysql> show columns from cars; +-------+--------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+--------+------+-----+---------+----------------+ | id | int(3) | NO | PRI | NULL | auto_increment | | color | int(3) | YES | | NULL | | | brand | int(3) | YES | | NULL | | | model | int(3) | YES | | NULL | | +-------+--------+------+-----+---------+----------------+ 4 rows in set (0.00 sec) mysql> insert into cars (color, brand, model) values (1,2,1), (3,1,2), (5,3,1), -> (4,4,2), (2,2,3), (3,5,4), (4,1,3), (2,2,1), (5,2,3), (4,5,1); Query OK, 10 rows affected (0.00 sec) Records: 10 Duplicates: 0 Warnings: 0 mysql> select * from cars; +----+-------+-------+-------+ | id | color | brand | model | +----+-------+-------+-------+ | 1 | 1 | 2 | 1 | | 2 | 3 | 1 | 2 | | 3 | 5 | 3 | 1 | | 4 | 4 | 4 | 2 | | 5 | 2 | 2 | 3 | | 6 | 3 | 5 | 4 | | 7 | 4 | 1 | 3 | | 8 | 2 | 2 | 1 | | 9 | 5 | 2 | 3 | | 10 | 4 | 5 | 1 | +----+-------+-------+-------+ 10 rows in set (0.00 sec) 

这会给我们足够的数据(我希望)能够掩盖下面的不同types的连接的例子,并且提供足够的数据使它们值得。

因此,深入了解它的老板,老板想知道他所有的跑车的身份证

这是一个简单的两个表连接。 我们有一个表格来标识模型和表格中的可用库存。 正如您所看到的, carsmodel列中的数据与cars表格的models列有关。 现在,我们知道模型表的Sports ID为1 ,所以让我们写联接。

 select ID, model from cars join models on model=ID 

所以这个查询看起来不错吧? 我们已经确定了这两个表格,并包含了我们需要的信息,并使用一个连接来正确识别要join的列。

 ERROR 1052 (23000): Column 'ID' in field list is ambiguous 

哦,不! 在我们的第一个查询错误! 是的,这是一个梅花。 你看,查询确实得到了正确的列,但其中一些存在于两个表中,所以数据库对我们所指的实际列的意思和位置感到困惑。 有两个解决scheme来解决这个问题。 首先是好的,简单的,我们可以使用tableName.columnName告诉数据库到底是什么意思,就像这样:

 select cars.ID, models.model from cars join models on cars.model=models.ID +----+--------+ | ID | model | +----+--------+ | 1 | Sports | | 3 | Sports | | 8 | Sports | | 10 | Sports | | 2 | Sedan | | 4 | Sedan | | 5 | 4WD | | 7 | 4WD | | 9 | 4WD | | 6 | Luxury | +----+--------+ 10 rows in set (0.00 sec) 

另一个可能更经常使用,被称为表格别名。 这个例子中的表格有简短的名字,但是input类似KPI_DAILY_SALES_BY_DEPARTMENT东西可能会很快变老,所以一个简单的方法就是像这样对表格进行昵称:

 select a.ID, b.model from cars a join models b on a.model=b.ID 

现在回到请求。 正如你所看到的,我们有我们需要的信息,但是我们也有没有被要求的信息,所以我们需要在声明中包含一个where子句,以便按照要求获得跑车。 因为我更喜欢表别名方法,而不是反复使用表名,所以我会坚持从这一点开始。

显然,我们需要在我们的查询中添加一个where子句。 我们可以通过ID=1model='Sports'来识别跑车。 由于ID是索引和主键(它碰巧less打字),让我们在查询中使用。

 select a.ID, b.model from cars a join models b on a.model=b.ID where b.ID=1 +----+--------+ | ID | model | +----+--------+ | 1 | Sports | | 3 | Sports | | 8 | Sports | | 10 | Sports | +----+--------+ 4 rows in set (0.00 sec) 

答对了! 老板很高兴。 当然,作为一个老板,他从不满意他所要求的东西,他看着信息,然后说我也想要颜色

好的,所以我们已经写好了很多部分的查询,但是我们需要使用第三个颜色表。 现在,我们的主要信息表cars存储汽车颜色ID和这个链接回到颜色ID列。 因此,以与原文相似的方式,我们可以join第三个表格:

 select a.ID, b.model from cars a join models b on a.model=b.ID join colors c on a.color=c.ID where b.ID=1 +----+--------+ | ID | model | +----+--------+ | 1 | Sports | | 3 | Sports | | 8 | Sports | | 10 | Sports | +----+--------+ 4 rows in set (0.00 sec) 

该死,虽然表格被正确地join了,而且相关的栏目也被链接了,但是我们忘记了从刚刚链接的新表格中提取实际的信息

 select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID where b.ID=1 +----+--------+-------+ | ID | model | color | +----+--------+-------+ | 1 | Sports | Red | | 8 | Sports | Green | | 10 | Sports | White | | 3 | Sports | Black | +----+--------+-------+ 4 rows in set (0.00 sec) 

对,那是我们背上的老板。 现在,更详细地解释一些这个。 正如你所看到的,我们声明中的from子句连接了我们的主表(我经常使用一个包含信息的表而不是查找表或维表)。查询在切换表的时候也同样适用,当我们回到这个查询在几个月的时间内阅读它,所以通常最好尝试编写一个好的和容易理解的查询 – 直观地说明,使用好的缩进,使一切都清晰如果你继续教别人,试着在他们的查询中灌输这些特征 – 特别是如果你要排除故障。

以这种方式连接越来越多的表格是完全可能的。

 select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID join brands d on a.brand=d.ID where b.ID=1 

虽然我忘了在join语句中join一个我们想join多个列的表,但这里是一个例子。 如果models表具有特定于品牌的模型,并因此也有一个名为brand的列,该列与ID字段中的brands表链接,则可以这样来完成:

 select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID join brands d on a.brand=d.ID and b.brand=d.ID where b.ID=1 

您可以看到,上面的查询不仅将连接的表链接到主cars表,还指定已连接的表之间的连接。 如果没有这样做,结果被称为笛卡尔连接 – 这是dba说话不好。 笛卡尔连接是返回行的位置,因为信息不告诉数据库如何限制结果,所以查询返回符合条件的所有行。

因此,举一个笛卡尔连接的例子,让我们运行下面的查询:

 select a.ID, b.model from cars a join models b +----+--------+ | ID | model | +----+--------+ | 1 | Sports | | 1 | Sedan | | 1 | 4WD | | 1 | Luxury | | 2 | Sports | | 2 | Sedan | | 2 | 4WD | | 2 | Luxury | | 3 | Sports | | 3 | Sedan | | 3 | 4WD | | 3 | Luxury | | 4 | Sports | | 4 | Sedan | | 4 | 4WD | | 4 | Luxury | | 5 | Sports | | 5 | Sedan | | 5 | 4WD | | 5 | Luxury | | 6 | Sports | | 6 | Sedan | | 6 | 4WD | | 6 | Luxury | | 7 | Sports | | 7 | Sedan | | 7 | 4WD | | 7 | Luxury | | 8 | Sports | | 8 | Sedan | | 8 | 4WD | | 8 | Luxury | | 9 | Sports | | 9 | Sedan | | 9 | 4WD | | 9 | Luxury | | 10 | Sports | | 10 | Sedan | | 10 | 4WD | | 10 | Luxury | +----+--------+ 40 rows in set (0.00 sec) 

好神,那很难看 但就数据库而言,这正是要求的。 在查询中,我们要求carsIDmodelmodels 。 但是,因为我们没有指定如何连接表,所以数据库将第一个表中的每一行与第二个表中的每一行进行匹配。

好吧,老板回来了,他又想要更多的信息。 我想要相同的列表,但也包括四驱车

然而,这给了我们一个很好的借口来看看两种不同的方式来实现这一点。 我们可以添加另一个条件,像这样的where子句:

 select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID join brands d on a.brand=d.ID where b.ID=1 or b.ID=3 

虽然上述方法可以很好地工作,但让我们以不同的方式来看看,这是一个很好的借口来显示union查询如何工作。

我们知道以下将返回所有跑车:

 select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID join brands d on a.brand=d.ID where b.ID=1 

以下将返回所有的四驱车:

 select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID join brands d on a.brand=d.ID where b.ID=3 

因此,通过在它们之间添加union all子句,第二个查询的结果将被附加到第一个查询的结果中。

 select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID join brands d on a.brand=d.ID where b.ID=1 union all select a.ID, b.model, c.color from cars a join models b on a.model=b.ID join colors c on a.color=c.ID join brands d on a.brand=d.ID where b.ID=3 +----+--------+-------+ | ID | model | color | +----+--------+-------+ | 1 | Sports | Red | | 8 | Sports | Green | | 10 | Sports | White | | 3 | Sports | Black | | 5 | 4WD | Green | | 7 | 4WD | White | | 9 | 4WD | Black | +----+--------+-------+ 7 rows in set (0.00 sec) 

正如你所看到的,第一个查询的结果首先被返回,然后是第二个查询的结果。

在这个例子中,简单地使用第一个查询就简单得多了,但是union查询对于特定的情况可能是很好的。 它们是从不容易连接在一起的表格中返回特定结果的好方法 – 或者完全无关的表格。 有几条规则可以遵循。

  • 第一个查询的列types必须与下面每个其他查询的列types匹配。
  • 第一个查询的列名将被用来标识整个结果集。
  • 每个查询中的列数必须相同。

现在,你可能想知道使用unionunion all 什么区别。 union查询将删除重复,而union all不会。 这确实意味着在union all使用union时,性能会受到很大的影响,但结果可能是值得的 – 我不会在这种情况下进行推测。

在这个说明中,这里可能值得注意一些额外的注释。

  • 如果我们想要命令结果,我们可以使用一个order by但不能再使用这个别名。 在上面的查询中, order by a.ID附加order by a.ID会导致错误 – 就结果而言,该列被称为ID而不是a.ID – 即使在两个查询中都使用了相同的别名。
  • 我们只能order by声明发出一个order by ,而且必须作为最后的声明。

对于下面的例子,我在表中添加了一些额外的行。

我已经把Holden添加到品牌表中。 我还添加了一行color值为12 cars – 在颜色表中没有参考。

好吧,老板又回来了,吼了要求 – *我想要计算每个品牌的数量和车里的数量!“ – 典型的,我们刚刚讨论了一个有趣的部分,老板想要更多的工作。

Rightyo,所以我们需要做的第一件事是获得可能的品牌的完整列表。

 select a.brand from brands a +--------+ | brand | +--------+ | Ford | | Toyota | | Nissan | | Smart | | BMW | | Holden | +--------+ 6 rows in set (0.00 sec) 

现在,当我们join到我们的汽车表格时,我们得到以下结果:

 select a.brand from brands a join cars b on a.ID=b.brand group by a.brand +--------+ | brand | +--------+ | BMW | | Ford | | Nissan | | Smart | | Toyota | +--------+ 5 rows in set (0.00 sec) 

这当然是一个问题 – 我们没有看到任何提到我添加的可爱的Holden品牌。

这是因为联接在两个表中查找匹配的行。 由于没有Holdentypes的汽车数据,它不会被返回。 这是我们可以使用outer连接的地方。 这将返回一个表中的所有结果,而不pipe它们是否在另一个表中匹配:

 select a.brand from brands a left outer join cars b on a.ID=b.brand group by a.brand +--------+ | brand | +--------+ | BMW | | Ford | | Holden | | Nissan | | Smart | | Toyota | +--------+ 6 rows in set (0.00 sec) 

现在我们已经有了,我们可以添加一个可爱的聚合函数来计算一下,让老板离开我们一会儿。

 select a.brand, count(b.id) as countOfBrand from brands a left outer join cars b on a.ID=b.brand group by a.brand +--------+--------------+ | brand | countOfBrand | +--------+--------------+ | BMW | 2 | | Ford | 2 | | Holden | 0 | | Nissan | 1 | | Smart | 1 | | Toyota | 5 | +--------+--------------+ 6 rows in set (0.00 sec) 

就这样,离开了老板skulks。

现在,为了更详细地解释这个,外连接可以是leftrighttypes。 左或右定义哪个表完全包含在内。 left outer join将包含左侧表中的所有行,而(您猜对了) right outer join会将右表中的所有结果带入结果中。

一些数据库将允许一个full outer join ,这将从两个表中带回结果(无论是否匹配),但是在所有数据库中都不支持。

现在,我想可能是在这个时候,你想知道你是否可以在查询中合并连接types – 答案是肯定的,你完全可以。

 select b.brand, c.color, count(a.id) as countOfBrand from cars a right outer join brands b on b.ID=a.brand join colors c on a.color=c.ID group by a.brand, c.color +--------+-------+--------------+ | brand | color | countOfBrand | +--------+-------+--------------+ | Ford | Blue | 1 | | Ford | White | 1 | | Toyota | Black | 1 | | Toyota | Green | 2 | | Toyota | Red | 1 | | Nissan | Black | 1 | | Smart | White | 1 | | BMW | Blue | 1 | | BMW | White | 1 | +--------+-------+--------------+ 9 rows in set (0.00 sec) 

那么,为什么这不是预期的结果呢? 这是因为尽pipe我们select了从汽车到品牌的外部连接,但是在颜色连接中没有指定颜色,因此特定的连接只会返回两个表中匹配的结果。

以下是可以获得我们预期的结果的查询:

 select a.brand, c.color, count(b.id) as countOfBrand from brands a left outer join cars b on a.ID=b.brand left outer join colors c on b.color=c.ID group by a.brand, c.color +--------+-------+--------------+ | brand | color | countOfBrand | +--------+-------+--------------+ | BMW | Blue | 1 | | BMW | White | 1 | | Ford | Blue | 1 | | Ford | White | 1 | | Holden | NULL | 0 | | Nissan | Black | 1 | | Smart | White | 1 | | Toyota | NULL | 1 | | Toyota | Black | 1 | | Toyota | Green | 2 | | Toyota | Red | 1 | +--------+-------+--------------+ 11 rows in set (0.00 sec) 

正如我们所看到的,我们在查询中有两个外部连接,并且结果正如预期的那样通过。

那么现在呢,你问的其他types的联合呢? 交叉口怎么样?

那么并不是所有的数据库都支持这个intersection但几乎所有的数据库都允许你通过一个连接创build一个交集(或者至less是一个结构合理的where语句)。

交集是一种类似于上面描述的union的连接 – 不同之处在于, 它只返回由联合join的各个单独的查询之间相同的数据行(我的意思是相同的)。 只有在各方面相同的行才会被返回。

一个简单的例子就是这样:

 select * from colors where ID>2 intersect select * from colors where id<4 

虽然普通union查询将返回表的所有行(第一个查询返回ID>2的任何东西,第二个ID<4任何东西),这将导致整个集合,但相交查询只会返回匹配id=3的行id=3因为它符合两个标准。

现在,如果你的数据库不支持intersect查询,上面的查询可以很容易地用下面的查询来完成:

 select a.ID, a.color, a.paint from colors a join colors b on a.ID=b.ID where a.ID>2 and b.ID<4 +----+-------+----------+ | ID | color | paint | +----+-------+----------+ | 3 | Blue | Metallic | +----+-------+----------+ 1 row in set (0.00 sec) 

如果您希望使用不固有支持交叉查询的数据库在两个不同的表中执行交集,则需要在表的每一列上创build一个联接。

好吧,我发现这篇文章非常有趣,我想分享一些关于创build查询的知识。 感谢Fluffeh 。 其他可能会阅读本文并可能认为我错了的人可以自由编辑和批评我的答案。 ( 老实说,我很感激纠正我的错误。

我将在MySQL标签中发布一些常见问题。


窍门1号( 与多个条件匹配的行

给定这个模式

 CREATE TABLE MovieList ( ID INT, MovieName VARCHAR(25), CONSTRAINT ml_pk PRIMARY KEY (ID), CONSTRAINT ml_uq UNIQUE (MovieName) ); INSERT INTO MovieList VALUES (1, 'American Pie'); INSERT INTO MovieList VALUES (2, 'The Notebook'); INSERT INTO MovieList VALUES (3, 'Discovery Channel: Africa'); INSERT INTO MovieList VALUES (4, 'Mr. Bean'); INSERT INTO MovieList VALUES (5, 'Expendables 2'); CREATE TABLE CategoryList ( MovieID INT, CategoryName VARCHAR(25), CONSTRAINT cl_uq UNIQUE(MovieID, CategoryName), CONSTRAINT cl_fk FOREIGN KEY (MovieID) REFERENCES MovieList(ID) ); INSERT INTO CategoryList VALUES (1, 'Comedy'); INSERT INTO CategoryList VALUES (1, 'Romance'); INSERT INTO CategoryList VALUES (2, 'Romance'); INSERT INTO CategoryList VALUES (2, 'Drama'); INSERT INTO CategoryList VALUES (3, 'Documentary'); INSERT INTO CategoryList VALUES (4, 'Comedy'); INSERT INTO CategoryList VALUES (5, 'Comedy'); INSERT INTO CategoryList VALUES (5, 'Action'); 

find至less属于ComedyRomance类别的所有电影

这个问题有时可能非常棘手。 看起来像这样的查询将是答案:

 SELECT DISTINCT a.MovieName FROM MovieList a INNER JOIN CategoryList b ON a.ID = b.MovieID WHERE b.CategoryName = 'Comedy' AND b.CategoryName = 'Romance' 

SQLFiddle演示

这肯定是错误的,因为它不会产生任何结果 。 对此的解释是每行只有一个CategoryName有效值。 例如,第一个条件返回true ,第二个条件总是false。 因此,通过使用AND运算符,两个条件都应该是真实的; 否则,这将是错误的。 另一个查询是这样的,

 SELECT DISTINCT a.MovieName FROM MovieList a INNER JOIN CategoryList b ON a.ID = b.MovieID WHERE b.CategoryName IN ('Comedy','Romance') 

SQLFiddle演示

并且结果仍然不正确,因为它匹配的logging在categoryName 上至less有一个匹配categoryName真正的解决scheme 是通过计算每个电影的logging实例的数量 。 实例的数量应该与条件中提供的值的总数相匹配。

 SELECT a.MovieName FROM MovieList a INNER JOIN CategoryList b ON a.ID = b.MovieID WHERE b.CategoryName IN ('Comedy','Romance') GROUP BY a.MovieName HAVING COUNT(*) = 2 

SQLFiddle演示(答案)

  • 关系部门的SQL

窍门2( 每个条目的最高logging

鉴于架构,

 CREATE TABLE Software ( ID INT, SoftwareName VARCHAR(25), Descriptions VARCHAR(150), CONSTRAINT sw_pk PRIMARY KEY (ID), CONSTRAINT sw_uq UNIQUE (SoftwareName) ); INSERT INTO Software VALUES (1,'PaintMe','used for photo editing'); INSERT INTO Software VALUES (2,'World Map','contains map of different places of the world'); INSERT INTO Software VALUES (3,'Dictionary','contains description, synonym, antonym of the words'); CREATE TABLE VersionList ( SoftwareID INT, VersionNo INT, DateReleased DATE, CONSTRAINT sw_uq UNIQUE (SoftwareID, VersionNo), CONSTRAINT sw_fk FOREIGN KEY (SOftwareID) REFERENCES Software(ID) ); INSERT INTO VersionList VALUES (3, 2, '2009-12-01'); INSERT INTO VersionList VALUES (3, 1, '2009-11-01'); INSERT INTO VersionList VALUES (3, 3, '2010-01-01'); INSERT INTO VersionList VALUES (2, 2, '2010-12-01'); INSERT INTO VersionList VALUES (2, 1, '2009-12-01'); INSERT INTO VersionList VALUES (1, 3, '2011-12-01'); INSERT INTO VersionList VALUES (1, 2, '2010-12-01'); INSERT INTO VersionList VALUES (1, 1, '2009-12-01'); INSERT INTO VersionList VALUES (1, 4, '2012-12-01'); 

find每个软件的最新版本。 显示以下列: SoftwareNameDescriptionsLatestVersion从VersionNo列 ), DateReleased

一些SQL开发人员错误地使用MAX()聚合函数。 他们倾向于这样创造,

 SELECT a.SoftwareName, a.Descriptions, MAX(b.VersionNo) AS LatestVersion, b.DateReleased FROM Software a INNER JOIN VersionList b ON a.ID = b.SoftwareID GROUP BY a.ID ORDER BY a.ID 

SQLFiddle演示

大多数RDBMS在这里产生一个语法错误,因为没有在group by子句中指定一些非聚集列 ),结果会在每个软件上产生正确的LatestVersion ,但显然DateReleased是不正确的。 MySQL不支持Window FunctionsCommon Table Expression但仍然像一些RDBMS一样。 解决这个问题的方法是创build一个subquery ,在每个软件上获取个体最大版本号,之后再join其他表中。

 SELECT a.SoftwareName, a.Descriptions, b.LatestVersion, c.DateReleased FROM Software a INNER JOIN ( SELECT SoftwareID, MAX(VersionNO) LatestVersion FROM VersionList GROUP BY SoftwareID ) b ON a.ID = b.SoftwareID INNER JOIN VersionList c ON c.SoftwareID = b.SoftwareID AND c.VersionNO = b.LatestVersion GROUP BY a.ID ORDER BY a.ID 

SQLFiddle演示(答案)


就这样了。 我会马上发布另一个关于MySQL标签的其他常见问题 。 感谢您阅读这篇小文章。 我希望你至less能从中得到一点知识。

更新1


技巧3( find两个ID之间的最新logging

给定架构

 CREATE TABLE userList ( ID INT, NAME VARCHAR(20), CONSTRAINT us_pk PRIMARY KEY (ID), CONSTRAINT us_uq UNIQUE (NAME) ); INSERT INTO userList VALUES (1, 'Fluffeh'); INSERT INTO userList VALUES (2, 'John Woo'); INSERT INTO userList VALUES (3, 'hims056'); CREATE TABLE CONVERSATION ( ID INT, FROM_ID INT, TO_ID INT, MESSAGE VARCHAR(250), DeliveryDate DATE ); INSERT INTO CONVERSATION VALUES (1, 1, 2, 'hi john', '2012-01-01'); INSERT INTO CONVERSATION VALUES (2, 2, 1, 'hello fluff', '2012-01-02'); INSERT INTO CONVERSATION VALUES (3, 1, 3, 'hey hims', '2012-01-03'); INSERT INTO CONVERSATION VALUES (4, 1, 3, 'please reply', '2012-01-04'); INSERT INTO CONVERSATION VALUES (5, 3, 1, 'how are you?', '2012-01-05'); INSERT INTO CONVERSATION VALUES (6, 3, 2, 'sample message!', '2012-01-05'); 

find两个用户之间的最新对话。

 SELECT b.Name SenderName, c.Name RecipientName, a.Message, a.DeliveryDate FROM Conversation a INNER JOIN userList b ON a.From_ID = b.ID INNER JOIN userList c ON a.To_ID = c.ID WHERE (LEAST(a.FROM_ID, a.TO_ID), GREATEST(a.FROM_ID, a.TO_ID), DeliveryDate) IN ( SELECT LEAST(FROM_ID, TO_ID) minFROM, GREATEST(FROM_ID, TO_ID) maxTo, MAX(DeliveryDate) maxDate FROM Conversation GROUP BY minFROM, maxTo ) 

SQLFiddle演示

第2部分 – 子查询

好吧,现在老板再次爆发了 – 我想要一个我们所有品牌的汽车清单,以及我们有多less品牌!

这是一个很好的机会使用我们的SQL好东西 – 子查询中的下一个技巧。 如果您对该术语不熟悉,则子查询是在另一个查询中运行的查询。 有很多不同的方式来使用它们。

对于我们的要求,让我们先把一个简单的查询结合起来,列出每辆车和品牌:

 select a.ID, b.brand from cars a join brands b on a.brand=b.ID 

现在,如果我们想简单地按照品牌sorting的汽车数量,我们当然可以写这个:

 select b.brand, count(a.ID) as countCars from cars a join brands b on a.brand=b.ID group by b.brand +--------+-----------+ | brand | countCars | +--------+-----------+ | BMW | 2 | | Ford | 2 | | Nissan | 1 | | Smart | 1 | | Toyota | 5 | +--------+-----------+ 

那么,我们应该能够简单地将计数函数添加到我们原来的查询权限中呢?

 select a.ID, b.brand, count(a.ID) as countCars from cars a join brands b on a.brand=b.ID group by a.ID, b.brand +----+--------+-----------+ | ID | brand | countCars | +----+--------+-----------+ | 1 | Toyota | 1 | | 2 | Ford | 1 | | 3 | Nissan | 1 | | 4 | Smart | 1 | | 5 | Toyota | 1 | | 6 | BMW | 1 | | 7 | Ford | 1 | | 8 | Toyota | 1 | | 9 | Toyota | 1 | | 10 | BMW | 1 | | 11 | Toyota | 1 | +----+--------+-----------+ 11 rows in set (0.00 sec) 

可悲的是,不,我们不能那样做。 原因是当我们添加汽车ID(列a.ID)时,我们必须将其添加到组中 – 所以现在,当计数function工作时,每个ID只有一个ID匹配。

这是我们可以使用子查询的地方 – 实际上,我们可以完成两种完全不同的子查询,它们将返回我们需要的相同结果。 首先是简单地把子查询放在select子句中。 这意味着每次我们获得一行数据时,子查询将会运行,获取一列数据,然后将其popup到我们的数据行中。

 select a.ID, b.brand, ( select count(c.ID) from cars c where a.brand=c.brand ) as countCars from cars a join brands b on a.brand=b.ID +----+--------+-----------+ | ID | brand | countCars | +----+--------+-----------+ | 2 | Ford | 2 | | 7 | Ford | 2 | | 1 | Toyota | 5 | | 5 | Toyota | 5 | | 8 | Toyota | 5 | | 9 | Toyota | 5 | | 11 | Toyota | 5 | | 3 | Nissan | 1 | | 4 | Smart | 1 | | 6 | BMW | 2 | | 10 | BMW | 2 | +----+--------+-----------+ 11 rows in set (0.00 sec) 

而巴姆!,这会做我们。 如果你注意到,这个子查询将不得不为我们返回的每一行数据运行。 即使在这个小例子中,我们只有五个不同的汽车品牌,但是子查询运行了十一次,因为我们有十一行数据正在返回。 所以,在这种情况下,它似乎不是编写代码最有效的方法。

对于不同的方法,让我们运行一个子查询并假装它是一个表:

 select a.ID, b.brand, d.countCars from cars a join brands b on a.brand=b.ID join ( select c.brand, count(c.ID) as countCars from cars c group by c.brand ) d on a.brand=d.brand +----+--------+-----------+ | ID | brand | countCars | +----+--------+-----------+ | 1 | Toyota | 5 | | 2 | Ford | 2 | | 3 | Nissan | 1 | | 4 | Smart | 1 | | 5 | Toyota | 5 | | 6 | BMW | 2 | | 7 | Ford | 2 | | 8 | Toyota | 5 | | 9 | Toyota | 5 | | 10 | BMW | 2 | | 11 | Toyota | 5 | +----+--------+-----------+ 11 rows in set (0.00 sec) 

好的,所以我们得到了相同的结果(顺序略有不同 – 看起来数据库想要返回我们这次select的第一列sorting的结果) – 但是同样正确的数字。

那么,两者之间有什么区别 – 我们应该什么时候使用每种types的子查询? 首先,让我们确定我们理解第二个查询是如何工作的。 我们在查询的from子句中select了两个表,然后写了一个查询,并告诉数据库它实际上是一个表,而数据库完全满意。 使用这种方法会有一些好处(以及一些限制)。 最重要的是这个子查询只运行一次 。 如果我们的数据库包含大量的数据,那么第一种方法可能会有很大的改进。 但是,由于我们使用这个表作为表,我们必须引入额外的数据行 – 以便它们实际上可以连接回我们的数据行。 如果我们要使用上面查询中的简单连接,还必须确保有足够的数据行。 如果您还记得,联接将只撤回具有匹配数据在联接两侧的行。 如果我们不小心,如果这个子查询中没有匹配的行,这可能会导致有效的数据不会从我们的cars表中返回。

现在,回头看第一个子查询,也有一些限制。 因为我们把数据拉回到单行,我们只能拉回一行数据。 在查询的select子句中使用的子查询通常只使用聚合函数,如sumcountmax或其他类似的聚合函数。 他们不需要,但通常是这样写的。

所以,在我们继续之前,让我们快速看看我们还可以使用子查询。 我们可以在where子句中使用它 – 现在,这个例子有点像我们的数据库一样,有更好的方法来获得下面的数据,但是看到它只是一个例子,让我们看看:

 select ID, brand from brands where brand like '%o%' +----+--------+ | ID | brand | +----+--------+ | 1 | Ford | | 2 | Toyota | | 6 | Holden | +----+--------+ 3 rows in set (0.00 sec) 

这会给我们一个品牌ID和品牌名称列表(第二列仅用于显示品牌),其中包含名称中的字母o

现在,我们可以在where子句中使用这个查询的结果:

 select a.ID, b.brand from cars a join brands b on a.brand=b.ID where a.brand in ( select ID from brands where brand like '%o%' ) +----+--------+ | ID | brand | +----+--------+ | 2 | Ford | | 7 | Ford | | 1 | Toyota | | 5 | Toyota | | 8 | Toyota | | 9 | Toyota | | 11 | Toyota | +----+--------+ 7 rows in set (0.00 sec) 

正如你所看到的,即使子查询返回了三个品牌ID,我们的汽车表只有两个条目。

在这种情况下,为了得到更多的细节,子查询就像我们写了下面的代码一样工作:

 select a.ID, b.brand from cars a join brands b on a.brand=b.ID where a.brand in (1,2,6) +----+--------+ | ID | brand | +----+--------+ | 1 | Toyota | | 2 | Ford | | 5 | Toyota | | 7 | Ford | | 8 | Toyota | | 9 | Toyota | | 11 | Toyota | +----+--------+ 7 rows in set (0.00 sec) 

同样,你可以看到从数据库返回时,子查询和手动input如何改变了行的顺序。

在我们讨论子查询时,让我们看看我们还可以用子查询来做些什么:

  • 您可以在另一个子查询中放置一个子查询,依此类推。 有一个取决于你的数据库的限制,但缺乏一些疯狂和疯狂的程序员的recursionfunction,大多数人将永远不会达到这个限制。
  • 您可以将多个子查询放入一个查询中, select子句中的一些, from子句中的一些以及where子句中的更多一些 – 只要记住每个子查询都会使查询更加复杂并且可能会花更长的时间来执行。

如果您需要编写一些高效的代码,那么以多种方式编写查询并查看(通过计时或通过使用解释计划)是最佳的查询来获得结果是有益的。 第一种方法可能并不总是最好的办法。

第3部分 – 技巧和有效的代码

MySQL in()效率

我想我会添加一些额外的位,提出了技巧和窍门。

One question I see come up a fair bit, is How do I get non-matching rows from two tables and I see the answer most commonly accepted as something like the following (based on our cars and brands table – which has Holden listed as a brand, but does not appear in the cars table):

 select a.ID, a.brand from brands a where a.ID not in(select brand from cars) 

And yes it will work.

 +----+--------+ | ID | brand | +----+--------+ | 6 | Holden | +----+--------+ 1 row in set (0.00 sec) 

However it is not efficient in some database. Here is a link to a Stack Overflow question asking about it, and here is an excellent in depth article if you want to get into the nitty gritty.

The short answer is, if the optimiser doesn't handle it efficiently, it may be much better to use a query like the following to get non matched rows:

 select a.brand from brands a left join cars b on a.id=b.brand where b.brand is null +--------+ | brand | +--------+ | Holden | +--------+ 1 row in set (0.00 sec) 

Update Table with same table in subquery

Ahhh, another oldie but goodie – the old You can't specify target table 'brands' for update in FROM clause .

MySQL will not allow you to run an update... query with a subselect on the same table. Now, you might be thinking, why not just slap it into the where clause right? But what if you want to update only the row with the max() date amoung a bunch of other rows? You can't exactly do that in a where clause.

 update brands set brand='Holden' where id= (select id from brands where id=6); ERROR 1093 (HY000): You can't specify target table 'brands' for update in FROM clause 

So, we can't do that eh? Well, not exactly. There is a sneaky workaround that a surprisingly large number of users don't know about – though it does include some hackery that you will need to pay attention to.

You can stick the subquery within another subquery, which puts enough of a gap between the two queries so that it will work. However, note that it might be safest to stick the query within a transaction – this will prevent any other changes being made to the tables while the query is running.

 update brands set brand='Holden' where id= (select id from (select id from brands where id=6 ) as updateTable); Query OK, 0 rows affected (0.02 sec) Rows matched: 1 Changed: 0 Warnings: 0 

You can use the concept of multiple queries in the FROM keyword. Let me show you one example:

 SELECT DISTINCT e.id,e.name,d.name,lap.lappy LAPTOP_MAKE,c_loc.cnty COUNTY FROM ( SELECT c.id cnty,l.name FROM county c, location l WHERE c.id=l.county_id AND l.end_Date IS NOT NULL ) c_loc, emp e INNER JOIN dept d ON e.deptno =d.id LEFT JOIN ( SELECT l.id lappy, c.name cmpy FROM laptop l, company c WHERE l.make = c.name ) lap ON e.cmpy_id=lap.cmpy 

You can use as many tables as you want to. Use outer joins and union where ever it's necessary, even inside table subqueries.

That's a very easy method to involve as many as tables and fields.

Hopes this makes it find the tables as you're reading through the thing:

jsfiddle

 mysql> show columns from colors; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(3) | NO | PRI | NULL | auto_increment | | color | varchar(15) | YES | | NULL | | | paint | varchar(10) | YES | | NULL | | +-------+-------------+------+-----+---------+----------------+