SQL查询从多个表返回数据

本文翻译自:SQL query return data from multiple tables

I would like to know the following: 我想知道以下内容:

  • how to get data from multiple tables in my database? 如何从我的数据库中的多个表中获取数据?
  • what types of methods are there to do this? 有什么类型的方法可以做到这一点?
  • what are joins and unions and how are they different from one another? 什么是联盟和联盟?它们如何彼此不同?
  • When should I use each one compared to the others? 我应该何时使用每一个与其他人相比?

I am planning to use this in my (for example - PHP) application, but don't want to run multiple queries against the database, what options do I have to get data from multiple tables in a single query? 我打算在我的(例如 - PHP)应用程序中使用它,但不想对数据库运行多个查询,我有什么选项可以从单个查询中的多个表中获取数据?

Note: I am writing this as I would like to be able to link to a well written guide on the numerous questions that I constantly come across in the PHP queue, so I can link to this for further detail when I post an answer. 注意:我正在写这篇文章,因为我希望能够链接到我在PHP队列中经常遇到的众多问题的精心编写的指南,因此我可以在发布答案时链接到此以获取更多详细信息。

The answers cover off the following: 答案涵盖以下内容:

  1. Part 1 - Joins and Unions 第1部分 - 联接和联盟
  2. Part 2 - Subqueries 第2部分 - 子查询
  3. Part 3 - Tricks and Efficient Code 第3部分 - 技巧和有效代码
  4. Part 4 - Subqueries in the From Clause 第4部分 - From子句中的子查询
  5. Part 5 - Mixed Bag of John's Tricks 第5部分 - John's Tricks的混合包

#1楼

参考:https://stackoom.com/question/qLXO/SQL查询从多个表返回数据


#2楼

Part 1 - Joins and Unions 第1部分 - 联接和联盟

This answer covers: 这个答案包括:

  1. Part 1 第1部分
    • Joining two or more tables using an inner join (See the wikipedia entry for additional info) 使用内部联接连接两个或多个表(有关其他信息,请参阅维基百科条目 )
    • How to use a union query 如何使用联合查询
    • Left and Right Outer Joins (this stackOverflow answer is excellent to describe types of joins) 左外连接(此stackOverflow答案非常适合描述连接类型)
    • Intersect queries (and how to reproduce them if your database doesn't support them) - this is a function of SQL-Server ( see info ) and part of the reason I wrote this whole thing in the first place. 相交查询(以及如果数据库不支持它们如何重现它们) - 这是SQL-Server的功能( 请参阅info ),这也是我首先编写这一整个内容的部分原因 。
  2. Part 2 第2部分
    • Subqueries - what they are, where they can be used and what to watch out for 子查询 - 它们是什么,它们可以在哪里使用以及需要注意什么
    • Cartesian joins AKA - Oh, the misery! 笛卡尔加入了AKA - 哦,痛苦!

There are a number of ways to retrieve data from multiple tables in a database. 有许多方法可以从数据库中的多个表中检索数据。 In this answer, I will be using ANSI-92 join syntax. 在这个答案中,我将使用ANSI-92连接语法。 This may be different to a number of other tutorials out there which use the older ANSI-89 syntax (and if you are used to 89, may seem much less intuitive - but all I can say is to try it) as it is much easier to understand when the queries start getting more complex. 这可能与许多使用旧版ANSI-89语法的其他教程不同(如果你习惯了89,可能看起来不那么直观 - 但我只能说是尝试它),因为它容易了解查询何时开始变得更复杂。 Why use it? 为什么要用它? Is there a performance gain? 是否有性能提升? The short answer is no, but it is easier to read once you get used to it. 简短的回答是否定的,但是一旦你习惯它就会更容易阅读。 It is easier to read queries written by other folks using this syntax. 使用此语法更容易读取其他人编写的查询。

I am also going to use the concept of a small caryard which has a database to keep track of what cars it has available. 我还将使用一个小型caryard的概念,它有一个数据库来跟踪它有哪些可用的汽车。 The owner has hired you as his IT Computer guy and expects you to be able to drop him the data that he asks for at the drop of a hat. 所有者已雇用您作为他的IT计算机人员,并希望您能够将他所要求的数据丢给他。

I have made a number of lookup tables that will be used by the final table. 我已经制作了一些将由最终表使用的查找表。 This will give us a reasonable model to work from. 这将为我们提供一个合理的模型。 To start off, I will be running my queries against an example database that has the following structure. 首先,我将针对具有以下结构的示例数据库运行查询。 I will try to think of common mistakes that are made when starting out and explain what goes wrong with them - as well as of course showing how to correct them. 我将尝试思考在开始时所犯的常见错误,并解释它们出了什么问题 - 当然还要说明如何纠正错误。

The first table is simply a color listing so that we know what colors we have in the car yard. 第一个表格只是一个颜色列表,以便我们知道我们在车场里有什么颜色。

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)

