我正在尝试加入两张牌,一张牌可以有多张牌,其中一些可能会被取消。
例如:
**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
感谢您的帮助!有什么想法吗?
答案 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 BY
和KEEP(DENSE_RANK FIRST)
,可以在数据的一次传递中完成此操作(无需子查询和外部查询)。
首先是一些家务。
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