MySQL通过使用连接查询来优化联合查询

MySQL通过使用连接查询来优化联合查询,mysql,join,union,Mysql,Join,Union,我有3个表-一个用于用户,一个用于他们的收入支付,一个用于他们的支出支付。我想在一个结果集中显示所有传入和传出的付款。我可以使用多个selects和一个union来实现这一点,但它似乎很麻烦,而且我怀疑由于子查询的缘故,它的速度很慢,而且表非常大(尽管我使用的是索引)。有没有更快的方法来实现这一点?可能使用完全外部联接 以下是模式的简化版本,其中包含一些示例数据: create table users ( id int auto_increment, name varchar(20),

我有3个表-一个用于用户,一个用于他们的收入支付,一个用于他们的支出支付。我想在一个结果集中显示所有传入和传出的付款。我可以使用多个
select
s和一个
union
来实现这一点,但它似乎很麻烦,而且我怀疑由于子查询的缘故,它的速度很慢,而且表非常大(尽管我使用的是索引)。有没有更快的方法来实现这一点?可能使用
完全外部联接

以下是模式的简化版本,其中包含一些示例数据:

create table users (
  id int auto_increment,
  name varchar(20),
  primary key (id)
) engine=InnoDB;
insert into users (name) values ('bob'),('fred');

create table user_incoming_payments (
  user_id int,
  funds_in int
) engine=InnoDB;
insert into user_incoming_payments
values (1,100),(1,101),(1,102),(1,103),
(2,200),(2,201),(2,202),(2,203);

create table user_outgoing_payments (
  user_id int,
  funds_out int
) engine=InnoDB;
insert into user_outgoing_payments
values (1,100),(1,101),(2,200),(2,201);
下面是一个丑陋的查询,它为用户bob生成了我想要的结果:

select * from (
 (select u.name, i.funds_in, 0 as 'funds_out' from users u
 inner join user_incoming_payments i on u.id = i.user_id)
 union
 (select u.name, 0 as 'funds_in', o.funds_out from users u
 inner join user_outgoing_payments o on u.id = o.user_id)
) a where a.name = 'bob'
order by a.funds_in asc, a.funds_out asc;
这里是我能做的与
join
s相同的事情的最接近的地方-但这并不正确,因为我希望此结果集与前面的结果集相同,并且我不确定如何使用
完全外部联接

select *
from users u
right join user_incoming_payments i on u.id = i.user_id
right join user_outgoing_payments o on u.id = o.user_id
where u.name = 'bob';

对于这个模型,我可能会按如下方式编写查询,但我怀疑它会有多大区别

select u.name
     , i.funds_in
     , 0 funds_out 
  from users u
  join user_incoming_payments i 
    on u.id = i.user_id
 where u.name = 'bob'
 union all
select u.name
     , 0 funds_in
     , o.funds_out 
  from users u
  join user_outgoing_payments o 
    on u.id = o.user_id
 where u.name = 'bob'
 order 
    by funds_in asc
     , funds_out asc;
但是,请注意,这里没有PK,这可能会带来问题


如果是我,我会有一个事务表,其中包括一个事务id PK、每个事务的时间戳,以及一个记录值是贷方还是借方的列。

MySQL不支持
完全外部联接。即使它确实支持它,我想你也不会想要它,因为它会引入一个半笛卡尔积。。。来自
传入
的每一行与
传出
中的每一行匹配,创建额外的行

如果
传入
中有四行,而
传出
中有六行,则联接操作生成的集合将包含24行

这看起来更像是一个集合串联操作。也就是说,有两个单独的集合要连接在一起。这不是一个
JOIN
操作。这是一个
联合所有
集合操作

SELECT ... FROM ... 
 UNION ALL
SELECT ... FROM ...
如果您不需要删除重复项(在这种情况下,如果
传入
中有多行的
资金在
中具有相同的值,我认为您不想删除任何行。)

然后使用
UNION ALL
set运算符,该运算符不执行重复行的检查和删除

UNION
操作符删除重复的行。我认为你不想要


派生表不是必需的

MySQL不会将谓词从外部表“推”到内联视图中。这意味着MySQL将为所有用户具体化一个包含所有传入和传出的派生表。外部查询将通过它查找行。在最新版本的MySQL之前,没有在派生表上创建索引

有关更高效查询的示例,请参见草莓的答案

对于这个小示例集,索引不会产生任何影响。但是,对于大型集合,您需要添加适当的覆盖索引

同样,对于这样的查询,我倾向于包含一个鉴别器列,告诉我哪个查询返回了一行

 (
   SELECT 'i' AS src
        , ...
     FROM ...
 )
   UNION ALL
 (
   SELECT 'o' AS src
        , ...
     FROM ...
 )
   ORDER BY ...

MySQL中没有完全的外部连接——尽管很明显,您可以用各种方式模拟它。但是,如果该查询生成所需的结果,那么完整的外部联接就不是所需的。你的查询很好(虽然我不确定超级查询是否有必要)@Ivan:你看不到问题中已有的样本数据和结果吗?如果没有,则说明您阅读得不够透彻。最好在子查询中放置过滤器
where u.name='bob'
。否则,它将生成一个中间表,所有用户都加入到他们的付款中,然后它将必须找到其中的所有BOB。但除此之外,您的联合方法是正确的方法。我同意Barmar的观点。MySQL不会将外部查询上的谓词
name='bob'
推送到内联视图中。MySQL将具体化一个派生表,其中包含
bob
fred
的行。在最新版本的MySQL之前,派生表上没有索引。而且不需要派生表。草莓是正确的(除了查询应该使用
UNION ALL
而不是
UNION
),除非我们缺少的规范中有一部分说“删除重复行”。请注意,
UNION
将删除重复行,这在这种情况下可能不可取。我们看不到任何保证,即在
用户\u收入\u付款
中不会有两行(或更多行)与
中的
资金\u值相同。我们可能希望使用
UNION ALL
set操作符来避免删除重复项。(如果不需要删除重复项,我们倾向于使用
UNION ALL
来提高性能,以避免重复项检查的开销。此外,对于这种类型的查询,我通常包括一个鉴别器列,即每个查询中都有一个额外的列,每个查询返回一个不同的短文本值(在这种情况下,可能是
'i'
'o'
,这让我知道哪个查询返回了一行。@spencer7593已修复(带有警告)他不会得到重复的行,因为他在子查询的不同列中放入
funds\u
funds\u
。@Barmar:但是如果
incoming\u
中有两行具有相同的
user\u id
funds\u in
值。示例数据没有显示任何重复项……但是如果我们插入mor呢将行添加到
传入的\uuu
(1100)、(1100)、(1100)
。我们没有看到任何约束阻止这一点。请注意,
UNION
操作将删除组合集中的所有重复项。行来自哪个集合无关紧要。。。