The brands table identifies the different brands of the cars out caryard could possibly sell. 品牌表标识了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)

The model table will cover off different types of cars, it is going to be simpler for this to use different car types rather than actual car models. 模型表将涵盖不同类型的汽车,使用不同的汽车类型而不是实际的汽车模型会更简单。

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)

And finally, to tie up all these other tables, the table that ties everything together. 最后,要将所有这些其他表格捆绑在一起,将所有这些表格联系在一起。 The ID field is actually the unique lot number used to identify cars. 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)

This will give us enough data (I hope) to cover off the examples below of different types of joins and also give enough data to make them worthwhile. 这将为我们提供足够的数据(我希望),以覆盖不同类型的连接的下面的示例,并提供足够的数据,使它们值得。

So getting into the grit of it, the boss wants to know The IDs of all the sports cars he has . 因此,老板想知道他拥有的所有跑车的身份证

This is a simple two table join. 这是一个简单的两个表连接。 We have a table that identifies the model and the table with the available stock in it. 我们有一个表格,用于识别模型和包含可用库存的表格。 As you can see, the data in the model column of the cars table relates to the models column of the cars table we have. 如您所见, cars表的model列中的数据与我们拥有的cars表的models列相关。 Now, we know that the models table has an ID of 1 for Sports so lets write the join. 现在,我们知道模型表的Sports ID为1 ,因此我们可以编写连接。

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

So this query looks good right? 那么这个查询看起来不错吧? We have identified the two tables and contain the information we need and use a join that correctly identifies what columns to join on. 我们已经确定了两个表并包含了我们需要的信息,并使用了一个正确标识要加入的列的连接。

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

Oh noes! 哦,不! An error in our first query! 我们的第一个查询出错了! Yes, and it is a plum. 是的,这是一个梅花。 You see, the query has indeed got the right columns, but some of them exist in both tables, so the database gets confused about what actual column we mean and where. 你看,查询确实得到了正确的列,但是其中一些列存在于两个表中,因此数据库对我们所指的实际列和位置感到困惑。 There are two solutions to solve this. 有两种解决方案可以解决这个问题。 The first is nice and simple, we can use tableName.columnName to tell the database exactly what we mean, like this: 第一个很好很简单,我们可以使用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)

The other is probably more often used and is called table aliasing. 另一种可能更常用,称为表别名。 The tables in this example have nice and short simple names, but typing out something like KPI_DAILY_SALES_BY_DEPARTMENT would probably get old quickly, so a simple way is to nickname the table like this: 这个例子中的表有简短的简单名称,但键入类似KPI_DAILY_SALES_BY_DEPARTMENT东西可能会很快变老,所以一个简单的方法就是像这样昵称表:

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

Now, back to the request. 现在,回到请求。 As you can see we have the information we need, but we also have information that wasn't asked for, so we need to include a where clause in the statement to only get the Sports cars as was asked. 正如您所看到的,我们拥有所需的信息,但我们也有未被要求的信息,因此我们需要在声明中包含where子句,以便按照要求获得跑车。 As I prefer the table alias method rather than using the table names over and over, I will stick to it from this point onwards. 由于我更喜欢​​表别名方法而不是一遍又一遍地使用表名,因此我将从这一点开始坚持使用它。

Clearly, we need to add a where clause to our query. 显然,我们需要在查询中添加一个where子句。 We can identify Sports cars either by ID=1 or model='Sports' . 我们可以通过ID=1model='Sports'来识别跑车。 As the ID is indexed and the primary key (and it happens to be less typing), lets use that in our query. 由于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)

