需要一个更好的选择 - 外部连接32次到同一个表

时间:2011-05-12 05:41:00

标签: sql join query-optimization teradata

我有一个讨厌的SQL查询问题,我很乐意帮助一个优雅的解决方案。我试图避免32个左外连接到同一个表。

数据库是Teradata。

我有一张包含1400万条记录和33列的表格。主键(我们称之为Trans_Id)和32个编码字段(让我们称之为encoded_1 ... encoded_32)。像这样:

CREATE SET TABLE BigTable ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
     (
      TRANS_ID VARCHAR(20) CHARACTER SET LATIN NOT CASESPECIFIC,
      ENCODED_1 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      ENCODED_2 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      ENCODED_3 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      ...
      ENCODED_32 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC )
PRIMARY INDEX ( TRANS_ID );

我还有一个包含编码/解码值的表。假设此表中有100条记录。

CREATE SET TABLE LookupTable ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
     (
      UNIQ_PK { just random numbers }
      ENCODED_VAR VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC,
      DECODED_DESC VARCHAR(50) CHARACTER SET LATIN NOT CASESPECIFIC)
PRIMARY INDEX ( UNIQ_PK );

我想避免像这样讨厌的连接(我使用省略号而不是显示所有32个外连接):

SELECT 
TRANS_ID
, a.ENCODED_1
, b1.DECODED_DESC DECODED_DESC_1
, a.ENCODED_2
, b2.DECODED_DESC DECODED_DESC_2
...
, a.ENCODED_31
, b31.DECODED_DESC DECODED_DESC_31
, a.ENCODED_32
, b32.DECODED_DESC DECODED_DESC_32
FROM BigTable a
LEFT OUTER JOIN LookupTable b1 ON a.ENCODED_1 = b1.ENCODED
LEFT OUTER JOIN LookupTable b2 ON a.ENCODED_2 = b1.ENCODED
...
LEFT OUTER JOIN LookupTable b31 ON a.ENCODED_31 = b31.ENCODED
LEFT OUTER JOIN LookupTable b32 ON a.ENCODED_32 = b32.ENCODED

任何帮助将不胜感激。我有一种感觉,外加入14M记录32次并不是有效的方法!

6 个答案:

答案 0 :(得分:3)

您可以创建一个函数,将VARCHAR(10)encoded_var作为参数并返回VARCHAR(50)decoding_desc,然后您的选择将是这样的:

SELECT TRANS_ID,
     ENCODED_1, somefunc(ENCODED_1) AS DECODED_DESC_1,
     ENCODED_2, somefunc(ENCODED_2) AS DECODED_DESC_2,
     etc.

根据您计划一次返回的行数,这是可行的。

答案 1 :(得分:2)

如果encoded_1,encoded_2等都被用作同一个表的查找键,听起来它们都是“相同的想法”。但我首先想到的是,在这种情况下更好的设计是:

big_table (trans_id, var_id, encoded_var)
lookup_table (encoded_var, decoded_desc)

然后查询变为:

select trans_id, var_id, encoded_var, decoded_desc
from big_table
join lookup_table on lookup_table.encoded_var=big_table.encoded_var

我不知道这是否是真正的字段名称,或者您是否只是想忽略不相关的细节。您可能会在此处遗漏相关详细信息。 encoded_1和encoded_2等之间的区别是什么?如果它们是可互换的,则没有理由为它们分别设置字段。实际上,它会导致很多问题。即使存在语义差异,如果它们都使用相同的查找表,它们都必须来自同一个域。

例如,几年前我在一个系统上管理我们组织生产和使用的技术手册。每本手册都有3位经理。 (负责预算和日程安排的行政经理,负责跟踪谁需要副本并确保他们获得副本的股票经理,以及负责实际文本的内容管理员。)但他们都来自相同的人员名单,因为同一个人通常会有不止一个这样的角色,或者可能对不同的手册有不同的角色。所以我们制作了一个带有id,姓名,电子邮件地址等的“人”表,然后在基本手册中我创建了3列,每个管理员类型一列。

这是一个巨大的错误。我应该做的是创建一个包含手动ID,经理类型ID和人员ID的单独表格,然后为3个经理类型而不是3个字段记录3个记录。

为什么呢?有三列,我遇到了你描述的同样的问题,虽然规模较小:我不得不从手册表加入人员表三次。像“Bob Smith负责哪些书籍?”这样的查询需要一个令人惊讶的复杂查询,如

select ... whatever ...
from manual
join person p1 on p1.person_id=manual.admin_id
join person p2 on p2.person_id=manual.stockmanager_id
join person p3 on p3.person_id=manual.contentmanager_id
where p1.name='Bob Smith'
 or p2.name='Bob Smith'
 or p3.name='Bob Smith'

使用单个列就可以简单地

select ... whatever ...
from manual
join manual_manager on manual_manager.manual_id=manual.manual_id
join person on person.person_id=manual_manager.person_id
where person.name='Bob Smith'"

