我想了解以下情况:
我正计划在我的(例如-PHP)应用程序中使用这一点,但不想对数据库运行多个查询,我有什么选项可以在单个查询中从多个表中获取数据呢?
注意:我之所以写这篇文章,是因为我希望能够链接到一个关于我在PHP队列中经常遇到的众多问题的很好的指南,所以当我发布答案时,我可以链接到这篇文章以获得更多的细节。
这些答案涵盖了以下几个方面:
这个答案包括:
从数据库中的多个表中检索数据有多种方法。在这个答案中,我将使用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,他有。
这是一个简单的两个表连接。我们有一个识别模型的表和其中有可用库存的表。如您所见,cars
表的model
列中的数据与我们拥有的cars
表的models
列相关。现在,我们知道models表的ID为1
,用于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)
答对了!老板很高兴。当然,作为一个老板,他从不满足自己的要求,他看了看信息,然后说我也想要颜色。
好的,我们已经编写了查询的很好的一部分,但是我们需要使用第三个表,它是colors。现在,我们的主信息表cars
存储了car color ID,这链接回colors 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
的列,该列链接回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)
天哪,真难看。然而,就数据库而言,正是所求。在查询中,我们从cars
中请求id
,从models
中请求model
。但是,因为我们没有指定如何连接表,数据库已经匹配了第一个表的每一行和第二个表的每一行。
好吧,老板回来了,他又想要更多的信息。我想要同样的清单,但也包括四轮驱动在其中。
然而,这给了我们一个很好的借口,看看两种不同的方法来实现这一点。我们可以在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
则不会。这确实意味着在使用union
时,在使用unionALL
时,性能会受到很小的影响,但结果可能是值得的--不过,我不会在本文中推测这类事情。
在这一点上,这里可能值得注意一些额外的注意事项。
order by
,但不能再使用别名。在上面的查询中,用a.id追加顺序将导致错误--就结果而言,该列被称为id
而不是a.id
-尽管两个查询中使用了相同的别名。
order by
语句,并且它必须作为最后一条语句。在下一个示例中,我将向表中添加一些额外的行。
我已将holden
添加到brands表中。我还在cars
中添加了一行,它的color
值为12
-在colors表中没有引用。
好了,老板又回来了,汪汪要求-*我要我们每个品牌的数量和车的数量!`-通常情况下,我们刚讨论到一个有趣的部分,老板要更多的工作。
好的,所以我们要做的第一件事就是得到一份可能的品牌的完整列表。
select
a.brand
from
brands a
+--------+
| brand |
+--------+
| Ford |
| Toyota |
| Nissan |
| Smart |
| BMW |
| Holden |
+--------+
6 rows in set (0.00 sec)
现在,当我们将它加入到我们的cars表中时,我们得到以下结果:
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
品牌。
这是因为联接会在两个表中查找匹配的行。由于cars中没有类型为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)
说完,老板就躲开了。
现在,为了更详细地解释这一点,外部联接可以是left
或right
类型。左或右定义了哪个表是完全包含的。左外部联接
将包括左侧表中的所有行,而右外部联接
将右侧表中的所有结果带入结果(您猜到了)。
某些数据库允许完全外部联接
,这将从两个表返回结果(无论匹配与否),但并非所有数据库都支持这一功能。
现在,我可能想知道,您是否可以在查询中合并连接类型--答案是肯定的,您绝对可以。
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)
那么,为什么这不是预期的结果呢?这是因为尽管我们选择了从cars到brands的外部连接,但它没有在连接到colors中指定--因此特定的连接只会带回两个表中匹配的结果。
下面是一个查询,它可以获得我们所期望的结果:
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)
正如我们所看到的,我们在查询中有两个外部连接,结果正如所期望的那样通过。
那么,其他类型的连接呢?十字路口呢?
不是所有数据库都支持交集
,但几乎所有数据库都允许您通过连接(或者至少是结构良好的where语句)创建交集。
交集是一种联接类型,与上面描述的联合
有点类似--但区别在于它只返回由联合联接的各个单独查询之间相同(我的意思是相同)的数据行。只有在所有方面都相同的行才会返回。
一个简单的例子如下:
select
*
from
colors
where
ID>2
intersect
select
*
from
colors
where
id<4
正常的union
查询将返回表的所有行(第一个查询返回id>2
上的任何内容,第二个查询返回具有id<4
的任何内容),从而得到完整的集合,而intersect查询将只返回与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)
如果希望使用不支持交集查询的数据库在两个不同的表之间执行交集,则需要在表的每一列上创建一个联接。
好的,我发现这篇文章非常有趣,我想分享我的一些关于创建查询的知识。谢谢你的这个绒毛。其他人可能读到这篇文章,可能觉得我错了,他们有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');
问题
查找至少同时属于喜剧
和浪漫
类别的所有电影。
解决方案
这个问题有时会很棘手。这样一个问题似乎就是答案:-
SELECT DISTINCT a.MovieName
FROM MovieList a
INNER JOIN CategoryList b
ON a.ID = b.MovieID
WHERE b.CategoryName = 'Comedy' AND
b.CategoryName = 'Romance'
这肯定是非常错误的,因为它没有产生任何结果。对此的解释是,每一行上只有一个categoryName
的有效值。例如,第一个条件返回true,第二个条件总是false。因此,通过使用和
运算符,这两个条件都应为真;否则就是假的。另一个查询是这样的,
SELECT DISTINCT a.MovieName
FROM MovieList a
INNER JOIN CategoryList b
ON a.ID = b.MovieID
WHERE b.CategoryName IN ('Comedy','Romance')
并且结果仍然不正确,因为它与在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
给定架构,
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列),DateReleed
解决方案
一些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
(由于没有在GROUP BY
子句上指定某些非聚合列,大多数RDBMS都会在此生成语法错误)结果在每个软件上生成正确的LatestVersion
,但显然DateReleed
不正确。MySQL
还不支持窗口函数
和公共表表达式
,有些RDBMS已经支持了这些。解决此问题的方法是创建一个子查询
,该查询获取每个软件上的单个最大版本号
,然后在其他表上进行联接。
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
所以就这样了。当我回忆起mysql
标签上的其他常见问题时,我将发布另一个。感谢您阅读这篇小文章。我希望你至少从这件事中得到哪怕是一点点的知识。
更新1
给定架构
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
)
好了,现在老板又闯进来了--我想要一份我们所有这个牌子的车的清单,以及我们总共有多少辆这个牌子的车!
这是一个很好的机会来使用我们的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)时,我们必须将其添加到组by中,所以现在,当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)
然后砰!,这会让我们受不了的。但是,如果您注意到,这个子查询必须针对我们返回的每一行数据运行。即使在这个小例子中,我们只有五个不同品牌的汽车,但是子查询运行了十一次,因为我们有十一行数据要返回。因此,在这种情况下,它似乎不是编写代码的最有效的方式。
对于另一种方法,让我们运行一个子查询并假设它是一个表:
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
子句中使用的子查询通常只使用聚合函数,如sum
,count
,max
或其他类似的聚合函数。他们不必这样做,但他们通常是这样写的。
因此,在继续之前,让我们快速了解一下还可以在哪些地方使用子查询。我们可以在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,我们的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
子句中--请记住,您放入的每个子查询都将使查询更加复杂,并且可能需要更长的时间来执行。如果您需要编写一些有效的代码,那么可以通过多种方式编写查询并查看(通过定时或使用解释计划)哪一个查询是获得结果的最佳查询。第一种有效的方法可能并不总是最好的方法。