Bingo! 答对了! The boss is happy. 老板很高兴。 Of course, being a boss and never being happy with what he asked for, he looks at the information, then says I want the colors as well . 当然,作为老板,从不满足于他的要求,他会查看信息,然后说我也想要颜色

Okay, so we have a good part of our query already written, but we need to use a third table which is colors. 好的,所以我们已经编写了很多查询,但我们需要使用第三个颜色表。 Now, our main information table cars stores the car color ID and this links back to the colors ID column. 现在,我们的主要信息表cars存储汽车颜色ID,并将其链接回颜色ID列。 So, in a similar manner to the original, we can join a third table: 因此,以与原始类似的方式,我们可以加入第三个表:

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)

Damn, although the table was correctly joined and the related columns were linked, we forgot to pull in the actual information from the new table that we just linked. 该死的,虽然表格已正确连接且相关列已链接,但我们忘记从刚刚链接的新表中提取实际信息

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)

Right, that's the boss off our back for a moment. 是的,那是我们背后的老板。 Now, to explain some of this in a little more detail. 现在,更详细地解释其中的一些内容。 As you can see, the from clause in our statement links our main table (I often use a table that contains information rather than a lookup or dimension table. The query would work just as well with the tables all switched around, but make less sense when we come back to this query to read it in a few months time, so it is often best to try to write a query that will be nice and easy to understand - lay it out intuitively, use nice indenting so that everything is as clear as it can be. If you go on to teach others, try to instill these characteristics in their queries - especially if you will be troubleshooting them. 正如您所看到的,我们的语句中的from子句链接了我们的主表(我经常使用一个包含信息而不是查找或维度表的表。查询也可以与所有切换的表一样工作,但没有意义当我们回到这个查询在几个月的时间内阅读它,所以通常最好尝试编写一个很好且易于理解的查询 - 直观地列出,使用精美的缩进以便一切都清晰如果你继续教别人,试着在他们的查询中灌输这些特征 - 特别是如果你要对它们进行故障排除。

It is entirely possible to keep linking more and more tables in this manner. 完全可以以这种方式连接越来越多的表。

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

While I forgot to include a table where we might want to join more than one column in the join statement, here is an example. 虽然我忘了在join语句中包含一个我们可能想要连接多个列的表,但这是一个例子。 If the models table had brand-specific models and therefore also had a column called brand which linked back to the brands table on the ID field, it could be done as this: 如果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

You can see, the query above not only links the joined tables to the main cars table, but also specifies joins between the already joined tables. 您可以看到,上面的查询不仅将连接表链接到主cars表,还指定已连接表之间的连接。 If this wasn't done, the result is called a cartesian join - which is dba speak for bad. 如果没有这样做,结果将被称为笛卡尔联合 - 这是dba说不好的。 A cartesian join is one where rows are returned because the information doesn't tell the database how to limit the results, so the query returns all the rows that fit the criteria. 笛卡尔连接是返回行的连接,因为该信息不会告诉数据库如何限制结果,因此查询将返回符合条件的所有行。

So, to give an example of a cartesian join, lets run the following query: 因此,为了举一个笛卡尔连接的例子,让我们运行以下查询:

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)

Good god, that's ugly. 天哪,这太丑了。 However, as far as the database is concerned, it is exactly what was asked for. 但是,就数据库而言,它正是所要求的。 In the query, we asked for for the ID from cars and the model from models . 在查询中,我们要求提供carsIDmodelmodels However, because we didn't specify how to join the tables, the database has matched every row from the first table with every row from the second table. 但是,因为我们并没有指定如何联接表,数据库匹配了从第一个表的每一行与第二个表的每一行。

Okay, so the boss is back, and he wants more information again. 好的,所以老板又回来了,他又想要更多的信息。 I want the same list, but also include 4WDs in it . 我想要相同的列表,但也包括4WD

This however, gives us a great excuse to look at two different ways to accomplish this. 然而,这给了我们一个很好的借口来看两种不同的方法来实现这一目标。 We could add another condition to the where clause like this: 我们可以在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

While the above will work perfectly well, lets look at it differently, this is a great excuse to show how a union query will work. 虽然上面的内容非常有效,但让我们看一下,这是展示union查询如何工作的一个很好的借口。

