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 导致单行子查询返回多行错误