简化冗余左连接

Posted

技术标签:

【中文标题】简化冗余左连接【英文标题】:Simplify a redundant left join 【发布时间】:2015-12-15 13:57:49 【问题描述】:

使用 Oracle,我希望执行以下查询,但我想知道是否有更“智能”的方式来执行此操作。

Select * from Sales Sales1
left join Sales Sales2 on Sales2.val = Sales1.val
left join Sales Sales3 on Sales3.val = Sales2.val
left join Sales Sales4 on Sales4.val = Sales3.val
left join Sales Sales5 on Sales5.val = Sales4.val
...

这是我的示例数据的样子

customer number | acct | start balance | open date | prev account 
       a            1         100         01-01-15       b-1     
       b            1          80         03-04-14       
       c            2         200         04-11-14       c-1
       c            1         150         06-12-15        
       d            1         600         08-16-15         
       e            3         400         12-19-15       e-2
       e            2         150         10-21-14       e-1
       e            1         100         01-18-13       

结果集如下所示:

   Customer | start | open    | prevStart_01 | prevOpen_01 | prevStart_02 | prevOpen_02    
        a-1 |    100| 01-01-15|            80 |   03-04-14 |             | 
        c-2 |    200| 04-11-14|           150 |   06-11-14 |             | 
        e-3 |    400| 12-19-15|           150 |   10-21-14 |          100|   01-18-13 

如您所见,我需要根据结果继续加入另一条销售记录,并且我需要继续这样做,直到返回一个空的结果集。我当前的场景是运行查询并查看 sales5、sales6、sales7 等中是否有值。

【问题讨论】:

@xQbert 我需要每个连接中的几列。 查看分层查询后可能无济于事。然而,取消数据透视可能。但是,我需要知道相关字段和示例数据的表结构以及预期的结果。这是example unpivot,它可能对您有帮助,也可能对您没有帮助,因为这个问题对预期结果仍然有点模糊。我看到的问题是数据没有标准化,这使得查询需要额外的连接。但是为什么你首先需要连接呢?提供样本数据预期结果。 @xQbert 我添加了一些示例数据和我正在寻找的内容,但我认为您的答案与我正在寻找的内容接近。 啊,那么您是在动态枢轴之后,而不是未枢轴。您想在 prevstart 和 prevOpen 上旋转数据。这是一个示例:***.com/questions/11987067/… @xQbert 这里的 IN 子句会是什么样子?匹配列是客户编号和帐户的串联。 【参考方案1】:

每当您必须自行加入未知次数时,您应该考虑CONNECT BY。您在这里的特殊需求并不是那么简单,但CONNECT BY 仍然是解决方案的关键要素。

在下面的 SQL 中,mockup_data 子因素只是为了给我一些数据。你会使用你的实际表。

这个想法是您在数据中搜索“根”——不是任何其他记录的prev_account 的记录。然后,您从这些和CONNECT BY 开始获取他们以前的所有帐户,数量不限。然后你PIVOT 将它们全部放入列中。

有一件事——Oracle SQL 语句不能有任意(数据驱动的)列数。解析 SQL 时必须知道编号。因此,在您的 PIVOT 子句中,您需要指定您将支持的最大“级别”数,以便 Oracle 知道结果集可以包含多少列。

这是 SQL。

WITH 
mockup_data as (
SELECT   
'a' customer_Number,           1 acct,         100 start_balance,        to_date('01-01-15','MM-DD-YY') open_date,       'b-1' prev_account from dual union all      
SELECT 'b'            ,1,          80,         to_date('03-04-14','MM-DD-YY'), null       from dual union all 
SELECT 'c'            ,2,         200,         to_date('04-11-14','MM-DD-YY'),       'c-1' from dual union all 
SELECT 'c'            ,1,         150,         to_date('06-12-15','MM-DD-YY'),        null from dual union all 
SELECT 'd'            ,1,         600,         to_date('08-16-15','MM-DD-YY'),        null from dual union all 
SELECT 'e'            ,3,         400,         to_date('12-19-15','MM-DD-YY'),       'e-2' from dual union all 
SELECT 'e'            ,2,         150,         to_date('10-21-14','MM-DD-YY'),     'e-1' from dual union all 
SELECT 'e'            ,1,         100,         to_date('01-18-13','MM-DD-YY'),     null  from dual ),
data_with_roots AS
       (SELECT d.*,
               CASE
                 WHEN (SELECT COUNT (*)
                       FROM   mockup_data d2
                       WHERE  d2.prev_account = d.customer_number || '-' || d.acct) = 0 THEN
                   'Y'
                 ELSE
                   'N'
               END
                 is_root
        FROM   mockup_data d),
     hierarchy AS
       (SELECT CONNECT_BY_ROOT (customer_number) customer_number,
               CONNECT_BY_ROOT (acct) acct,
               CONNECT_BY_ROOT (start_balance) start_balance,
               CONNECT_BY_ROOT (open_date) open_date,
               start_balance prev_start_balance,
               open_date prev_open_date,
               LEVEL - 1 lvl
        FROM   data_with_roots d
        CONNECT BY customer_number || '-' || acct = PRIOR prev_account
        START WITH is_root = 'Y'),
     previous_only AS
       (SELECT *
        FROM   hierarchy
        WHERE  lvl >= 1)
SELECT *
FROM   previous_only PIVOT (MAX (prev_start_balance) AS prev_start, MAX (prev_open_date) AS prev_open
                     FOR lvl
                     IN (1 AS "01", 2 AS "02", 3 AS "03", 4 AS "04", 5 AS "05" -- etc... as many levels as you need to support
                                                                              ));

【讨论】:

一个想法——在另一个方向连接可能会更好(即START WITH prev_account IS NULL。这将允许您使用CONNECT_BY_ISLEAF 来确定我在上面所说的“根”。它可能会更有效。你可以玩的东西...... 谢谢。这真的很彻底。是否可以在 pivot 子句中使用 MAX(acct) 来驱动结果列的数量,或者是否必须列出它们? 如果您想在单个查询中执行此操作而不使用动态 SQL,则需要将它们列出。这对于您的客户端应用程序通常也很重要——知道它将获得多少列以及它们被称为什么。如果您真的不喜欢它,您可以跳过PIVOT 并使用LISTAGG 将所有内容集中到一个大列中(由管道或其他任何内容分隔)并让您的客户端应用程序对其进行解析。

以上是关于简化冗余左连接的主要内容,如果未能解决你的问题,请参考以下文章

MySQL左连接多对一行

MySQL 三表链式左连接,按最后一个表过滤

在这种情况下我应该使用左连接吗?

Hive sql中的 各种join(内连接左外连接右外连接满外连接)

Hive sql中的 各种join(内连接左外连接右外连接满外连接)

Hive sql中的 各种join(内连接左外连接右外连接满外连接)