oracle加入一对多关系,返回第一个加入日期

时间:2016-10-21 09:30:28

标签: sql oracle join one-to-many

我正在尝试加入两张牌,一张牌可以有多张牌,其中一些可能会被取消。

例如:

**Customer Card**
Cust ID | Cust Acct | Card No | Join Date | Cancel Date
1       | 10001     | E100001 | 20150501  | 20160101
1       | 10001     | E100002 | 20151001  | 0
2       | 10002     | E100003 | 20150101  | 20160601
3       | 10003     | E100004 | 20150201  | 0
4       | 10003     | E100005 | 20160101  | 0 

**Customer Account**
Cust ID | Cust Acct 
1       | 10001     
2       | 10002     
3       | 10003 

基本上,即使卡被取消,我也希望显示所有第一张加入卡的帐号。如果第一张卡被取消,则需要显示第二张卡加入日期。

预期结果:

Cust ID | Cust Acct | Card No | Join Date | Cancel Date
1       | 10001     | E100002 | 20151001  | 0
2       | 10002     | E100003 | 20151001  | 20160601
3       | 10003     | E100004 | 20150201  | 0

感谢您的帮助!有什么想法吗?

3 个答案:

答案 0 :(得分:1)

一种方法使用row_number()

select cc.*, ca.CardNo, ca.JoinDate, ca.CancelDate
from customercard cc join
     (select ca.*, 
             row_number() over (partition by custid order by joindate asc) as seqnum
      from customeraccount ca
     ) ca
     on cc.custid = ca.custid and seqnum = 1;

答案 1 :(得分:1)

使用GROUP BYKEEP(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(如果ELSE中没有CASE部分则默认为NULL表达)。默认情况下,asc在排序中排在最后(默认情况下为then 0)。因此,如果所有卡片仍然有效,则它们都将具有标志0并且通过该标志的排序无关紧要。如果所有都被取消,那么所有的标志都是NULL,所以按这个标志排序并不重要。但是如果有些是有效的而有些是取消的,那么有效的将是第一个,因此最早的日期将仅从有效的卡中挑选。

注意then 'a'(标志值为0)无关紧要;我可以将它设为1,甚至是一个字符串(NULL)和"条件排序"因为同样的原因,它会起作用。我将不是NULL的内容附加到有效卡上,将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

<强>输出

months += 1, days-= 1

答案 2 :(得分:0)

试试这个:

编辑:我正在窃取mathguy的“customer_card”表创建。可以想象他的方式也有效,所以这是另一个解决方案:

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
     )
, allresults as(
select
cust_id,
cust_acct,
card_no,
join_date,
cancel_date,
rank() over(partition by cust_acct order by decode(cancel_date, 0, 1, 2), join_date, rownum) DATE_RANK

from customer_card
)

select
*
from allresults
where DATE_RANK = 1