36

正確に最適に設計されていないデータベースを継承したため、一部のデータを操作する必要があります。私がしなければならない種類のことのより一般的なアナロジーを与えましょう:

Studentテーブル、StudentClass彼が参加したすべてのクラスの記録を保持するテーブル、およびこの生徒を教えたすべての教師を格納するテーブルStudentTeacherがあるとします。はい、私はそれがばかげたデザインであることを知っています、そしてクラステーブルに先生を保存することはより理にかなっています-しかしそれは私たちが取り組んでいるものです。

今度はデータをクリーンアップしたいと思います。生徒に教師がいるがクラスがない場所、またはクラスはあるが教師がいない場所をすべて見つけたいと思います。したがって、SQL:

select *
from StudentClass sc
full outer join StudentTeacher st on st.StudentID = sc.StudentID
where st.id is null or sc.id is null

Linqでそれをどのように行いますか?

4

5 に答える 5

28

私はここに答えがあると思います、それは私が望んでいたほどエレガントではありませんが、それはトリックをするはずです:

var studentIDs = StudentClasses.Select(sc => sc.StudentID)
  .Union(StudentTeachers.Select(st => st.StudentID);
  //.Distinct(); -- Distinct not necessary after Union
var q =
  from id in studentIDs
  join sc in StudentClasses on id equals sc.StudentID into jsc
  from sc in jsc.DefaultIfEmpty()
  join st in StudentTeachers on id equals st.StudentID into jst
  from st in jst.DefaultIfEmpty()
  where st == null ^ sc == null
  select new { sc, st };

これらの2つのステートメントを1つにまとめることもできますが、コードの明確さを犠牲にすることになると思います。

于 2010-01-19T18:02:38.130 に答える
19

拡張方法:

public static IEnumerable<TResult> FullOuterJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector)
                where TInner : class
                where TOuter : class
            {
                var innerLookup = inner.ToLookup(innerKeySelector);
                var outerLookup = outer.ToLookup(outerKeySelector);

                var innerJoinItems = inner
                    .Where(innerItem => !outerLookup.Contains(innerKeySelector(innerItem)))
                    .Select(innerItem => resultSelector(null, innerItem));

                return outer
                    .SelectMany(outerItem =>
                        {
                            var innerItems = innerLookup[outerKeySelector(outerItem)];

                            return innerItems.Any() ? innerItems : new TInner[] { null };
                        }, resultSelector)
                    .Concat(innerJoinItems);
            }

テスト:

[Test]
public void CanDoFullOuterJoin()
{
    var list1 = new[] {"A", "B"};
    var list2 = new[] { "B", "C" };

    list1.FullOuterJoin(list2, x => x, x => x, (x1, x2) => (x1 ?? "") + (x2 ?? ""))
         .ShouldCollectionEqual(new [] { "A", "BB", "C"} );
}
于 2011-01-12T05:12:31.443 に答える
18

与えられた2つのコレクションabに対して、必要な完全な外部結合は次のようになります。

a.Union(b).Except(a.Intersect(b));

aとbが同じタイプでない場合は、2つの個別の左外部結合が必要です。

var studentsWithoutTeachers =
    from sc in studentClasses
    join st in studentTeachers on sc.StudentId equals st.StudentId into g
    from st in g.DefaultIfEmpty()
    where st == null
    select sc;
var teachersWithoutStudents =
    from st in studentTeachers
    join sc in studentClasses on st.StudentId equals sc.StudentId into g
    from sc in g.DefaultIfEmpty()
    where sc == null
    select st;

Concat()を使用した1行のオプションは次のとおりです。

(from l in left
 join r in right on l.Id equals r.Id into g
 from r in g.DefaultIfEmpty()
 where r == null
 select new {l, r})
     .Concat(
     from r in right
     join sc in left on r.Id equals sc.Id into g
     from l in g.DefaultIfEmpty()
     where l == null
     select new {l, r});
于 2010-01-19T01:55:08.383 に答える
1

スタート...

 var q = from sc in StudentClass
            join st in StudentTeachers on sc.StudentID equals st.StudentID into g
            from st in g.DefaultIfEmpty()
            select new {StudentID = sc.StudentID, StudentIDParent = st == null ? "(no StudentTeacher)" : st.StudentID...........};

その他のサンプルについては、 http://www.linqpad.net/も参照してください。

于 2010-01-18T12:12:35.913 に答える
1

Shaulの回答に基づいていますが、少し合理化されています。

var q =
  from id in studentIDs
  join sc in StudentClasses on id equals sc.StudentID into jsc
  join st in StudentTeachers on id equals st.StudentID into jst
  where jst.Any() ^ jsc.Any() //exclusive OR, so one must be empty

  //this will return the group with the student's teachers, and an empty group
  //   for the student's classes - 
  //   or group of classes, and empty group of teachers
  select new { classes = jsc, teachers = jst };

  //or, if you know that the non-empty group will always have only one element:
  select new { class = jsc.DefaultIfEmpty(), teacher = jst.DefaultIfEmpty() };

完全な外部結合の場合、これも機能することに注意してください。句を省略し、2番目ではなく上記whereの最初の句を使用します。select

于 2013-06-30T11:27:43.150 に答える