2016-10-21 8 views
2

2人のテーブルに参加しようとしました.1人の人が複数のカードを持ち、そのうちのいくつかがキャンセルされる可能性があります。例えばoracleが1対多の関係に参加し、1回目の参加日を返します

は:

**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 

は基本的に、私はカードがキャンセルされていても、何のカードに参加する第1回ですべてのアカウントを表示しないようにしたいです。 1枚目のカードがキャンセルされた場合、2枚目のカード参加日を示す必要があります。

期待される結果:支援のための

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

お客様のためにすべてのカードがキャンセルされると、何が表示されるのでしょうか? – hemalp108

+0

最も早い参加を表示 – crchin

答えて

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; 
+0

申し訳ありませんが、私は私の質問を編集しました。私は1つの条件を逃した。最初のカードがキャンセルされた場合、最初のカード参加日ではなく、2回目のカード参加日を示す必要があります。 – crchin

1

これはGROUP BYKEEP(DENSE_RANK FIRST)を使用して、(サブクエリと外側のクエリを必要とせずに)データ上ワンパスで行うことができます。

最初にいくつかのハウスキーピング。

  • 二重引用符で囲まれた名前を使用しない限り、表名と列名にはスペースを入れることはできません。大文字と小文字は区別されません。
  • 日付の列は数値形式であるようですが、これは非常に貧弱な方法です。 20151490(14番目の月の90日目)などの入力がDBに格納されないようにするにはどうすればよいですか?すべての日付を日付として保存するべきです(SHOULD)。しかし、それらを正確にその形式で保存することで、正しい順序比較が可能になります(ただし、これは偶然であり、信頼されるべきではありません)。それはあなたの質問の要点ではないので、私はそのままデータを使用しました。
  • なぜ結合が必要ですか?最初のテーブルにはcust_idが含まれていてはなりません。これは、データベース設計の第2の通常の形式に違反しています。実際に、最初のテーブルにその列がある場合は、2番目のテーブルが必要ない、または結合が必要ではありません。 (cust_idが最初のテーブルにない場合は、参加が必要ですが、タイトルに関わらず、参加することではなく、正しい行を選ぶことが本当に問題なので、脇に置いておきます。
  • 最初のテーブルには、同じアカウントに関連付けられた2つのcust_id,3,4があります(2番目のテーブルと矛盾します)。私はそれが誤植であり、実際には4は3でなければならないと仮定しますが、これは2番目の正規形がなぜ重要かを正確に示しています。あなたはcust_idを最初のテーブルに入れてはいけません。

再フォーマットされた要件の鍵は、条件付き注文です。指定されたアカウントについて、ファイル上のすべてのカードがキャンセルされた場合、またはキャンセルされていないカードがある場合は、最も早いカードを選択してください。join_dateただし、アカウントに両方の種類のカードが混在している場合は、キャンセルされていない最も早いカードを選んでください。 SQLでは、2つの式(2つの式のうち、SECONDがjoin_date)で合成順序付けを行うことができます。第1の基準は「条件付き」部分である。以下のソリューションでは、式CASE when cancel_date = 0 then 0 endを使用します。つまり、キャンセルされていないカードにはフラグ0が設定され、取り消されたカードにはフラグNULL(の式にELSEの部分がない場合のデフォルト)が設定されます。デフォルトでNULLは注文の最後になります(デフォルトではasc)。したがって、すべてのカードがまだ有効な場合、それらはすべてフラグ0を持ち、このフラグによる順序は関係ありません。すべてがキャンセルされた場合、フラグはすべてNULLになります。したがって、このフラグの順序は問題になりません。しかし、有効なものと無効なものがある場合、有効なものが最初に来るので、最も早い日付は有効なカードからのみ選択されます。

then 0(フラグ値0)は無関係です。私はそれを1、または文字列(then 'a')にすることができ、 "条件付き順序付け"は同じ理由で同様に機能します。有効なカードには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 
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 
+0

テーブルを盗む(むしろ、テストデータサブクエリの作成)は絶対にうまくいきます。 {:-) – mathguy

+0

ありがとうございます。ちょうどあなたに信用を与えることなくそれをすることができなかった:) – Balter

関連する問題