随着所有的重复,有几次程序员偶然只检查了2个字段而不是全部3个字段并不奇怪。有1个字段,这个错误是不可能的。有3个字段,如果我们添加了第4种类型的管理器,我们将不得不添加另一列,然后更改查看这些字段的每个查询。有1个字段,我们可能不会。等

有3个字段,我们需要3个索引,还有其他性能影响。

我怀疑同样的想法适用于你。

如果您的32个字段都是完全可互换的,那么该表只需要一个序列号来创建一个唯一的pk。如果它们之间存在一些差异,那么您可以创建一个代码来区分它们。

答案 2 :(得分:1)

我会将LookUp表上的PI更改为Encoded_Var作为初学者。您必须在每个Encoded_Var列上重新分发大表才能加入LookUp表。为什么每次都不得不重新分配LookUp表。

你的桌面设计是否有理由不接近

CREATE SET TABLE BigTable ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
     (
      TRANS_ID    VARCHAR(20) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
      ENCODED_VAR VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL
UNIQUE PRIMARY INDEX ( TRANS_ID, ENCODED_VAR );

这将在trans_id和encoded_var之间建立更合适的1:M关系。除非有遗漏的相关细节可以解释为什么这不起作用。实际上,如果有必要,您可以将此表构建为关系表,并使用另一个表格,如下所示:

    CREATE SET TABLE BigTable2 ,NO FALLBACK , NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM = DEFAULT
         (
          TRANS_ID    VARCHAR(20) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
          OtherData1  VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL,
          OtherData2  SMALLINT NOT NULL,
          ....,
          OtherDataN  VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL
   UNIQUE PRIMARY INDEX ( TRANS_ID );

希望有所帮助。

答案 3 :(得分:1)

如果您不想重复编写相同的查询,我建议将其放在视图中。

为了表现,我建议如下:

  • 正如Rob Paller建议的那样,将LookupTable上的主要索引更改为ENCODED_VAR。
  • 在LookupTable中,使用DECODED_DESC = null添加新记录,ENCODED_VAR =您将永远不会使用的某个值。更新BigTable以使用此值替换所有null ENCODED_ *。然后,您可以更改查询以使用所有内部联接并获得相同的结果。

答案 4 :(得分:0)

难道你不能这样:

SELECT 
TRANS_ID
, a.ENCODED_1
, CASE a.ENCODED_1 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_1
, a.ENCODED_2
, CASE a.ENCODED_2 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_2
...
, a.ENCODED_31
, CASE a.ENCODED_31 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_31
, a.ENCODED_32
, CASE a.ENCODED_32 WHEN b.ENCODED THEN b.DECODED_DESC END DECODED_DESC_32
FROM BigTable a
 LEFT JOIN LookupTable b ON (
   a.ENCODED_1 = b.ENCODED OR
   a.ENCODED_2 = b.ENCODED OR
   ...
   a.ENCODED_31 = b.ENCODED OR
   a.ENCODED_32 = b.ENCODED
 )

我也可能想要重写加入条件如下:

...ON b.ENCODED IN (a.ENCODED_1, a.ENCODED_2, ... a.ENCODED_31, a.ENCODED_32)

但是我不确定它是否比前一版本慢。实际上,我的猜测是,它确实会变慢,但我仍然会确认这一点。

答案 5 :(得分:0)

我遇到了同样的问题,也是在Teradata上。一位同事使用一个LEFT OUTER JOIN和一些CASE声明告诉我一个优雅的解决方案。

但是,您的示例有点令人困惑,因为您正在加入一个不存在的列(“LookupTable”中的“ENCODED”列,我假设它应该是“ENCODED_VAR”?)。

SELECT     TRANS_ID    
           , a.ENCODED_1    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_1
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_1    
           , a.ENCODED_2    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_2
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_2    
           ...    
           , a.ENCODED_31    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_31
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_31    
           , a.ENCODED_32    
           , MAX(CASE WHEN b.ENCODED_VAR = a.ENCODED_32
                 THEN b.DECODED_DESC
                 ELSE NULL
             END) DECODED_DESC_32    
FROM BigTable a
LEFT OUTER JOIN LookupTable b 
    ON a.ENCODED_1 = b.ENCODED_VAR
    OR a.ENCODED_2 = b.ENCODED_VAR
    ...
    OR a.ENCODED_31 = b.ENCODED_VAR
    OR a.ENCODED_32 = b.ENCODED_VAR
GROUP BY a.TRANS_ID 

这确实依赖于BigTable中的ENCODED_n与LookupTable中的ENCODED_VAR之间存在1:1的关系。

另外,另外,您不应在Teradata表中使用随机数作为PRIMARY INDEX。虽然这会给你很好的表分发,但在进行表查找时它将完全没用。如果为PI使用通常连接的字段,则数据库可以直接转到存储数据的AMP。但是,如果没有这个,DBMS必须每次都进行全表扫描。您可以使用ENCODED_VAR作为主要索引,并且只要分布仍然合理,就可以看到性能大大提高。

我希望这有效。我认为这适用于您的情况。我还没有验证我的代码是否正确,但它与我自己的解决方案非常相似。