We know that the following will return all the Sports cars: 我们知道以下将返回所有跑车:

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

And the following would return all the 4WDs: 以下将返回所有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

So by adding a union all clause between them, the results of the second query will be appended to the results of the first query. 因此,通过在它们之间添加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)

As you can see, the results of the first query are returned first, followed by the results of the second query. 如您所见,首先返回第一个查询的结果,然后返回第二个查询的结果。

In this example, it would of course have been much easier to simply use the first query, but union queries can be great for specific cases. 在这个例子中,简单地使用第一个查询当然要容易得多,但是对于特定情况, union查询可能很好。 They are a great way to return specific results from tables from tables that aren't easily joined together - or for that matter completely unrelated tables. 它们是从表格中返回特定结果的好方法,这些表格不容易连接在一起 - 或者就完全不相关的表格而言。 There are a few rules to follow however. 但是,有一些规则要遵循。

  • The column types from the first query must match the column types from every other query below. 第一个查询中的列类型必须与下面每个其他查询的列类型相匹配。
  • The names of the columns from the first query will be used to identify the entire set of results. 第一个查询中的列名称将用于标识整个结果集。
  • The number of columns in each query must be the same. 每个查询中的列数必须相同。

Now, you might be wondering what the difference is between using union and union all . 现在,您可能想知道使用unionunion all之间的区别是什么 。 A union query will remove duplicates, while a union all will not. 一个union查询将删除重复的,而union all不会。 This does mean that there is a small performance hit when using union over union all but the results may be worth it - I won't speculate on that sort of thing in this though. 这确实意味着在使用union over union all时会有很小的性能损失,但结果可能是值得的 - 尽管如此我不会推测这类事情。

On this note, it might be worth noting some additional notes here. 在这方面,可能值得注意一些补充说明。

  • If we wanted to order the results, we can use an order by but you can't use the alias anymore. 如果我们想要订购结果,我们可以使用order by但您不能再使用别名。 In the query above, appending an order by a.ID would result in an error - as far as the results are concerned, the column is called ID rather than a.ID - even though the same alias has been used in both queries. 在上面的查询中, order by a.ID附加order by a.ID会导致错误 - 就结果而言,该列称为ID而不是a.ID - 即使在两个查询中都使用了相同的别名。
  • We can only have one order by statement, and it must be as the last statement. 我们只能有一个order by语句,它必须是最后一个语句。

For the next examples, I am adding a few extra rows to our tables. 对于下一个示例,我在表中添加了一些额外的行。

I have added Holden to the brands table. 我已将Holden添加到品牌表中。 I have also added a row into cars that has the color value of 12 - which has no reference in the colors table. 我还在cars中添加了一行, color值为12 - 在颜色表中没有参考。

Okay, the boss is back again, barking requests out - *I want a count of each brand we carry and the number of cars in it!` - Typical, we just get to an interesting section of our discussion and the boss wants more work. 好吧,老板又回来了,咆哮着要求 - *我想要计算我们携带的每个品牌及其中的汽车数量!` - 典型的,我们只是讨论一个有趣的部分,老板想要更多的工作。

Rightyo, so the first thing we need to do is get a complete listing of possible brands. Rightyo,所以我们需要做的第一件事是获得可能的品牌的完整列表。

select
    a.brand
from
    brands a

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

Now, when we join this to our cars table we get the following result: 现在,当我们将它加入我们的汽车表时,我们得到以下结果:

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)

Which is of course a problem - we aren't seeing any mention of the lovely Holden brand I added. 这当然是一个问题 - 我们没有看到我添加的可爱的Holden品牌。

This is because a join looks for matching rows in both tables. 这是因为连接在两个表中查找匹配的行。 As there is no data in cars that is of type Holden it isn't returned. 由于Holden类型的汽车中没有数据,因此不予退货。 This is where we can use an outer join. 这是我们可以使用outer连接的地方。 This will return all the results from one table whether they are matched in the other table or not: 这将返回一个表中的所有结果,无论它们是否与另一个表匹配:

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)

Now that we have that, we can add a lovely aggregate function to get a count and get the boss off our backs for a moment. 现在我们已经有了这个,我们可以添加一个可爱的聚合函数来计算并暂时让老板退缩。

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)

