首页 文章

SQL查询从多个表返回数据

提问于
浏览
397

我想知道以下内容:

  • 如何从我的数据库中的多个表中获取数据?

  • 有哪些方法可以做到这一点?

  • 什么是联盟和联盟?它们如何彼此不同?

  • 我应该何时使用每一个与其他人相比?

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

注意:我正在写这篇文章,因为我希望能够链接到我在PHP队列中经常遇到的众多问题的精心编写的指南,因此我可以在发布答案时链接到此以获取更多详细信息 .

答案涵盖以下内容:

6 回答

  • 420

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

    您可以根据需要使用任意数量的表 . 即使在表子查询中,也可以在必要时使用外连接和并集 .

    这是一种非常简单的方法,可以涉及表格和字段 .

  • 59

    希望这能让你在阅读这些东西时找到表格:

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

    第1部分 - 加入和结合

    这个答案包括:

    • 第1部分

    • 使用内部联接连接两个或多个表(有关其他信息,请参阅wikipedia entry

    • 如何使用联合查询

    • 左右外连接(这个stackOverflow answer非常适合描述连接类型)

    • 相交查询(以及如果数据库不支持它们如何重现它们) - 这是SQL-Server(see info)的函数,并且首先是reason I wrote this whole thing的一部分 .

    • 第2部分

    • 子查询 - 它们是什么,可以在哪里使用以及需要注意什么

    • 笛卡尔加入了AKA - 哦,痛苦!

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

    我还将使用一个小型caryard的概念,它有一个数据库来跟踪它有哪些可用的汽车 . 所有者雇用了你作为他的IT计算机人员,并期望你能够丢掉他所要求的数据 .

    我已经制作了一些将由最终表使用的查找表 . 这将为我们提供一个合理的模型 . 首先,我将针对具有以下结构的示例数据库运行查询 . 我将尝试思考在开始时所犯的常见错误,并解释它们出了什么问题 - 当然还要说明如何纠正错误 .

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

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

    品牌表标识了caryard可能出售的汽车的不同品牌 .

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

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

    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)
    

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

    因此,老板想知道他所拥有的所有跑车的身份证 .

    这是一个简单的两表连接 . 我们有一个表格,用于识别模型和包含可用库存的表格 . 如您所见, cars 表的 model 列中的数据与我们所拥有的 cars 表的 models 列相关 . 现在,我们知道模型表的 1 的ID为 1 ,因此我们可以编写连接 .

    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=1model='Sports' 来识别跑车 . 由于ID已被索引并且主键(并且它恰好是较少键入),因此我们在查询中使用它 .

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

    答对了!老板很高兴 . 当然,作为老板,从不对他要求的东西感到满意,他会查看信息,然后说我也想要颜色 .

    好的,所以我们已经编写了很多查询,但是我们需要使用第三个颜色表 . 现在,我们的主要信息表 cars 存储汽车颜色ID,并将此链接返回到颜色ID列 . 因此,以与原始类似的方式,我们可以加入第三个表:

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

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

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

    是的,那是我们背后的老板 . 现在,更详细地解释其中的一些内容 . 正如您所看到的,我们的语句中的 from 子句链接了我们的主表(我经常使用一个包含信息而不是查找或维度表的表 . 查询可以同样适用于所有切换的表,但没有意义当我们回到这个查询以便在几个月内阅读它时,所以通常最好尝试编写一个很好且易于理解的查询 - 直观地说明,使用精美的缩进以便一切都清晰如果你继续教别人,试着在他们的查询中灌输这些特征 - 特别是如果你要对它们进行故障排除 .

    完全可以以这种方式连接越来越多的表 .

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

    虽然我忘了在 join 语句中包含一个我们可能想要加入多个列的表,但这是一个例子 . 如果 models 表具有品牌特定的模型,因此也有一个名为 brand 的列链接回 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 表,还指定已连接表之间的连接 . 如果这不是't done, the result is called a cartesian join - which is dba speak for bad. A cartesian join is one where rows are returned because the information doesn'告诉数据库如何限制结果,那么查询将返回符合条件的所有行 .

    因此,为了给出一个笛卡尔连接的示例,让我们运行以下查询:

    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)
    

    天哪,这太丑了 . 但是,就数据库而言,它正是所要求的 . 在查询中,我们要求_26261_来自 carsmodel 来自 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 查询对于特定情况可能很好 . 它们是从表格中返回特定结果的好方法,这些表格不容易连接在一起 - 或者就完全不相关的表格而言 . 但是,有一些规则要遵循 .

    • 第一个查询中的列类型必须与下面每个其他查询的列类型相匹配 .

    • 第一个查询中列的名称将用于标识整个结果集 .

    • 每个查询中的列数必须相同 .

    现在,你可能be wondering what the使用 unionunion all 之间存在差异 . union 查询将删除重复项,而 union all 则不会 . 这确实意味着当使用 union 而不是 union all 时会有轻微的性能损失,但结果可能是值得的 - 我不会在此推测这类事情虽然 .

    在这方面,可能值得注意一些补充说明 .

    • 如果我们想要订购结果,我们可以使用 order by ,但您不能再使用别名了 . 在上面的查询中,追加 order by a.ID 会导致错误 - 就结果而言,该列被称为 ID 而不是 a.ID - 即使在两个查询中都使用了相同的别名 .

    • 我们只能有一个 order by 语句,它必须是最后一个语句 .

    对于下一个示例,我在表中添加了一些额外的行 .

    我已将 Holden 添加到品牌表中 . 我还在 cars 中添加了一行, color 的值为 12 - 在colors表中没有引用 .

    好吧,老板又回来了,咆哮着要求 - *我想要计算我们携带的每个品牌及其中的汽车数量!` - 典型的,我们只是讨论一个有趣的部分,老板想要更多的工作 .

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

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

    现在,当我们将它加入我们的汽车表时,我们得到以下结果:

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

    这当然是一个问题 - 我们没有看到我添加的可爱的 Holden 品牌 .

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

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

    现在我们已经有了这个,我们可以添加一个可爱的聚合函数来计算并暂时让老板退缩 .

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

    随之而来的是,老板偷偷摸摸 .

    现在,为了更详细地解释这一点,外连接可以是 leftright 类型 . 左或右定义完全包含哪个表 . left outer join 将包含左侧表格中的所有行,而(您猜对了) right 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语句) .

    交集是一种类似于 union 的连接,如上所述 - 但不同之处在于它只返回由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)
    

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

  • 16

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

    MySQL in()效率

    我想我会添加一些额外的位,用于提出的提示和技巧 .

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

    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)
    

    但是在某些数据库中效率不高 . 这是一个link to a Stack Overflow question询问它,如果你想进入细节,这里是excellent in depth article .

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

    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)
    

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

    啊,另一个老人但是好东西 - 旧的你可以't specify target table '品牌'在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
    

    所以,我们做不到那个呃?好吧,不完全是 . 有一个偷偷摸摸的解决方法,令人惊讶的大量用户不知道 - 虽然它确实包括一些你需要注意的hackery .

    您可以将子查询粘贴到另一个子查询中,这会在两个查询之间留下足够的空隙,以便它可以工作 . 但是,请注意,将查询粘贴到事务中可能是最安全的 - 这将阻止在查询运行时对表进行任何其他更改 .

    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
    
  • 95

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

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


    特技1号(符合多个条件的行)

    鉴于此架构

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

    QUESTION

    查找至少属于 ComedyRomance 类别的所有电影 .

    Solution

    这个问题有时候非常棘手 . 似乎这样的查询将是答案: -

    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 上至少有一个匹配的记录 . real solution 将通过计算每部电影的记录实例数量来计算 . 实例数应与条件中提供的值的总数相匹配 .

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

    SQLFiddle演示(答案)


    特技2号(每个条目的最大记录)

    给定架构,

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

    QUESTION

    在每个软件上查找最新版本 . 显示以下列: SoftwareNameDescriptionsLatestVersion (来自VersionNo列), DateReleased

    Solution

    一些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 标签上的任何其他常见问题解答 . 感谢您阅读这篇小文章 . 我希望你至少从中得到一些知识 .

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

    QUESTION

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

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

  • 5

    第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!,这会对我们有用 . 如果您注意到,此子查询将不得不运行我们返回的每一行数据 . 即使在这个小例子中,我们只有五个不同的汽车品牌,但子查询运行了十一次,因为我们有十一行数据正在返回 . 因此,在这种情况下,它似乎不是编写代码的最有效方式 .

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

    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 子句中使用的子查询通常只使用聚合函数,如 sumcountmax 或其他类似的聚合函数 . 他们没有必要,但这通常是他们的写作方式 .

    因此,在我们继续之前,让我们快速了解一下我们可以使用子查询的其他位置 . 我们可以在 where 子句中使用它 - 现在,这个例子在我们的数据库中有点做作,有更好的方法来获取以下数据,但看到它只是一个例子,让我们来看看:

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

    这将返回一个品牌ID和品牌名称列表(第二列仅添加给我们展示品牌),其中包含名称中的字母 o .

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

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

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

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

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

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

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

    • 您可以将子查询放在另一个子查询中,依此类推 . 有一个限制取决于你的数据库,但缺乏一些疯狂和疯狂程序员的递归功能,大多数人永远不会达到这个限制 .

    • 您可以将多个子查询放入单个查询中, select 子句中的一些子查询, from 子句中的一些子查询以及 where 子句中的一些子查询 - 请记住,您输入的每个子查询都会使查询更加复杂和可能需要更长的时间来执行 .

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

相关问题