SQL查询从多个表返回数据


434

我想知道以下内容:

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

我打算在我的应用程序(例如,PHP)中使用此功能,但是不想对数据库运行多个查询,我需要在单个查询中从多个表中获取数据的哪些选项?

注意:我正在写这篇文章是因为我希望能够链接到有关我在PHP队列中不断遇到的众多问题的书面指南,因此在发布答案时可以链接至此以获取更多详细信息。

答案涵盖以下内容:

  1. 第1部分-联接和联合
  2. 第2部分-子查询
  3. 第3部分-技巧和有效代码
  4. 第4部分-“从”子句中的子查询
  5. 第五部分-约翰技巧的混合袋

Answers:


469

第1部分-联接和联合

该答案包括:

  1. 第1部分
  2. 第2部分
    • 子查询-它们是什么,可以在哪里使用以及需要注意什么
    • 笛卡尔加入AKA-痛苦!

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

我还将使用小型堆场的概念,该堆场具有一个数据库来跟踪其可用的汽车。所有者已将您雇用为他的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)

品牌表标识了车库外可能出售的汽车的不同品牌。

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)

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

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)

这将为我们提供足够的数据(我希望),以掩盖下面不同类型的联接的示例,并提供足够的数据以使它们值得。

因此,老板想了解这个问题,老板想知道他拥有的所有跑车的ID

这是一个简单的两张表联接。我们有一个表,用于标识模型以及具有可用库存的表。正如你所看到的,在数据model的列cars表涉及models的列cars,我们有表。现在,我们知道models表的ID为1for,Sports因此让我们编写联接。

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

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

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

哦,不!我们的第一个查询有错误!是的,它是一个李子。您会看到,查询确实有正确的列,但是两个表中都存在一些列,因此数据库对于实际的含义是什么以及在何处感到困惑。有两种解决方案可以解决此问题。第一个很简单,我们可以使用它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)

另一种可能更常用,称为表别名。此示例中的表具有简单易用的简单名称,但是键入类似的名称KPI_DAILY_SALES_BY_DEPARTMENT可能很快就会变老,因此一种简单的方法是对表进行昵称,如下所示:

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

现在,返回到请求。如您所见,我们拥有所需的信息,但是我们也有未要求提供的信息,因此我们需要在语句中包含where子句,以便仅按要求获取跑车。由于我更喜欢​​表别名方法,而不是一遍又一遍地使用表名,因此从现在开始,我将坚持使用它。

显然,我们需要在查询中添加where子句。我们可以通过ID=1或识别跑车model='Sports'。由于ID已编入索引且主键(而且恰好键入较少),因此请在查询中使用它。

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列。因此,以与原始表类似的方式,我们可以连接第三个表:

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)

该死,尽管表已正确连接并且相关列已链接,但我们忘记从刚链接的新表中提取实际信息

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表,但这是一个示例。如果该models表具有特定于品牌的模型,因此还具有一个称为的列brand,该列链接回brandsID字段中的表,则可以这样进行:

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)

天哪,这很丑。但是,就数据库而言,正是所要求的。在查询中,我们要求IDfrom carsmodelfrom models。但是,因为我们没有指定如何联接表,数据库匹配了每一个从第一表行与每一从第二表行。

好的,老板回来了,他希望再次提供更多信息。我想要相同的列表,但还要包含4WD

但是,这为我们提供了一个很好的借口来研究实现此目的的两种不同方法。我们可以向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对于特定情况,查询可能会更好。它们是从不容易连接在一起的表中返回表中特定结果的好方法-或完全无关的表。但是,有一些规则要遵循。

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

现在,您可能想知道使用union和之间有什么区别union all。一个union查询将删除重复,而union all不会。这确实意味着使用unionover 时性能会受到较小的影响,union all但是结果可能值得-尽管我不会在这种情况下进行推测。

关于此注释,在这里可能需要注意一些其他注释。

  • 如果要对结果进行排序,可以使用an,order by但不能再使用别名。在上面的查询中,附加an order by a.ID会导致错误-就结果而言,该列将被调用,ID而不是a.ID-即使两个查询都使用了相同的别名。
  • 我们只能有一个order by声明,并且必须作为最后一个声明。

对于下一个示例,我将向表中添加一些额外的行。

我已添加Holden到品牌表。我还添加了一行,到cars具有color的价值12-它在颜色表中没有提及。

好吧,老板又回来了,咆哮着请求-*我想统计一下我们经营的每个品牌以及其中的汽车数量! 。

Rightyo,所以我们要做的第一件事就是完整列出所有可能的品牌。

select
    a.brand
from
    brands a

+--------+
| brand  |
+--------+
| Ford   |
| Toyota |
| Nissan |
| Smart  |
| BMW    |
| Holden |
+--------+
6 rows in set (0.00 sec)

