459

我想知道以下内容:

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

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

注意:我写这篇文章是因为我希望能够链接到关于我在 PHP 队列中经常遇到的众多问题的编写良好的指南,所以当我发布答案时,我可以链接到这个以获得更多详细信息。

答案涵盖以下内容:

  1. 第 1 部分 - 连接和联合
  2. 第 2 部分 - 子查询
  3. 第 3 部分 - 技巧和高效代码
  4. 第 4 部分 - From 子句中的子查询
  5. 第 5 部分 - 混合的约翰的把戏
4

6 回答 6

500

第 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我们拥有的表格列相关。现在,我们知道模型表的 ID 为for,所以让我们编写连接。carsmodelscars1Sports

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表具有特定品牌的模型,因此也有一个名为brandwhich 的列链接回brands该字段上的表ID,则可以这样做:

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)

天哪,太丑了 但是,就数据库而言,这正是所要求的。在查询中,我们要求提供IDfromcarsmodelfrom 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

以下将返回所有 4WD:

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但结果可能是值得的——不过我不会推测这种事情。

在此说明中,可能值得注意的是此处的一些附加说明。

  • 如果我们想对结果进行排序,我们可以使用 anorder by但您不能再使用别名。在上面的查询中,附加 anorder 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)

就这样,老板偷偷溜走了。

现在,为了更详细地解释这一点,外连接可以是leftorright类型。Left 或 Right 定义了完全包含哪个表。Aleft outer join将包含左侧表格中的所有行,而(您猜对了)aright outer join将右侧表格中的所有结果带入结果中。

一些数据库将允许从两个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)

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

于 2012-09-18T11:11:39.310 回答
103

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

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

问题

查找至少属于类别的所有电影。 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 演示(答案)


Trick No. 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');

问题

查找每个软件的最新版本。显示以下列:SoftwareName, Descriptions, LatestVersion来自 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 已经支持的那样。解决此问题的方法是创建一个在每个软件上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


Trick No. 3(查找两个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 演示

于 2012-09-25T15:31:09.573 回答
66

第 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!,这对我们有用。如果你注意到了,这个子查询必须针对我们返回的每一行数据运行。即使在这个小例子中,我们也只有 5 个不同品牌的汽车,但子查询运行了 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,然后编写了一个查询并告诉数据库它实际上是一个表——数据库对此非常满意。使用此方法可能有一些好处(以及一些限制)。最重要的是这个子查询运行了一次。如果我们的数据库包含大量数据,那么很可能比第一种方法有很大的改进。但是,当我们将其用作表格时,我们必须引入额外的数据行 - 以便它们实际上可以连接回我们的数据行。我们还必须确保有足够的如果我们要像上面的查询那样使用简单的连接,则可以使用数据行。如果您还记得,联接只会拉回联接两侧具有匹配数据行。如果我们不小心,如果此子查询中没有匹配的行,这可能会导致无法从汽车表中返回有效数据。

现在,回顾第一个子查询,也有一些限制。因为我们将数据拉回单行,所以我们只能拉回一行数据。查询子句中使用的子查询通常仅使用聚合函数,select例如sum、或其他类似的聚合函数。他们不必这样做但这通常是他们的写作方式。countmax

所以,在我们继续之前,让我们快速看一下我们可以在哪里使用子查询。我们可以在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)

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

当我们讨论子查询时,让我们看看我们还能用子查询做什么:

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

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

于 2012-09-19T09:47:16.180 回答
61

第 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)

但是它在某些数据库中效率不高。这是一个指向 Stack Overflow 问题的链接,如果您想深入了解它,这里是一篇极好的深度文章。

简短的回答是,如果优化器不能有效地处理它,最好使用如下查询来获取不匹配的行:

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 子句中指定目标表“品牌”进行更新

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
于 2012-09-20T08:01:53.630 回答
19

您可以在 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

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

这是涉及尽可能多的表和字段的一种非常简单的方法。

于 2012-09-22T08:23:09.400 回答
10

希望这使它在您阅读内容时找到表格:

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    |                |
+-------+-------------+------+-----+---------+----------------+
于 2015-02-19T18:46:47.453 回答