And with that, away the boss skulks. 随之而来的是,老板偷偷摸摸。

Now, to explain this in some more detail, outer joins can be of the left or right type. 现在,为了更详细地解释这一点,外连接可以是leftright类型。 The Left or Right defines which table is fully included. 左或右定义完全包含哪个表。 A left outer join will include all the rows from the table on the left, while (you guessed it) a right outer join brings all the results from the table on the right into the results. left outer join将包括左侧表中的所有行,而(您猜对了) right outer join将右侧表中的所有结果带入结果中。

Some databases will allow a full outer join which will bring back results (whether matched or not) from both tables, but this isn't supported in all databases. 某些数据库将允许full outer join ,这将从两个表中返回结果(无论是否匹配),但并非所有数据库都支持此结果。

Now, I probably figure at this point in time, you are wondering whether or not you can merge join types in a query - and the answer is yes, you absolutely can. 现在,我可能想到了这个时间点,你想知道你是否可以在查询中合并连接类型 - 答案是肯定的,你绝对可以。

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)

So, why is that not the results that were expected? 那么,为什么这不是预期的结果呢? It is because although we have selected the outer join from cars to brands, it wasn't specified in the join to colors - so that particular join will only bring back results that match in both tables. 这是因为虽然我们选择了从汽车到品牌的外部联接,但是在颜色的连接中没有指定 - 因此特定的连接只会带回两个表中匹配的结果。

Here is the query that would work to get the results that we expected: 以下是可以获得我们预期结果的查询:

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)

As we can see, we have two outer joins in the query and the results are coming through as expected. 我们可以看到,查询中有两个外连接,结果按预期方式通过。

Now, how about those other types of joins you ask? 现在,您问的其他类型的连接怎么样? What about Intersections? 交叉路口怎么样?

Well, not all databases support the intersection but pretty much all databases will allow you to create an intersection through a join (or a well structured where statement at the least). 好吧,并非所有数据库都支持intersection但几乎所有数据库都允许您通过连接(或至少是结构良好的where语句)创建交集。

An Intersection is a type of join somewhat similar to a union as described above - but the difference is that it only returns rows of data that are identical (and I do mean identical) between the various individual queries joined by the union. 交集是一种类似于如上所述的union的连接 - 但不同之处在于它只返回由union连接的各个单独查询之间相同(并且我的意思相同)的数据行。 Only rows that are identical in every regard will be returned. 只返回每个方面相同的行。

A simple example would be as such: 一个简单的例子是这样的:

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

While a normal union query would return all the rows of the table (the first query returning anything over ID>2 and the second anything having ID<4 ) which would result in a full set, an intersect query would only return the row matching id=3 as it meets both criteria. 正常的union查询将返回表的所有行(第一个查询返回ID>2的任何内容,第二个查询ID<4任何内容)将导致完整集,而交叉查询只返回行匹配的id=3因为它符合两个标准。

Now, if your database doesn't support an intersect query, the above can be easily accomlished with the following query: 现在,如果您的数据库不支持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)

If you wish to perform an intersection across two different tables using a database that doesn't inherently support an intersection query, you will need to create a join on every column of the tables. 如果希望使用不本身支持交集查询的数据库跨两个不同的表执行交集,则需要在表的每一列上创建连接。


#3楼

Part 2 - Subqueries 第2部分 - 子查询

Okay, now the boss has burst in again - I want a list of all of our cars with the brand and a total of how many of that brand we have! 好的,现在老板再次爆发了 - 我想要一份包含该品牌的所有汽车清单以及我们拥有的这个品牌的总数!

This is a great opportunity to use the next trick in our bag of SQL goodies - the subquery. 这是一个很好的机会,可以在我们的SQL好东西中使用下一个技巧 - 子查询。 If you are unfamiliar with the term, a subquery is a query that runs inside another query. 如果您不熟悉该术语,则子查询是在另一个查询中运行的查询。 There are many different ways to use them. 有许多不同的方法可以使用它们。

For our request, lets first put a simple query together that will list each car and the brand: 对于我们的请求,让我们首先将一个简单的查询放在一起,列出每辆汽车和品牌:

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

