Oracle join 子查询的第一行

Posted

技术标签:

【中文标题】Oracle join 子查询的第一行【英文标题】:Oracle join on first row of a subquery 【发布时间】:2012-02-06 05:05:25 【问题描述】:

这可能看起来很简单,但不知何故并非如此。我有一个名为 TBL_A 的历史汇率数据表,如下所示:

|   id   |  rate  |  added_date  |
|--------|--------|--------------|
|  bill  |  7.50  |   1/24/2011  |
|  joe   |  8.50  |   5/3/2011   |
|  ted   |  8.50  |   4/17/2011  |
|  bill  |  9.00  |   9/29/2011  |

在 TBL_B 中,我有几个小时需要加入到 TBL_A 的单行才能获得成本信息:

|   id   |  hours  |  added_date  |
|--------|---------|--------------|
|  bill  |   10    |   2/26/2011  |  
|  ted   |   4     |   7/4/2011   |
|  bill  |   9     |   10/14/2011 |

如您所见,对于 Bill,TBL_A 中有两个费率,但它们的日期不同。要正确获取一段时间内 Bill 的成本,您必须将 TBL_B 的每一行连接到 TBL_A 中适合该日期的一行。

我认为这很容易;因为这不需要非常快的查询,所以我可以为每一行成本信息做一个单独的子查询。但是,连接的子查询显然不能“看到”它们连接的其他表。此查询会在子查询中具有“h”别名的任何内容上引发无效标识符 (ORA-00904):

SELECT h.id, r.rate * h.hours as "COST", h.added_date
     FROM TBL_B h
     JOIN (SELECT * FROM (
                SELECT i.id, i.rate 
                     FROM TBL_A i 
                WHERE i.id = h.id and i.added_date < h.added_date
                ORDER BY i.added_date DESC)
           WHERE rownum = 1) r
      ON h.id = r.id

如果问题只是范围界定,我不知道我采用的方法是否可行。但我在这里要做的只是根据某些标准获得单行,所以我绝对愿意接受其他方法。

编辑:所需的输出是这样的:

|   id   |   cost  |  added_date  |
|--------|---------|--------------|
|  bill  |   75    |   2/26/2011  |  
|  ted   |   34    |   7/4/2011   |
|  bill  |   81    |   10/14/2011 |

请注意,Bill 在表中的两个条目中有两个不同的费率。第一行是 10 * 7.50 = 75,第二行是 9 * 9.00 = 81。

【问题讨论】:

期望的输出是什么?小时的总和? 我想这很模糊,我已经将我的问题编辑得更具体。 【参考方案1】:

尝试使用not exists

select
    b.id,
    a.rate,
    b.hours,
    a.rate*b.hours as "COST", 
    b.added_date,
    a.added_date
from
    tbl_b b
    inner join tbl_a a on
        b.id = a.id
where
    a.added_date < b.added_date
    and not exists (
        select 
            1 
        from 
            tbl_a a2 
        where 
            a2.added_date > a.added_date 
            and a2.added_date < b.added_date
            and a2.id = a.id 
        )

解释为什么会发生这种情况:只有相关的子查询才知道它们正在运行的上下文,因为它们是针对每一行运行的。连接子查询实际上是在连接之前执行的,因此它不知道周围的表。您需要返回所有标识信息以在查询的顶层进行连接,而不是尝试在子查询中进行。

【讨论】:

感谢您的解释,这非常有启发性,并且完全解释了为什么我的代码无法正常工作。但是,这种方法也不起作用。您会看到,在我的帖子中的示例中,Bill 有一行需要以“7.50”的速率加入。使用您发布的代码,它的 row_number() 值为 2,因此将被完全排除。除非有某种方法可以在 WHERE 子句中包含“如果这返回 X 行数然后这个”(我很确定这违反了 SQL 规范),我认为我需要某种方式将值注入子查询本身。跨度> @monitorjbl - 啊,明白了。我用not exists 做到了这一点,这应该可以帮助你。这个谓词是在计算表之后完成的,因此您可以使用它们中的列。它也相当快。我用临时表做到了这一点,它带来了预期的结果就好了。 啊哈!效果很好,非常感谢。我不知道“exists”关键字,每天学习新东西!【参考方案2】:
select id, cost, added_date from (
select
  h.id,
  r.rate * h.hours as "COST",
  h.added_date,
  -- For each record, assign r=1 for 'newest' rate
  row_number() over (partition by h.id, h.added_date order by r.added_date desc) r
from
  tbl_b h,
  tbl_a r 
where
  r.id = h.id and
  -- Date of rate must be entered before
  -- hours billed:
  r.added_date < h.added_date
)
where r = 1
;

【讨论】:

感谢您的回答,雷内。不幸的是,这行不通。这与 Eric 在编辑之前采用的方法相同,我在他的帖子上留下了评论,解释了为什么它不起作用。 据我所知,这与 Eric 所走的路线不同,我仍然相信它会如您所愿。在编辑答案之前,Eric 有一个不同的问题。 Eric 的第一个查询与您的非常相似,只是使用了不同的连接语法。他的原始查询(和您的)的问题在于,这将始终将 TBL_B 中的 any 行与 TBL_A 中的 latest 行连接起来。我正在寻找的是来自 TBL_B 的最新的 date-适当 行。如果 TBL_A 和 TBL_B 中有员工的两个条目,则连接必须反映 TBL_B 中的每一行可能需要连接到 TBL_A 中的不同行(结果可能对每一行使用不同的费率,而不仅仅是最新的)

以上是关于Oracle join 子查询的第一行的主要内容,如果未能解决你的问题,请参考以下文章

Oracle Left Join 导致单行子查询返回多行错误

JOIN 替代 SELECT 子查询

单行子查询在 Oracle 中返回多于一行

oracle的full join关联的表限制条件在on后面与限制在子查询的结果是不一样

Oracle sql使用子查询将多行结果分组为一行

如何加入子查询的第一行?