【问题标题】:Oracle SQL: Joining at most one associated entityOracle SQL:最多加入一个关联实体
【发布时间】:2011-01-20 17:45:41
【问题描述】:

我有表 BuildingAddress,其中每个 Building 都与 0..n Addresses 相关联。

我想列出 Buildings 和关联的 Address。如果Building 有多个入口,因此有多个Addresses,我不在乎显示哪一个。如果Building 没有已知地址,则地址字段应为null

也就是说,我想要一个左连接之类的东西,它最多可以连接每行一次。

如何在 Oracle SQL 中表达这一点?

PS:我的查询将包括对两个表的相当复杂的限制。因此,我想避免在查询文本中重复这些限制。

【问题讨论】:

  • 这些表是如何相互关联的?在单个 PK/FK 列上,还是涉及更多内容?
  • 从技术上讲,它是一种间接关联: Address n : 1 AddressMaster n : 1 BuildingMaster 1 : Building。这些关联中的每一个都由引用另一个表的主键的单列外键表示。

标签: sql oracle plsql analytics


【解决方案1】:

我会考虑在 SELECT 子句中查询地址,例如:

SELECT b.*
      ,(SELECT a.text
        FROM   addresses a
        WHERE  a.buildingid = b.id
        AND    ROWNUM=1) as atext
FROM   building b;

ROWNUM=1 的意思是“有的话就买一个,不在乎哪个”。

这种方法的优点是它可能会比大多数替代方法执行得更好,只要在addresses.buildingid 上存在合适的索引。一旦为每个查询的建筑物找到一个地址,它将停止寻找更多地址。

这种方法的缺点是,如果您想要地址表中的多个列,则不能 - 尽管您可以将它们连接到一个字符串中。

【讨论】:

    【解决方案2】:

    因为您不在乎显示多个地址中的哪一个:

    甲骨文 9i+:

    WITH summary AS (
          SELECT b.*,
                 a.*,
                 ROW_NUMBER() OVER (PARTITION BY b.building_id) rn
            FROM BUILDINGS b
       LEFT JOIN ADDRESSES a ON a.building_id = b.building_id)
    SELECT s.*
      FROM summary s
     WHERE s.rn = 1
    

    非子查询分解等价物:

    SELECT s.*
      FROM (SELECT b.*,
                   a.*,
                   ROW_NUMBER() OVER (PARTITION BY b.building_id) rn
               FROM BUILDINGS b
          LEFT JOIN ADDRESSES a ON a.building_id = b.building_id) s
     WHERE s.rn = 1
    

    【讨论】:

    【解决方案3】:

    您可以做的是限制您加入的地址。 例如,要求没有具有较低 id 的地址:

    select *
    from building b
    left join addresses a on (a.buildingid = b.id)
    where not exists (select 1 from addresses a2
                      where a2.buildingid = b.id and a2.id < a.id)
    

    在这种情况下,每栋建筑最多可获得 1 个地址。

    【讨论】:

    • 好主意,但将其付诸实践很困难,因为并非所有地址都有资格加入(该表还包含历史数据,我只想加入当前地址)。我必须复制一个地址是当前的条件,它包含它自己的两个子选择(是的,它是一个复杂的模式......)
    【解决方案4】:
    select b.*, max(a.id) as aid 
    from building b 
    left outer join addresses a on (a.buildingid = b.id) 
    group by a.buildingid 
    

    select b.*, maxid
    from building b 
    left outer join 
    (
     select buildingid, max(id) as maxid
     from addresses
     group by buildingid 
    ) a on (a.buildingid = b.id) 
    

    【讨论】:

    • 第一个会不会因为 b.* 没有被聚合而失败?
    【解决方案5】:

    美利通,

    这种方法使用嵌套的内联视图。我已经在大型数据集上证明了这种方法,它的表现非常好。

    理解查询的最佳方式是从最里面的“M”内联视图开始。为了调试和清晰起见,我添加了计数。这标识了每个建筑物的最大(即最近的???)地址 ID:

       select maxa.b_id, max(maxa.a_id) a_id, count(*) c
       from address maxa
       group by maxa.b_id;
    

    下一个“A”内联视图使用上面的“M”内联视图来决定要获取哪个地址,然后加入该地址id以返回一组地址字段:

      select ma.b_id, ma.a_id, ma.addr1, ma.addr2, ma.addr3, m.c
      from address ma, 
         ( select maxa.b_id, max(maxa.a_id) a_id, count(*) c
           from address maxa
           group by maxa.b_id ) m
      where ma.a_id = m.a_id;
    

    上面的“A”内联视图将一组转换后的地址传递给最终查询。 BUILDING 和 ADDRESS 之间的关系是 1 到 0..n,而 BUILDING 和“A”之间的关系是 1 到 0..1,一个基本的外连接:

    select b.b_id, b.b_code, b.b_name, a.*
     from building b, 
        ( select ma.b_id, ma.a_id, ma.addr1, ma.addr2, ma.addr3, m.c
          from address ma, 
             ( select maxa.b_id, max(maxa.a_id) a_id, count(*) c
               from address maxa
               group by maxa.b_id ) m
          where ma.a_id = m.a_id ) a
     where b.b_id = a.b_id (+);
    

    这种方法的主要优点是:

    1. 提供任意数量的地址列。
    2. 确定性,每次运行时返回完全相同的结果。
    3. 不会对您的最终查询造成过度的复杂性,这肯定会比这个更复杂。
    4. “A”内联视图可以很容易地封装在数据库视图中,可以称之为 LATEST_ADDRESS 视图:
    create view latest_address (b_id, a_id, addr1, addr2, addr3, c) as
    select ma.b_id, ma.a_id, ma.addr1, ma.addr2, ma.addr3, m.c
     from address ma, 
        ( select maxa.b_id, max(maxa.a_id) a_id, count(*) c
          from address maxa
          group by maxa.b_id ) m
     where ma.a_id = m.a_id;
    
    select b.b_id, b.b_code, b.b_name, a.*
     from building b, latest_address a
     where b.b_id = a.b_id (+);
    

    享受吧!
    马修

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-10
      • 1970-01-01
      • 1970-01-01
      • 2015-07-19
      相关资源
      最近更新 更多