web-dev-qa-db-ja.com

左外部結合から個別の行を取得する

特定のテーブルの行を検索するSQLを動的に生成するアプリケーションを構築しています(これは、従業員のようなメインドメインクラスです)。

Table1、Table2、Table1Table2Mapの3つのテーブルがあります。 Table1はTable2と多対多の関係にあり、Table1Table2Mapテーブルを通じてマップされます。しかし、Table1は私のメインテーブルなので、関係は実質的に1対多のようです。

私のアプリは、基本的にこれらすべてのテーブルの行を含む結果セットを提供するSQLを生成します。 select句とjoinは変更されませんが、where句はユーザーの操作に基づいて生成されます。いずれの場合も、結果表示のメインテーブルであるため、結果セットにTable1の重複する行は必要ありません。現在、生成されるクエリは次のとおりです。

select distinct Table1.Id as Id, Table1.Name, Table2.Description from Table1
left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id)
left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)

簡単にするため、where句は除外しました。問題は、Table1について明確に述べたにもかかわらず、Table1のTable2に複数の行がある場合です。結果セットはTable2内の一致するすべての行を選択する必要があるため、Table1の行が重複しています。

さらに詳しく説明するために、Id = 1のTable1の行について、Table1Table2Map(1、1)および(1、2)に2つの行があり、ID2が1のTable2の2つの行にTable1をマッピングするとします。この場合は重複行。今度は、クエリがId 1のTable1行を1回だけ返します。これは、Table1の対応するエントリのアクティブな値のようなTable2の行が1つしかないためです(この情報はマッピングテーブルにあります)。 Table1の行が重複しないようにする方法はありますか?.

私が問題を解決しようとしている方法にいくつかの基本的な問題があると思いますが、それが何であるかを見つけることができません。前もって感謝します。

20
Nazgul

試してください:

left outer join (select distinct YOUR_COLUMNS_HERE ...) SUBQUERY_ALIAS on ...

つまり、テーブルに対して直接結合するのではなく、結合する行を制限するサブクエリに対して結合します。

GROUP BYTable1.Idを使用すると、余分な行が削除されます。結合側のメカニズムについて心配する必要はありません。

私は巨大なクエリでこのソリューションを思いつきましたが、このソリューションはクエリ時間にあまり影響しませんでした。

注:私は質問されてから3年後にこの質問に答えていますが、これは私が信じている誰かを助けるかもしれません。

12
kommradHomer

左結合を外部適用に書き換えることができるため、次のように上位1と順序を使用できます。

select Table1.Id as Id, Table1.Name, Table2.Description 
from Table1
outer apply (
   select top 1 *
   from Table1Table2Map
   where (Table1Table2Map.Table1Id = Table1.Id) and Table1Table2Map.IsActive = 1
   order by somethingCol 
) t1t2
outer apply (
   select top 1 *
   from Table2
   where (Table2.Id = Table1Table2Map.Table2Id)
) t2;

「top」または「order by」のない外部適用は、左外部結合とまったく同じであることに注意してください。これは、もう少し制御を与えるだけです。 (クロス適用は内部結合と同等です)。

Row_number()関数を使用して同様のことを行うこともできます。

 select * from (
      select distinct Table1.Id as Id, Table1.Name, Table2.Description,
        rowNum = row_number() over ( partition by table1.id order by something )
      from Table1
      left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id)
      left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)
 ) x
 where rowNum = 1;

IsActiveフラグが他のテーブルを1行に絞り込むことができる場合、これのほとんどは当てはまりませんが、それらが役立つ場合があります。

7
John Gibb

1つのポイントについて詳しく説明すると、Table1の行ごとにTable2の「アクティブな」行は1つだけであると述べました。その行は、where句に入れることができるほどアクティブとしてマークされていませんか?あるいは、ユーザーが提供する動的条件に、アクティブなものとアクティブでないものを決定する魔法がありますか?.

Table2から何も選択する必要がない場合の解決策は、EXISTS関数を使用できるという点で比較的単純ですが、句にTAble2.Descriptionを配置しているので、そうではないと仮定します。

基本的に、Table2の関連する行と無関係な行を何で区切っていますか?アクティブなフラグですか、それとも動的な状態ですか?最初の列?これが、実際に重複を削除する方法です。

DISTINCT句は使いすぎる傾向があります 。ここではそうではないかもしれませんが、かなり一般的な問題である実際の問題を解決するのではなく、DISTINCTで必要な結果をハッキングしようとしている可能性があるようです。

3
cletus

結合にアクティビティ句を含める必要があります(区別する必要はありません)。

select Table1.Id as Id, Table1.Name, Table2.Description from Table1
left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id) and Table1Table2Map.IsActive = 1
left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)
2
Arvo

Table2の複数の行を表示する場合は、table1の重複データが表示されます。 table2で集約関数(IE Max、Min)を使用したい場合は、これによりtable1から重複行が削除されますが、table2から一部のデータが非表示になります。

追加の説明については、質問の私の回答もご覧ください #70161

1
Nathan Koop