Now, if we wanted to simply get a count of cars sorted by brand, we could of course write this: 现在,如果我们想简单地按品牌排序的汽车数量,我们当然可以这样写:

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

So, we should be able to simply add in the count function to our original query right? 那么,我们应该能够简单地将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)

Sadly, no, we can't do that. 可悲的是,不,我们做不到。 The reason is that when we add in the car ID (column a.ID) we have to add it into the group by - so now, when the count function works, there is only one ID matched per ID. 原因是当我们添加汽车ID(列a.ID)时,我们必须将它添加到组中 - 所以现在,当count函数工作时,每个ID只匹配一个ID。

This is where we can however use a subquery - in fact we can do two completely different types of subquery that will return the same results that we need for this. 这是我们可以使用子查询的地方 - 实际上我们可以执行两种完全不同类型的子查询,这些子查询将返回我们需要的相同结果。 The first is to simply put the subquery in the select clause. 第一种是简单地将子查询放在select子句中。 This means each time we get a row of data, the subquery will run off, get a column of data and then pop it into our row of data. 这意味着每次我们获得一行数据时,子查询将运行,获取一列数据,然后将其弹出到我们的数据行中。

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)

And Bam!, this would do us. 和Bam!,这会对我们有用。 If you noticed though, this sub query will have to run for each and every single row of data we return. 但是,如果您注意到,则必须为我们返回的每一行数据运行此子查询。 Even in this little example, we only have five different Brands of car, but the subquery ran eleven times as we have eleven rows of data that we are returning. 即使在这个小例子中,我们只有五个不同的汽车品牌,但是子查询运行了十一次,因为我们有11行数据正在返回。 So, in this case, it doesn't seem like the most efficient way to write code. 因此,在这种情况下,它似乎不是编写代码的最有效方式。

For a different approach, lets run a subquery and pretend it is a table: 对于不同的方法,让我们运行子查询并假装它是一个表:

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)

Okay, so we have the same results (ordered slightly different - it seems the database wanted to return results ordered by the first column we picked this time) - but the same right numbers. 好的,所以我们得到了相同的结果(排序略有不同 - 似乎数据库想要返回我们这次选择的第一列排序的结果) - 但是相同的正确数字。

So, what's the difference between the two - and when should we use each type of subquery? 那么,两者之间有什么区别 - 我们何时应该使用每种类型的子查询? First, lets make sure we understand how that second query works. 首先,让我们确保我们了解第二个查询的工作原理。 We selected two tables in the from clause of our query, and then wrote a query and told the database that it was in fact a table instead - which the database is perfectly happy with. 我们在查询的from子句中选择了两个表,然后编写了一个查询并告诉数据库它实际上是一个表 - 而数据库非常满​​意。 There can be some benefits to using this method (as well as some limitations). 使用此方法可能会有一些好处(以及一些限制)。 Foremost is that this subquery ran once . 最重要的是这个子查询运行了一次 If our database contained a large volume of data, there could well be a massive improvement over the first method. 如果我们的数据库包含大量数据,那么第一种方法可能会有很大的改进。 However, as we are using this as a table, we have to bring in extra rows of data - so that they can actually be joined back to our rows of data. 但是,由于我们将其用作表格,因此我们必须引入额外的数据行 - 以便它们实际上可以连接回我们的数据行。 We also have to be sure that there are enough rows of data if we are going to use a simple join like in the query above. 如果我们要在上面的查询中使用简单的连接,我们还必须确保有足够的数据行。 If you recall, the join will only pull back rows that have matching data on both sides of the join. 如果你还记得,连接只会拉回来是对的加入双方匹配的数据行。 If we aren't careful, this could result in valid data not being returned from our cars table if there wasn't a matching row in this subquery. 如果我们不小心,如果此子查询中没有匹配的行,则可能导致无法从我们的cars表返回有效数据。

Now, looking back at the first subquery, there are some limitations as well. 现在,回顾第一个子查询,也存在一些局限性。 because we are pulling data back into a single row, we can ONLY pull back one row of data. 因为我们将数据拉回到一行,所以我们只能撤回一行数据。 Subqueries used in the select clause of a query very often use only an aggregate function such as sum , count , max or another similar aggregate function. 查询的select子句中使用的子查询通常仅使用聚合函数,例如sumcountmax或其他类似的聚合函数。 They don't have to, but that is often how they are written. 他们没有必要 ,但这通常是他们的写作方式。