现在,当我们将其连接到汽车表时,将得到以下结果:

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我添加的可爱品牌。

这是因为联接在两个表中查找匹配的行。由于汽车中没有任何类型的数据,Holden因此不会返回。这是我们可以使用outer联接的地方。这将返回一个表中的所有结果,无论它们是否与另一表中的结果匹配:

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)

这样一来,老板就走了。

现在,为了更详细地说明这一点,外部联接可以是leftright类型。左或右定义完全包含哪个表。A left outer join将包括左侧表中的所有行,而(您猜对了)a right outer join将右侧表中的所有结果带入结果中。

某些数据库允许使用a full outer join来从两个表中带回结果(无论是否匹配),但是并非所有数据库都支持。

现在,我可能想知道此时,您想知道是否可以在查询中合并联接类型-答案是肯定的,您绝对可以。

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)

那么,为什么不是预期的结果呢?这是因为尽管我们选择了从汽车到品牌的外部联接,但未在颜色联接中指定-因此特定联接只会带回在两个表中都匹配的结果。

这是可以获取我们期望的结果的查询:

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)

如我们所见,查询中有两个外部联接,结果按预期进行。

现在,您问这些其他类型的联接如何?那路口呢?

好吧,并非所有数据库都支持,intersection但是几乎所有数据库都将允许您通过联接(或至少结构良好的where语句)创建交集。

Intersection是一种连接类型,与union上述的连接有点类似-但区别在于它返回由联合连接的各个单个查询之间相同(并且我的意思是相同)的数据行。仅返回在各方面相同的行。

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

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

普通union查询将返回表的所有行(第一个查询返回的内容ID>2,第二个返回具有的内容ID<4),这将导致一个完整的记录集,而相交的查询将仅返回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)

如果希望使用本身不支持交集查询的数据库在两个不同的表之间执行交集,则需要在表的每一列上创建一个联接。


2
@Fluffeh好答案。我有一个建议:如果您想使其成为杀手级的SQL教程,则只会添加Venn图;多亏了他们,我立即了解了左右联接。个人要求:您是否有关于常见错误/性能调整的任何教程?
StrayChild01年

25
天啊。我的滚轮坏了。很棒的问题和答案。我希望我可以投票10次。
阿马尔·穆拉利

3
呵呵,谢谢您的积极反馈。继续滚动,这只是第一个答案。所以说我的答案太长了,无法适合一个“答案”,所以我不得不用几个:)
Fluffeh 2013年

7
老实说,我认为这个答案需要缩短一些。
einpoklum 2015年

优秀的文章。数据库连接101
maqs

101

好的,我发现这篇文章非常有趣,我想分享一些有关创建查询的知识。感谢您的Fluffeh。其他可能会读到此书并可能觉得我错了的人可以101%的自由编辑和批评我的答案。(老实说,我非常感谢您纠正我的错误。

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


第一招(匹配多个条件的行

给定这种架构

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');

查找至少同时属于和类别的所有电影 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演示

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

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演示(答案)


招数2(每次输入的最高记录

给定架构,

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');

在每个软件上找到最新版本。显示以下列: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演示

由于未在group by子句中指定一些未聚合的列,因此大多数RDBMS会在此上产生语法错误),结果LatestVersion在每个软件上都可以生成正确的代码,但显然DateReleased是不正确的。MySQL不支持Window FunctionsCommon Table Expression但是像某些RDBMS一样已经支持。解决此问题的方法是创建一个在每个软件上subquery获得最大值versionNo,然后在其他表上联接的最大值。

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。感谢您阅读这篇小文章。我希望您至少能从中学到一些知识。

更新1


第三招(查找两个ID之间的最新记录

给定架构

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');

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

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演示


太棒了!请注意,您的第一个解决方案之所以有效,是因为这两个字段都有唯一的约束。您本可以使用更通用的解决方案来解决一个常见问题。在我看来,唯一的解决办法是为做个人选择comedyromanceHaving当时不适合..
nawfal 2012年

@nawfal不是真的,如果未添加唯一约束,则需要distincthading子句中添加SQLFiddle Demo:D
John Woo,

63

第2部分-子查询

好吧,现在老板又来了- 我想要列出我们所有带有该品牌的汽车的清单,以及我们拥有的该品牌的总数!

这是使用SQL礼包中的下一个技巧(子查询)的绝佳机会。如果您不熟悉该术语,则子查询是在另一个查询中运行的查询。有许多不同的使用方式。

对于我们的请求,让我们首先进行一个简单的查询,该查询将列出每个汽车和品牌:

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

现在,如果我们只是想获得按品牌分类的汽车数量,我们当然可以这样写:

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 |
+--------+-----------+

因此,我们应该能够简单地将count函数添加到原始查询中,对吗?

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)时,我们必须将其添加到分组中-因此,现在,当count函数起作用时,每个ID仅匹配一个ID。

但是,我们可以在这里使用子查询-实际上,我们可以执行两种完全不同类型的子查询,这些子查询将返回为此所需的相同结果。首先是简单地将子查询放在select子句中。这意味着每次我们获取一行数据时,子查询都会运行,获取一列数据,然后将其弹出到我们的数据行中。

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)

和Bam !,这可以帮助我们。但是,如果您注意到了,该子查询将必须针对我们返回的每一行数据运行。即使在这个小例子中,我们也只有五个不同品牌的汽车,但是子查询运行了11次,因为我们要返回的数据是11行​​。因此,在这种情况下,这似乎不是编写代码的最有效方法。

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

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)

