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

Posted

技术标签:

【中文标题】在多列上连接大量表的策略?【英文标题】:A strategy to join a large number of tables on multiple columns? 【发布时间】:2014-07-10 11:19:34 【问题描述】:

我知道我可以通过编写简单的连接来轻松连接 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)

显然,这既复杂又丑陋。有没有办法在不使用如此复杂的连接的情况下以更简洁的方式获得相同的结果集?

【问题讨论】:

您可以发布查询吗? 您的意思是除了重新设计数据库以不必进行那种类型的连接之外?还是购买合适的大型数据库服务器? 我不认为有一个通用的答案。在我看来,您希望对 OLTP 数据库执行 DSS 查询并期望获得不错的性能。这可能是可能的,但它肯定需要经验和专业知识,而这些经验和专业知识并不总是可以在不同的 RDMS 之间转移。理想情况下,您在 OLTP 数据库之上构建 OLAP 多维数据集并查询它们。如果这不是一个选项,我会考虑(在 Oracle 的情况下)表/索引分区、物化视图、并行查询执行、稳定执行计划等。据我所知,SQLServer 和 Postgress 也支持类似的技术。 @a1ex07 - 感谢亚历克斯的回复。实际上,我想从一个巨大的查询中提取结果并将其保存为不同的文件格式。现在我添加了一些代码来演示我的问题,请重新考虑打开我的问题。如果需要更多信息,请告诉我。 有没有办法在不使用如此复杂的连接的情况下以更简洁的方式获得相同的结果集? 如果数据库正确规范化,那么您创建一个非规范化视图并查询它。但是您必须使用复杂的连接来创建它。所以是和不是。 【参考方案1】:

您可以使用视图和函数。视图使 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>

【讨论】:

视图主要是为了让代码更漂亮,但不会对速度有太大帮助,因为无论如何都必须每次都获取数据。函数看起来也不错,它们隐藏了复杂性,但有时会阻止数据库使用索引,所以我会小心它们。【参考方案2】:

“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 进行优化,或者充其量获得平庸的性能。

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

JOIN上, 开启WHERE 条件, 或用于ORDER BY

可能受益于索引。

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

【讨论】:

【参考方案3】:

我以前也遇到过同样的情况,我的策略是使用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】:

如果它是所有子查询,您可以在每个子查询中执行它,并且当所有匹配数据发生时,只要所有表 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

【讨论】:

【参考方案5】:

创建实体化视图并在夜间刷新它们。或仅在认为必要时刷新它们。例如,您可以有 2 个视图,一个用永远不会更改的旧数据实现,另一个用实际数据实现普通视图。然后是这些之间的结合。因此,对于您需要的任何输出,您都可以拥有更多这样的视图。

如果您的数据库引擎不支持物化视图,只需在夜间对另一个表中的旧数据进行非规范化处理即可。

也检查一下:Refresh a Complex Materialized View

【讨论】:

【参考方案6】:

如果您想知道是否有其他方法可以访问数据。一种方法是对对象概念感兴趣。我在甲骨文上的任何事件。它工作得很好并且简化了开发。 但它需要一种业务对象方法。

从你的例子中我们可以使用两个概念:

参考 继承

谁能简化查询的可读性,有时还可以提高速度。

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;

返回:

文本2 | 44 文本 | 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;

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

希望它能为您服务

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

以上是关于在多列上连接大量表的策略?的主要内容,如果未能解决你的问题,请参考以下文章

BigQuery - 将多列连接成一列以获取大量列

在多列上使用投影 - 连接表

数据框在多列上连接,pyspark中的列有一些条件[重复]

连接池溢出以及大量查询系统表的问题

JPA继承-连接策略

oracle 连接