在多列上连接大量表的策略?

时间:2014-05-21 17:00:06

标签: mysql sql sql-server oracle postgresql

我知道通过编写简单的连接,我可以轻松地加入2-3个小表。但是,如果您有7-8个表,其中包含2000万行,并且连接1-3列,这些连接可能变得非常慢 即使你有正确的指数。而且,查询也变得冗长而丑陋。

是否存在进行此类大型连接的替代策略,最好是数据库不可知?

修改

这是连接的伪代码。请注意,某些表在连接中使用之前可能必须不显示 -

select * from
    (select c1,c2,c3... From t1 where) as s1
inner join 
    (select c1,... From t2 where) as s2
inner join
    (unpivot table to get c1,c2... From t3 where) as s3
inner join 
    (select c1,c2,c3... From t2 where) as s4
on
    (s1.c1 = s2.c1)
and
    (s1.c1 = s3.c1 and s1.c2 = s3.c2)
and
    (s1.c1 = s4.c1 and s2.c2 = s4.c2 and s1.c3 = s4.c3)
显然,这是复杂而丑陋的。有没有办法在不使用这种复杂连接的情况下以更整洁的方式获得相同的结果集?

7 个答案:

答案 0 :(得分:3)

“7-8桌”听起来并不令人担忧。现代RDBMS可以处理很多。 您的伪代码查询可以从根本上简化为以下形式:

SELECT a.c1 AS a_c1, a.c2 AS a_c2, ...  -- use column aliases ...
      ,b.c1, b.c2, ...  -- .. If you really have same names more than once
      ,c.c1, c.c2, ...
      ,d.c1, d.c2, ...
FROM   t1 a
JOIN   t2 b  USING (c1)
JOIN  (unpivot table to get c1,c2... From t3 where) c USING (c1,c2)
JOIN   t2 d ON d.c1 = a.c1 AND d.c2 = b.c2 AND d.c3 = d.c3
WHERE <some condition on a>
AND   <more conditions> ..

只要在JOIN的表 left 中明确匹配列名,USING语法就会缩短代码。如果任何内容可能不明确,请使用我上次连接条件中演示的显式表单。这是所有标准SQL,但根据this Wikipedia page

  

MS SQL Server和Sybase不支持USING子句。

在大多数RDBMS中,在伪代码中使用所有子查询是没有意义的。查询计划程序找到应用条件和获取列本身的最佳方法。智能查询规划人员还会按照他们认为合适的任何顺序重新排列表格,以达到快速查询计划。

此外,理论上只存在称为“数据库不可知”的东西。没有一个主要的RDBMS完全实现SQL标准,并且它们都有不同的弱点和优势。您为您的RDBMS进行优化或最多获得平庸的表现。

索引策略非常重要。只要我们可以从索引中插入一个充满行指针的手,就可以在SELECT中对2000万行无关紧要。索引策略在很大程度上取决于您的RDBMS品牌。列:

  • JOIN
  • WHERE条件,
  • 或用于ORDER BY

可能从索引中受益。

还有各种类型的索引,专为满足各种要求而设计。 B树,GIN,GiST ,.部分,多列,功能,覆盖。各种运算符类。要优化性能,您只需要了解RDBMS的基础知识和功能。 The excellent PostgreSQL manual on indexes to give you an overview.

答案 1 :(得分:2)

如果索引无法提供足够大的性能提升,我已经看到了三种处理方法。  第一种是使用临时表。数据库执行的连接越多,估计的行获取的数据就会真正减慢查询速度。如果运行连接以及将返回最小行数的where子句,并将中间结果存储在临时表中以允许基数估计器,则可以显着提高计数性能。这个解决方案是唯一一个不创建新数据库对象的解决方案。

第二种解决方案是数据库仓库,或至少是一个额外的非规范化表。在这种情况下,您将创建一个附加表来保存查询的最终结果,或者创建几个执行主要连接并保存中间结果的表。例如,如果您有一个customers表和另外三个包含客户信息的表,您可以创建一个新表,其中包含加入四个表的结果。当您将此查询用于报表时,此解决方案通常可以使用,您可以使用当天生成的新数据每晚加载报表。这个解决方案比第一个解决方案更快,但更难实现并保持结果最新。

第三个解决方案是materilized view / indexed view。此解决方案在很大程度上取决于您使用的db平台。 Oracle和Sql Server都可以创建视图然后将其编入索引,从而为视图提供更高的性能。这可能是因为没有当前记录或更高的数据成本来存储视图结果,但它可以提供帮助。

答案 2 :(得分:2)

创建实体化视图并在整晚刷新它们。或者只在必要时刷新它们。例如,您可以拥有2个视图,一个是使用不会被更改的旧数据实现的,另一个是具有实际数据的普通视图。然后是这些之间的联盟。因此,对于您需要的任何输出,您可以拥有更多这样的视图。

如果您的数据库引擎不支持实体化视图,那么只需在夜间对另一个表中的旧数据进行非规范化。

另请检查:Refresh a Complex Materialized View

答案 3 :(得分:2)

我之前遇到过同样的情况,我的策略是使用WITH子句。

查看更多here

WITH 

-- group some tables into a "temporary" view called MY_TABLE_A
MY_TABLE_A AS 
(
SELECT T1.FIELD1, T2.FIELD2, T3.FIELD3
  FROM T1
  JOIN T2 ON T2.PKEY = T1.FKEY
  JOIN T3 ON T3.PKEY = T2.FKEY
),