So, before we move on, lets have a quick look at where else we can use a subquery. 因此,在我们继续之前,让我们快速了解一下我们可以使用子查询的其他位置。 We can use it in the where clause - now, this example is a little contrived as in our database, there are better ways of getting the following data, but seeing as it is only for an example, lets have a look: 我们可以在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)

This returns us a list of brand IDs and Brand names (the second column is only added to show us the brands) that contain the letter o in the name. 这将返回一个品牌ID和品牌名称列表(第二栏仅添加给我们展示品牌),其中包含名称中的字母o

Now, we could use the results of this query in a where clause this: 现在,我们可以在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)

As you can see, even though the subquery was returning the three brand IDs, our cars table only had entries for two of them. 正如您所看到的,即使子查询返回了三个品牌ID,我们的汽车表也只有两个品牌的条目。

In this case, for further detail, the subquery is working as if we wrote the following code: 在这种情况下,为了进一步详细说明,子查询的工作方式就像我们编写了以下代码:

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)

Again, you can see how a subquery vs manual inputs has changed the order of the rows when returning from the database. 同样,您可以看到子查询与手动输入在从数据库返回时如何更改行的顺序。

While we are discussing subqueries, lets see what else we can do with a subquery: 在我们讨论子查询时,让我们看看我们可以用子查询做些什么:

  • You can place a subquery within another subquery, and so on and so on. 您可以将子查询放在另一个子查询中,依此类推。 There is a limit which depends on your database, but short of recursive functions of some insane and maniacal programmer, most folks will never hit that limit. 有一个限制取决于你的数据库,但缺乏一些疯狂和疯狂的程序员的递归功能,大多数人永远不会达到这个限制。
  • You can place a number of subqueries into a single query, a few in the select clause, some in the from clause and a couple more in the where clause - just remember that each one you put in is making your query more complex and likely to take longer to execute. 你可以将一些子查询放入一个查询中,一些放在select子句中,一些放在from子句中,另外一些放在where子句中 - 只要记住你放入的每个子查询都会使你的查询更加复杂并且可能执行时间更长。

If you need to write some efficient code, it can be beneficial to write the query a number of ways and see (either by timing it or by using an explain plan) which is the optimal query to get your results. 如果您需要编写一些有效的代码,那么以多种方式编写查询并查看(通过计时或使用解释计划)这是获得结果的最佳查询可能是有益的。 The first way that works may not always be the best way of doing it. 第一种方法可能并不总是最好的方式。


#4楼

Part 3 - Tricks and Efficient Code 第3部分 - 技巧和有效代码

MySQL in() efficiency MySQL在()效率

I thought I would add some extra bits, for tips and tricks that have come up. 我想我会添加一些额外的位,用于提出的提示和技巧。

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): 我看到的一个问题是,我如何从两个表中获得不匹配的行 ,我看到最常被接受的答案如下所示(基于我们的汽车和品牌表 - 其中Holden列为品牌,但没有出现在汽车表中):

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. 这是一个Stack Overflow问题的链接,询问它,如果你想进入细节,这里有一篇很好的深度文章 。

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 . 啊,另一个老人但是好东西 - 旧的你不能在FROM子句中指定目标表'品牌'进行更新

MySQL will not allow you to run an update... query with a subselect on the same table. MySQL不允许您在同一个表上使用subselect运行update...查询。 Now, you might be thinking, why not just slap it into the where clause right? 现在,你可能在想,为什么不把它打成where子句呢? But what if you want to update only the row with the max() date amoung a bunch of other rows? 但是如果你只想更新一行其他行的max()日期呢? You can't exactly do that in a where clause. 你不能在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

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. 有一个偷偷摸摸的解决方法,令人惊讶的大量用户不知道 - 虽然它确实包括一些你需要注意的hackery。

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

#5楼

You can use the concept of multiple queries in the FROM keyword. 您可以在FROM关键字中使用多个查询的概念。 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. 这是一种非常简单的方法,可以涉及表格和字段。


#6楼

