多对一加入

时间:2011-07-05 08:27:07

标签: sql query-optimization

假设我有以下表格:

create table table_a
(
  id_a,
  name_a,
  primary_key (id_a)
);

create table table_b
(
  id_b,
  id_a is not null, -- (Edit)
  name_b,
  primary_key (id_b),
  foreign_key (id_a) references table_a (id_a)
);

我们可以通过多种方式在这些表上创建连接视图:

create view join_1 as
(
  select 
    b.id_b, 
    b.id_a, 
    b.name_b, 
    a.name_a 
  from table_a a, table_b b
  where a.id_a = b.id_a
);

create view join_2 as
(
  select 
    b.id_b, 
    b.id_a, 
    b.name_b, 
    a.name_a 
  from table_b b left outer join table_a a
  on a.id_a = b.id_a
);

create view join_3 as
(
  select 
    b.id_b, 
    b.id_a, 
    b.name_b, 
    (select a.name_a from table_a a where b.id_b = a.id_a) as name_a 
  from table_b b;
);

我们知道:

(1)table_aid_a必须至少有一个条目(由于表B中的外键)和 (2)table_aid_a的最多一个条目(由于表A中的主键)

然后我们知道table_a中只有一个条目与联接链接。

现在考虑以下SQL:

select id_b, name_b from join_X;

请注意,这不会从table_a中选择任何列,并且因为我们知道在此联接中table_b加入了一个我们真正不应该在执行时table_a以上选择。

那么编写上述连接视图的最佳方法是什么?

我应该只使用标准join_1,并希望优化器根据主键和外键确定不需要访问table_a吗?

或者最好像join_2甚至是join_3那样编写它,这样可以更明确地说明来自table_b的每一行的连接只有一行?

编辑+其他问题

我是否应该在正常联接(例如join_3)中选择子选择(如join_1)?

4 个答案:

答案 0 :(得分:0)

直观地说,我认为join_1执行速度稍慢,因为您假设优化器可以转换连接是错误的,因为您没有声明table_b.id_a列是NOT NULL。实际上,这意味着(1)是错误的。 table_b.id_a可以是NULL。即使你知道它不可能,优化者也不知道。

join_2join_3而言,根据您的数据库,可能会进行优化。找出的最好方法是运行(Oracle语法)

EXPLAIN select id_b, name_b from join_X;

研究执行计划。它会告诉您table_a是否已加入。另一方面,如果您的观点应该是可重用的,那么我会选择普通join并忘记早熟优化。使用适当的统计信息和索引可以获得更好的结果,因为join操作并不总是那么昂贵。但这当然取决于你的统计数据。

答案 1 :(得分:0)

在SQL Server下,

1 + 2实际上是相同的。

我从未使用[3],但看起来很奇怪。我强烈怀疑优化器会使它等同于其他2。

运行所有3个语句并比较生成的执行计划是一个很好的练习。

因此,给定相同的性能,最清楚的阅读得到我的投票 - [2]是支持它的标准,否则[1]。

在您的情况下,如果您不想要A中的任何列,为什么还要在语句中包含Table_A?

如果这是一个简单的过滤器 - 即只包括从那里排在表A中存在,即使我不想从表A的任何的cols表B行,那么所有3个语法都很好,虽然你可能会发现,使用如果某些dbs中的EXISTS更高效:

 SELECT * from Table_B b WHERE EXISTS (SELECT 1 FROM Table_A a WHERE b.id_b = a.id_a)

虽然根据我的经验,这通常与其他任何人的表现相当。

您也会问,然后您会选择子查询而不是其他表达式。这归结为它是否是一个相关的子查询。

基本上 - 相关子查询必须被用于在外部语句的每行执行一次 - 这是上述的真的 - 。在表B中的每一行必须运行对表A的子查询

如果子查询只能运行一次

 SELECT * from Table_B b WHERE b.id_a IN (SELECT a.id_a FROM Table_A a WHERE a.id_a > 10)

然后,子查询通常比更高性能的联接 - 尽管我怀疑某些优化仍然能够发现这两者减少到相同的执行计划

同样,最好的办法是运行两个语句,并比较执行计划。

最后也是最简单的 - 给你FK你可以写:

 SELECT * From Table_B b WHERE b.id_a IS NOT NULL

答案 2 :(得分:0)

为什么你为此目的使用视图?如果您想从表中获取数据,请从表中获取数据。

或者,如果您需要一个视图来转换表中的某些列(例如将NULL合并为零),则只在B表上创建一个视图。如果某个DBA想要实现一个所有选择必须通过视图而不是表格的策略,这也适用: - )

在这两种情况下,您都不必担心多表访问。

答案 3 :(得分:0)

这将取决于平台。

SQL Server既分析了约束(外键,主键等)的逻辑含义,又扩展了内联的VIEW。这意味着'无关紧要' VIEW代码的一部分被优化器废弃。 SQL Server将为所有三种情况提供完全相同的执行计划。 (注意;优化器可以处理的复杂性有限制,但它当然可以处理这个问题。)

然而,并非所有平台都是平等的 - 有些人可能不会以相同的方式分析约束,假设您出于某种原因编码了联接 - 有些人可能预编译VIEW的执行/解释计划

因此,要确定行为,您必须了解特定平台的功能。在绝大多数情况下,优化器都是一个复杂的野兽,因此最好的测试只是尝试并看到它。

修改

在回答您的额外问题时,每个首选的相关子查询是什么?没有简单的答案,因为它取决于您尝试实施的数据和逻辑。

在我过去使用它们的过程中肯定会出现这种情况,既可以简化查询的结构(可维护性),也可以启用特定的逻辑。

如果字段table_b.id_a引用了table_a中的许多条目,则可能只需要最新名称。您可以使用(SELECT TOP 1 name_a FROM table_a WHERE id_a = table_b.id_a ORDER BY id_a DESC)实现该功能。

简而言之,取决于 - 关于查询的逻辑
- 关于数据的结构
- 关于代码的最终布局

我经常发现它不是必需的,但通常我发现这是一个积极的选择。


注意:

根据相关的子查询,它实际上并不总是为每个记录执行一次'例如,SQL Server将所需的逻辑扩展为与查询的其余部分一起执行。重要的是要注意在执行之前处理/编译SQL代码。 SQL只是一种表达基于集合的逻辑的方法,然后使用优化器可用的最优算法将其转换为传统的循环等。

由于优化器的功能或限制,其他RDBMS的执行方式可能不同。使用IN (SELECT blah FROM blah)或使用EXISTS (SELECT * FROM blah)时,某些RDBMS表现良好,但有些表现非常糟糕。这同样适用于相关子查询。 Sub表现得异常出色,有些表现不佳,但大部分表现都非常好。