这可以使用GROUP BY 和KEEP(DENSE_RANK FIRST) 一次性遍历数据(不需要子查询和外部查询)。
首先是一些家务。
- 表名和列名中不能有空格(除非您使用双引号,在大多数情况下这是一种不必要且非常糟糕的做法)。
- 您的日期列似乎是数字格式,这是一种非常糟糕的做法。如何防止像 20151490(第 14 个月的第 90 天)这样的输入存储在数据库中?所有日期都应该存储为日期。但是,以这种格式存储它们可以进行正确的顺序比较(尽管这只是偶然的,不应该依赖)。不过,由于这不是您问题的重点,所以我按原样使用数据。
- 为什么需要加入?第一个表不应包含
cust_id - 包含它违反了数据库设计的第二个正常形式。实际上,如果您确实在第一个表中有该列,我认为不需要第二个表或连接。 (如果cust_id 不在第一个表中,那么您确实需要加入,但我将把它放在一边,因为问题实际上是关于选择正确的行,而不是加入 - 尽管有标题)。
- 在第一个表中,您有两个
cust_id,3 和 4,与同一个帐户相关联(也与第二个表相矛盾)。我认为这是一个错字,实际上 4 应该是 3 - 但这正好说明了为什么第二范式如此重要。第一个表中不应包含 cust_id。
重新制定要求的关键是条件排序。如果给定帐户的所有卡都已取消,或者没有取消,则选择最早join_date 的卡。但是,如果一个帐户混合了两种卡,则选择最早未取消的卡。在 SQL 中,这可以通过复合排序来实现(通过两个表达式,其中 SECOND 是join_date)。第一个标准是“条件”部分。在下面的解决方案中,我使用表达式CASE when cancel_date = 0 then 0 end。也就是说,一张未被取消的卡片的标志为 0,而被取消的卡片的标志为NULL(如果CASE 表达式中没有ELSE 部分,则为默认值)。默认情况下,NULL 在排序中排在最后(默认情况下为 ascending)。因此,如果所有卡仍然有效,它们都将具有标志 0,并且该标志的排序无关紧要。如果全部取消,则该标志对所有都为 NULL,因此按此标志排序将无关紧要。但是如果有的是有效的,有的是取消的,那么有效的会在前,所以最早的日期只会从有效的卡片中选择。
注意then 0(标志值为0)无关紧要;我可以将其设为 1,甚至可以设为字符串 (then 'a'),并且“条件排序”的工作原理相同,并且出于相同的原因。我将不是NULL 的内容附加到有效卡上,将NULL 附加到取消的卡上;这才是最重要的。
这也是 Gordon 需要进行的更改才能使他的解决方案发挥作用。但是,在这种情况下,我更喜欢 KEEP(DENSE_RANK FIRST) 方法,尤其是在性能很重要的情况下(当您有大量客户、帐户和信用卡存档时可能会出现这种情况)。
with
customer_card ( cust_id , cust_acct , card_no , join_date , cancel_date ) as (
select 1, 10001, 'E100001', 20150501, 20160101 from dual union all
select 1, 10001, 'E100002', 20151001, 0 from dual union all
select 2, 10002, 'E100003', 20150101, 20160601 from dual union all
select 3, 10003, 'E100004', 20150201, 0 from dual union all
select 3, 10003, 'E100005', 20160101, 0 from dual
)
-- end of test data; actual solution begins HERE
select cust_id, cust_acct,
min(card_no) keep (dense_rank first
order by case when cancel_date = 0 then 0 end, join_date) as card_no,
min(join_date) keep (dense_rank first
order by case when cancel_date = 0 then 0 end, join_date) as join_date,
min(cancel_date) keep (dense_rank first
order by case when cancel_date = 0 then 0 end, join_date) as cancel_date
from customer_card
group by cust_id, cust_acct
order by cust_id, cust_acct -- ORDER BY is optional
;
输出:
CUST_ID CUST_ACCT CARD_NO JOIN_DATE CANCEL_DATE
--------- ---------- ------- --------- -----------
1 10001 E100002 20151001 0
2 10002 E100003 20150101 20160601
3 10003 E100004 20150201 0