web-dev-qa-db-ja.com

LEFT JOINからINNER JOINへの変換、FROM句は変更できません

私は2つのテーブルを持っています:BASEはマスターであり、フィールドCHILDを介してIDにリンクされています:左外部結合/ 1:Nリレーション。
これらの2つのテーブルには、2つの日付フィールドstart_dateおよびend_dateで定義された履歴データが含まれています。
ただし、BASEの履歴は、CHILDの履歴から独立しています。例えば。:

BASE
1   1/1/2000    1/1/2010
1   1/1/2010    31/12/2012

CHILD
1   1/1/2000    31/12/2005
1   31/12/2005  1/1/2010

目標:
特定の日付について、両方のテーブルでアクティブレコードを選択し、1つの結果レコードに表示します。

問題:
CHILDに一致がない場合(例:2011年1月1日)、「行がありません」と表示されますが、BASE(およびCHILD- fieldsのNull値)のデータが表示されます。

これは私のクエリです:

SELECT
  BASE.*, CHILD.*
FROM
  BASE LEFT OUTER JOIN CHILD ON (BASE.ID=CHILD.ID)
WHERE
  BASE.ID  = '1'
  AND BASE.START_DATE  <=  TO_DATE('1/1/2011', 'DD/MM/YYYY')
  AND BASE.END_DATE  >  TO_DATE('1/1/2011', 'DD/MM/YYYY')
  AND CHILD.START_DATE  <=  TO_DATE('1/1/2011', 'DD/MM/YYYY')
  AND CHILD.END_DATE  >  TO_DATE('1/1/2011', 'DD/MM/YYYY')

CHILD-WHERE句の列により、LEFT JOININNER JOINに変換されることはわかっています。私はこのフォーラムで見つけたIS NULL- trickも試しましたが、それでもまだ苦労しています。

重要な注意:
技術的なセットアップのため、FROM句を変更することはできないため、WHERE句を使用して解決策を見つける必要があります…

手伝ってくれますか?

2
NDT

中途半端にエレガントな解決策を見つける前に、それは私にかなりの頭痛の種を引き起こしました:

WITH x AS (
    SELECT BASE.id AS b_id
          ,BASE.start_date AS b_start_date
          ,BASE.end_date AS b_end_date
          ,CHILD.id AS c_id
          ,CHILD.start_date AS c_start_date
          ,CHILD.end_date AS c_end_date
          ,CASE WHEN DATE '2011-1-1' BETWEEN CHILD.start_date AND CHILD.end_date
                                     OR CHILD.id IS NULL THEN 0 ELSE 1 END AS c_hit
    FROM   BASE LEFT OUTER JOIN CHILD ON (BASE.ID=CHILD.ID)
    WHERE  BASE.ID = 1
    AND    DATE '2011-1-1' BETWEEN BASE.start_date AND BASE.end_date
    )
SELECT b_id, b_start_date, b_end_date
      ,c_id, c_start_date, c_end_date
FROM   x
WHERE  x.c_hit = 0

UNION ALL
SELECT b_id, b_start_date, b_end_date
      ,NULL, NULL,         NULL
FROM   x
GROUP  BY b_id, b_start_date, b_end_date
HAVING min(c_hit) = 1;

もちろん、より良い解決策は、制限を取り除き、基本クエリのJOINを調整することです。 @a_horseが上記のコメントで詩的に表現したように:

oRMフレームワークの危険

とにかく、目の前の質問を解決するには:
行を選択した後、BASE.idおよびBASE.start_date/BASE.end_date一致、4つの異なるケースがあります。

  1. 子がないため、CHILD.idはNULLです。
  2. 子供が一致します。
  3. 子は一致せず、他の一致する子もありません。
  4. 子供は一致しませんが、他の一致する子供です。

これを1つのクエリレベルで解決するのは困難です。ウィンドウ関数とCASEステートメントとDISTINCTを使用してそれを行うことができます。実際、私は基本的なクエリを実行していましたが、それは途方もないものになります。

CTEを使用すると、構文が大幅に簡素化されます。
CTE:

  • BASEidをフィルターし、日付範囲を指定します。一致しない子を持つ行がまだ含まれています。
  • 同じ列名にエイリアスを割り当てる
  • 子が日付と一致する行にタグを付ける

最後のSELECTでは:

  • 一致する子を持つすべての行を返します
  • 一致する子が存在しない行を1つ返し、子列にはNULLを返します。

これは、それをいじりたい人のための sqlfiddle.comの作業デモ です。

3

編集された提案:

結合条件を変更せずに、条件を満たさない場合は、select句の子フィールドをnullにすることができますか?

Select base.*, 
case 
    when child.start_date <= to_date('1/1/2011','dd/mm/yyyy')
       and child.end_date > to_date('1/1/2011','dd/mm/yyyy')
    then child.field1
    else null
end as c_field1
from (...)
where base.start_date (...) and base.end_date (...) 

(where句にchild.start_dateまたはend_dateはありません)

0
Hellion