好的,所以我们得到的结果是相同的(顺序略有不同-似乎数据库希望返回我们这次选择的第一列排序的结果)-但数字正确。

那么,两者之间有什么区别-什么时候应该使用每种类型的子查询?首先,请确保我们了解第二个查询的工作方式。我们在from查询的子句中选择了两个表,然后编写了一个查询,并告诉数据库它实际上是一个表-数据库对此非常满意。有可能是使用这种方法一些好处(以及一些限制)。最重要的是,此子查询只运行了一次。如果我们的数据库包含大量数据,则与第一种方法相比,可能会有很大的改进。但是,当我们将其用作表时,我们必须引入额外的数据行-以便实际上可以将它们联接回到我们的数据行中。我们还必须确保有足够的如果我们要像上面的查询中那样使用简单的联接,则显示数据行。如果你还记得,连接只会拉回到那个有匹配数据行方的加入。如果我们不小心,如果此子查询中没有匹配的行,则可能会导致无法从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)

这将向我们返回一个包含o名称中字母的品牌ID和品牌名称的列表(第二列仅用于向我们显示品牌)。

现在,我们可以在以下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,我们的cars表也只有其中两个的条目。

在这种情况下,为进一步详细说明,子查询的工作就像我们编写了以下代码一样:

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)

同样,您可以看到子查询与手动输入如何从数据库返回时如何更改行的顺序。

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

  • 您可以将一个子查询放在另一个子查询中,依此类推。有一个限制取决于您的数据库,但是缺少一些疯狂而疯狂的程序员的递归功能,大多数人永远都不会达到这个限制。
  • 您可以将一些子查询到一个查询,几个在select条款,一些在from子句和一对夫妇更多的where条款-只要记住你把每一个都让你的查询更复杂,可能需要较长的时间执行。

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


对于新开发者而言非常重要:子查询可能针对每个结果运行一次,除非您可以将子查询用作联接(如上所示)。
Xeoncross

59

第3部分-技巧和有效代码

MySQL in()效率

我以为我会增加一些额外的提示和技巧。

我看到的一个问题是,如何从两个表中获取不匹配的行,我看到了最常见的答案,如下所示(根据我们的汽车和品牌表- 霍尔顿列为品牌,但未出现在汽车表中):

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

而且它的工作。

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

但是,它在某些数据库中效率高。这是一个有关此问题的堆栈溢出问题链接,如果您想了解更多细节,请阅读深度文章

简短的答案是,如果优化器不能有效地处理它,那么使用如下所示的查询来获取不匹配的行可能会更好:

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)

用子查询中的相同表更新表

啊,又是老歌,又是老歌-老歌不能在FROM子句中指定目标表'brands'进行更新

MySQL不允许您update...在同一表上运行带有子选择的查询。现在,您可能在想,为什么不只是将其拍打到where子句中呢?但是,如果您只想更新max()其他行之间的日期呢?您无法在where子句中完全做到这一点。

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

所以,我们不能这样做吗?好吧,不完全是。有一个偷偷摸摸的解决方法,很多用户都不知道-尽管它确实包含一些黑客,您需要注意。

您可以将子查询保留在另一个子查询中,这将在两个查询之间留出足够的间隔,以便它可以工作。但是,请注意,将查询保留在事务中可能是最安全的-这将防止在查询运行时对表进行任何其他更改。

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

3
只是要注意,从“效率”的角度来看,WHERE NOT EXISTS()构造几乎是相同的,但在我看来,它更易于阅读/理解。再说一次,我的知识仅限于MSSQL,如果在其他平台上也是如此,我将发誓。
deroby 2012年

前几天,我只是尝试了这种类型的比较,其中NOT IN()包含一个约有数百个ID的列表,并且它与查询的联接版本之间没有区别。当您迈入数千或数十亿时,也许会有所作为。
Buttle Butkus

18

您可以在FROM关键字中使用多个查询的概念。让我给你看一个例子:

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

您可以使用任意数量的表。在需要的地方使用外部联接和联合,即使在表子查询内部也是如此。

这是涉及表和字段的非常简单的方法。


8

希望这能使您在阅读事物时找到表:

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    |                |
+-------+-------------+------+-----+---------+----------------+
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.