-- group some tables into another "temporary" view called MY_TABLE_B
MY_TABLE_B AS 
(
SELECT T4.FIELD1, T5.FIELD2, T6.FIELD3
  FROM T4
  JOIN T5 ON T5.PKEY = T4.FKEY
  JOIN T6 ON T6.PKEY = T5.FKEY
)

-- use those views
SELECT A.FIELD2, B.FIELD3
  FROM MY_TABLE_A A
  JOIN MY_TABLE_B B ON B.FIELD1 = A.FIELD1
 WHERE A.FIELD3 = "X"
   AND B.FIELD2 = "Y"
;

答案 4 :(得分:1)

如果您想知道是否有其他方式来访问数据。一种方法是对对象概念感兴趣。我在Oracle上的任何事件。它运作良好,简化开发。 但它需要一种业务对象方法。

从您的示例中我们可以使用两个概念:

  • 参考
  • 承传

谁可以减轻查询的可读性,有时可以加快速度。

1:参考文献

引用是指向对象的指针。它允许删除表之间的连接,因为它们将被指向。

这是一个简单的例子:

CREATE TYPE S7 AS OBJECT (
    id          NUMBER(11)
    , code      NUMBER(11)
    , label2    VARCHAR2(1024 CHAR) 
);

CREATE TABLE S7_tbl OF S7 (
CONSTRAINT s7_k PRIMARY KEY(id)
);

CREATE TABLE S8 (
    info    VARCHAR2(500 CHAR)
    , info2 NUMBER(5)
    , ref_s7 REF S7 -- creation of the reference
);

我们在两个表中都插入了一些数据:

INSERT INTO S7_tbl VALUES ( S7 (1,1111, 'test'));
INSERT INTO S7_tbl VALUES ( S7 (2,2222, 'test2'));
INSERT INTO S7_tbl VALUES ( S7 (3,3333, 'test3'));
--
INSERT INTO S8 VALUES ('text', 22, (SELECT REF(s) FROM S7_TBL s WHERE s.code = 1111));
INSERT INTO S8 VALUES ('text2', 44, (SELECT REF(s) FROM S7_TBL s WHERE s.code = 1111));
INSERT INTO S8 VALUES ('text3', 11, (SELECT REF(s) FROM S7_TBL s WHERE s.code = 2222));

SELECT:

SELECT s8.info, s8.info2  FROM S8 s8 WHERE s8.ref_s7.code = 1111;

返回:

  • text2 | 44
  • text | 22

这是一种隐式连接

2:内在性

CREATE TYPE S6 AS OBJECT (
    name            VARCHAR2(255 CHAR)
    , date_start    DATE
)
/
DROP TYPE S1;;

CREATE TYPE S1 AS OBJECT(
    data1       NUMBER(11)
    , data2     VARCHAR(255 CHAR)
    , data3     VARCHAR(255 CHAR)
) INSTANTIABLE NOT FINAL
/
CREATE TYPE S2  UNDER S1 (
    dummy1      VARCHAR2(1024 CHAR)
    , dummy2    NUMBER(11)
    , dummy3    NUMBER(11)
    , info_s6      S6
) INSTANTIABLE  FINAL
/
CREATE TABLE S5 
(
    info1           VARCHAR2(128 CHAR)
    , info2         NUMBER(6)
    , object_s2   S2
)

我们只是在表格中插入一行

INSERT INTO S5 
VALUES (
    'info'
    , 2
    ,  S2(
        1       -- fill data1
        , 'xxx' -- fill data2 
        , 'yyy' -- fill data3
        , 'zzz' -- fill dummy1
        , 2     -- fill dummy2  
        , 4     -- fill dummy3        
         , S6(
             'example1'
             ,SYSDATE
          )
     )
);

SELECT:

SELECT 
 s.info1
 , s.objet_s2.data1
 ,s.objet_s2.dummy1
 ,s.objet_s2.info_s6.name
FROM S5 s;

我们可以看到,通过这种方法,我们可以轻松访问相关数据而无需使用。

希望它可以为你服务

答案 5 :(得分:0)

您可以使用视图和功能。视图使SQL代码优雅,易于阅读和撰写。函数可以返回单个值或行集,允许微调底层代码以提高效率。最后,在子查询级别进行过滤而不是在查询级别加入和过滤允许引擎生成较小的数据集以便稍后加入,其中索引不那么重要,因为要加入的数据量很小并且可以在运行中有效地计算。类似下面的查询可以包括高度复杂的查询,涉及数十个表和隐藏在视图和函数中的复杂业务逻辑,并且仍然非常有效。

SELECT a.*, b.*
FROM (SELECT * FROM ComplexView
      WHERE <filter that limits output to a few rows>) a
JOIN (SELECT x, y, z FROM AlreadySignificantlyFilteredView
      WHERE x IN (SELECT f_XValuesForDate(CURRENT_DATE))) b
  ON (a.x = b.x AND a.y = b.y AND a.z <= b.z)
WHERE <condition for filtering even further>

答案 6 :(得分:0)

如果它是所有子查询,你可以在子查询中为每个子查询执行它,并且所有匹配数据发生时,只要所有表c1,c2,c3

,它就应该如下所示
select * from
    (select c1,c2,c3... from t1) as s1
inner join 
    (select c1,... from t2 where c1 = s1.c1) as s2
inner join
    (unpivot table to get c1,c2... from t3 where c2 = s2.c2) as s3
inner join 
    (select c1,c2,c3... from t2 where c3 = s3.c3) as s4