Ok, I found this post very interesting and I would like to share some of my knowledge on creating a query. 好的,我发现这篇文章非常有趣,我想分享一些关于创建查询的知识。 Thanks for this Fluffeh . 谢谢你的Fluffeh Others who may read this and may feel that I'm wrong are 101% free to edit and criticise my answer. 其他可能会阅读此内容并且可能认为我错了的人有101%可以自由编辑和批评我的答案。 ( Honestly, I feel very thankful for correcting my mistake(s). ) 老实说,我非常感谢纠正我的错误。

I'll be posting some of the frequently asked questions in MySQL tag. 我将在MySQL标签中发布一些常见问题。


Trick No. 1 ( rows that matches to multiple conditions ) 特技1号( 符合多个条件的行

Given this schema 鉴于此架构

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

QUESTION

Find all movies that belong to at least both Comedy and Romance categories. 找到属于至少两个 的所有电影 ComedyRomance的类别。

Solution

This question can be very tricky sometimes. 这个问题有时候非常棘手。 It may seem that a query like this will be the answer:- 似乎这样的查询将是答案: -

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

which is definitely very wrong because it produces no result . 这绝对是非常错误的,因为它没有产生任何结果 The explanation of this is that there is only one valid value of CategoryName on each row . 对此的解释是每行只有一个有效的CategoryName值。 For instance, the first condition returns true , the second condition is always false. 例如,第一个条件返回true ,第二个条件总是false。 Thus, by using AND operator, both condition should be true; 因此,通过使用AND运算符,两个条件都应该为真; otherwise, it will be false. 否则,它将是假的。 Another query is like this, 另一个问题是这样的,

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

SQLFiddle Demo SQLFiddle演示

and the result is still incorrect because it matches to record that has at least one match on the categoryName . 并且结果仍然不正确,因为它匹配categoryName 上至少有一个匹配的记录。 The real solution would be by counting the number of record instances per movie . 真正的解决方案 是计算每部电影的记录实例数 The number of instance should match to the total number of the values supplied in the condition. 实例的数量应与条件中提供的值的总数相匹配。

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 Demo (the answer) SQLFiddle演示(答案)

  • SQL of Relational Division 关系部门的SQL

Trick No. 2 ( maximum record for each entry ) 技巧2号( 每个条目的最大记录

Given schema, 给定架构,

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

QUESTION

Find the latest version on each software. 查找每个软件的最新版本。 Display the following columns: SoftwareName , Descriptions , LatestVersion ( from VersionNo column ), DateReleased 显示以下列: SoftwareNameDescriptionsLatestVersion来自VersionNo列 ), DateReleased

Solution

Some SQL developers mistakenly use MAX() aggregate function. 一些SQL开发人员错误地使用MAX()聚合函数。 They tend to create like this, 他们倾向于像这样创造,

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

( most RDBMS generates a syntax error on this because of not specifying some of the non-aggregated columns on the group by clause ) the result produces the correct LatestVersion on each software but obviously the DateReleased are incorrect. 大多数RDBMS在此处生成语法错误,因为未在group by子句中指定一些非聚合列 )结果会在每个软件上生成正确的LatestVersion ,但显然DateReleased不正确。 MySQL doesn't support Window Functions and Common Table Expression yet as some RDBMS do already. MySQL不支持Window FunctionsCommon Table Expression ,因为某些RDBMS已经存在。 The workaround on this problem is to create a subquery which gets the individual maximum versionNo on each software and later on be joined on the other tables. 对这个问题的解决方法是创建一个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 Demo (the answer) SQLFiddle演示(答案)


So that was it. 就是这样。 I'll be posting another soon as I recall any other FAQ on MySQL tag. 我将很快发布另一个,因为我记得有关MySQL标签的任何其他FAQ Thank you for reading this little article. 感谢您阅读这篇小文章。 I hope that you have atleast get even a little knowledge from this. 我希望你至少从中得到一些知识。

UPDATE 1 更新1


Trick No. 3 ( Finding the latest record between two IDs ) 特技3号( 找到两个ID之间的最新记录

Given Schema 给出架构

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

QUESTION

Find the latest conversation between two users. 查找两个用户之间的最新对话。

Solution

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

你可能感兴趣的:(mysql,sql,select)