LINQ 左外连接多个表与组计数和行串联
LINQ Left Outer Join Multiple Tables with Group Count and Row Concatenation
有人可以帮忙解决以下问题吗?我简化了 table/column 名称等。我到处搜索,但我得到的答案是我想要在下面实现的结果的不完整解决方案。 LINQ 的新手,所以请善待。 :-)
表格
- Parent(Parent身份证,Parent姓名,Parent职业)
- Child (ChildId, ChildName, OtherField, ParentId)
- GrandChild(GrandChildId,GrandChildName,OtherField,ChildId)
Parent
+----------+------------+------------------+
| ParentId | ParentName | ParentOccupation |
+----------+------------+------------------+
| 1 | Mary | Teacher |
| 2 | Anne | Doctor |
| 3 | Michael | Farmer |
| 4 | Elizabeth | Police |
| 5 | Andrew | Fireman |
+----------+------------+------------------+
Child
+---------+-----------+-------------+----------+
| ChildId | ChildName | OtherField | ParentId |
+---------+-----------+-------------+----------+
| 1 | Ashley | [SomeValue] | 1 |
| 2 | Brooke | [SomeValue] | 1 |
| 3 | Ashton | [SomeValue] | 3 |
| 4 | Emma | [SomeValue] | 4 |
+---------+-----------+-------------+----------+
盛大Child
+--------------+----------------+-------------+---------+
| GrandChildId | GrandChildName | OtherField | ChildId |
+--------------+----------------+-------------+---------+
| 1 | Andrew | [SomeValue] | 1 |
| 2 | Isabelle | [SomeValue] | 2 |
| 3 | Lucas | [SomeValue] | 2 |
| 4 | Matthew | [SomeValue] | 4 |
+--------------+----------------+-------------+---------+
预期结果
+----------+------------+------------------+-----------------------+-------------------------+
| ParentId | ParentName | ParentOccupation | NumberOfGrandChildren | NamesOfGrandChildren |
+----------+------------+------------------+-----------------------+-------------------------+
| 1 | Mary | Teacher | 3 | Andrew, Isabelle, Lucas |
| 2 | Anne | Doctor | 0 | |
| 3 | Michael | Farmer | 0 | |
| 4 | Elizabeth | Police | 1 | Matthew |
| 5 | Andrew | Fireman | 0 | |
+----------+------------+------------------+-----------------------+-------------------------+
到目前为止我做了什么
LEFT OUTER JOINS - 获取所有列但没有聚合
var result1 = (from p in Parent
join c in Child on p.ParentId equals c.ParentId into pcj
from pc in pcj.DefaultIfEmpty()
join g in GrandChild on pc.ChildId equals g.ChildId into cgj
from cg in cgj.DefaultIfEmpty()
where [some criteria]
select new
{
ParentId = p.ParentId,
ParentName = p.ParentName,
ChildId = pc.ChildId,
ChildName = pc.ChildName,
GrandChildId = cg.GrandChildId,
GrandChildName = cg.GrandChildName
});
COUNTS - 包含聚合但并非所有 parent 列都在那里。另外 returns 计数为 1,而不是 0。
var result2 = (from p in Parent
join c in Child on p.ParentId equals c.ParentId into pcj
from pc in pcj.DefaultIfEmpty()
join g in GrandChild on pc.ChildId equals g.ChildId into cgj
from cg in cgj.DefaultIfEmpty()
where [some criteria]
group new { p } by new { p.ParentId } into r
select new
{
ParentId = r.Key.Id,
NumberOfGrandChildren = r.Count()
});
CONCATENATE COMMA SEPARATED ROW VALUES(用于孙辈的名字)- 在我解决上面的问题之前还没有尝试过,但请打开解决方案。
如何结合并实现上述结果?任何帮助表示赞赏!提前致谢。
编辑
题主发表的新评论表明Linq查询涉及EF Core。我最初的回答假设这是一个本地查询(Linq to Object)。事实上,它更像是一个解释查询(Linq to Entities)。
有关 Linq to object 和 Linq to entities 之间区别的解释,请参阅 linq to entities vs linq to objects - are they the same?。
既然如此,Robert McKee 的回答就更切题了。
出于好奇,Linqpad 显示此查询:
Parents
.Select(p => new
{
ParentId = p.Id,
ParentName = p.Name,
ParentOccupation = p.Occupation,
GrandChildrenCount = p.Children
.SelectMany(c => c.GrandChildren)
.Count(),
GranchildrenNames = string.Join(", ", p.Children
.SelectMany(c => c.GrandChildren)
.Select(gc => gc.Name))
});
将翻译成以下 SQL 查询:
SELECT "p"."Id", "p"."Name", "p"."Occupation", (
SELECT COUNT(*)
FROM "Children" AS "c"
INNER JOIN "GrandChildren" AS "g" ON "c"."Id" = "g"."ChildId"
WHERE "p"."Id" = "c"."ParentId"), "t"."Name", "t"."Id", "t"."Id0"
FROM "Parents" AS "p"
LEFT JOIN (
SELECT "g0"."Name", "c0"."Id", "g0"."Id" AS "Id0", "c0"."ParentId"
FROM "Children" AS "c0"
INNER JOIN "GrandChildren" AS "g0" ON "c0"."Id" = "g0"."ChildId"
) AS "t" ON "p"."Id" = "t"."ParentId"
ORDER BY "p"."Id", "t"."Id", "t"."Id0"
(使用 Sqlite,以及包含具有导航属性的实体 类 的自定义 EFCore 上下文)
原始答案 - 假设 Linq to object
您可以通过以下方式构建查询。
var Result = Parents
// Stage 1: for each parent, get its Chidren Ids
.Select(p => new
{
Parent = p,
ChildrenIds = Children
.Where(c => c.ParentId == p.Id)
.Select(c => c.Id)
.ToList()
})
// Stage 2: for each parent, get its Grandchildren, by using the childrenIds list constructed before
.Select(p => new
{
p.Parent,
GrandChildren = Grandchildren
.Where(gc => p.ChildrenIds.Contains(gc.ChildId))
.ToList()
})
// Stage 3: for each parent, count the grandchildren, and get their names
.Select(p => new
{
ParentId = p.Parent.Id,
ParentName = p.Parent.Name,
ParentOccupation = p.Parent.Occupation,
NumberOfGrandChildren = p.GrandChildren.Count(),
GranchildrenNames = string.Join(", ", p.GrandChildren.Select(gc => gc.Name))
});
这是一个完整的 LinqPad 脚本,可以随机生成数据,所以你可以试试看:
void Main()
{
var rnd = new Random();
var Parents = Enumerable
.Range(0, 10)
.Select(i => new Parent
{
Id = i,
Name = $"Parent-{i}",
Occupation = $"Occupation{i}"
})
.ToList();
var Children = Enumerable
.Range(0,15)
.Select(i => new Child
{
Id = i,
Name = $"Child{i}",
ParentId = rnd.Next(0, 10)
})
.ToList();
var GrandChildren = Enumerable
.Range(0, 25)
.Select(i => new GrandChildren
{
Id = i,
Name = $"GrandChild{i}",
ChildId = rnd.Next(0, 15)
})
.ToList();
var Result = Parents
// Stage 1: for each parent, get its Chidren Ids
.Select(p => new
{
Parent = p,
ChildrenIds = Children
.Where(c => c.ParentId == p.Id)
.Select(c => c.Id)
.ToList()
})
// Stage 2: for each parent, get its Grandchildren, by using the childrenIds list constructed before
.Select(p => new
{
p.Parent,
GrandChildren = GrandChildren
.Where(gc => p.ChildrenIds.Contains(gc.ChildId))
.ToList()
})
// Stage 3: for each parent, count the grandchildren, and get their names
.Select(p => new
{
ParentId = p.Parent.Id,
ParentName = p.Parent.Name,
ParentOccupation = p.Parent.Occupation,
NumberOfGrandChildren = p.GrandChildren.Count(),
GranchildrenNames = string.Join(", ", p.GrandChildren.Select(gc => gc.Name))
})
.Dump();
}
// You can define other methods, fields, classes and namespaces here
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public string Occupation { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
}
public class GrandChildren
{
public int Id { get; set; }
public string Name { get; set; }
public int ChildId { get; set; }
}
这是一组结果:
// Parents
0 Parent-0 Occupation0
1 Parent-1 Occupation1
2 Parent-2 Occupation2
3 Parent-3 Occupation3
4 Parent-4 Occupation4
5 Parent-5 Occupation5
6 Parent-6 Occupation6
7 Parent-7 Occupation7
8 Parent-8 Occupation8
9 Parent-9 Occupation9
// Children
0 Child0 1
1 Child1 5
2 Child2 8
3 Child3 6
4 Child4 9
5 Child5 3
6 Child6 0
7 Child7 4
8 Child8 9
9 Child9 7
10 Child10 8
11 Child11 2
12 Child12 7
13 Child13 7
14 Child14 8
// GrandChildren
0 GrandChild0 7
1 GrandChild1 11
2 GrandChild2 11
3 GrandChild3 14
4 GrandChild4 6
5 GrandChild5 0
6 GrandChild6 11
7 GrandChild7 6
8 GrandChild8 0
9 GrandChild9 12
10 GrandChild10 9
11 GrandChild11 7
12 GrandChild12 0
13 GrandChild13 3
14 GrandChild14 11
15 GrandChild15 9
16 GrandChild16 2
17 GrandChild17 12
18 GrandChild18 12
19 GrandChild19 12
20 GrandChild20 14
21 GrandChild21 12
22 GrandChild22 11
23 GrandChild23 14
24 GrandChild24 12
// Result
0 Parent-0 Occupation0 2 GrandChild4, GrandChild7
1 Parent-1 Occupation1 3 GrandChild5, GrandChild8, GrandChild12
2 Parent-2 Occupation2 5 GrandChild1, GrandChild2, GrandChild6, GrandChild14, GrandChild22
3 Parent-3 Occupation3 0
4 Parent-4 Occupation4 2 GrandChild0, GrandChild11
5 Parent-5 Occupation5 0
6 Parent-6 Occupation6 1 GrandChild13
7 Parent-7 Occupation7 8 GrandChild9, GrandChild10, GrandChild15, GrandChild17, GrandChild18, GrandChild19, GrandChild21, GrandChild24
8 Parent-8 Occupation8 4 GrandChild3, GrandChild16, GrandChild20, GrandChild23
9 Parent-9 Occupation9 0
假设您使用的是 EF,并且设置了导航属性,那么您的查询将如下所示:
var result = context.Parents
.Select(p => new {
p.ParentId,
p.ParentName,
p.ParentOccupation,
NumberOfGrandChildren = p.Children
.SelectMany(c => c.GrandChildren)
.Count(),
NamesOfGrandChildren = string.Join(", ", p.Children
.SelectMany(c => c.GrandChildren)
.Select(g => g.GrandChildName))
}).ToList();
有人可以帮忙解决以下问题吗?我简化了 table/column 名称等。我到处搜索,但我得到的答案是我想要在下面实现的结果的不完整解决方案。 LINQ 的新手,所以请善待。 :-)
表格
- Parent(Parent身份证,Parent姓名,Parent职业)
- Child (ChildId, ChildName, OtherField, ParentId)
- GrandChild(GrandChildId,GrandChildName,OtherField,ChildId)
Parent
+----------+------------+------------------+
| ParentId | ParentName | ParentOccupation |
+----------+------------+------------------+
| 1 | Mary | Teacher |
| 2 | Anne | Doctor |
| 3 | Michael | Farmer |
| 4 | Elizabeth | Police |
| 5 | Andrew | Fireman |
+----------+------------+------------------+
Child
+---------+-----------+-------------+----------+
| ChildId | ChildName | OtherField | ParentId |
+---------+-----------+-------------+----------+
| 1 | Ashley | [SomeValue] | 1 |
| 2 | Brooke | [SomeValue] | 1 |
| 3 | Ashton | [SomeValue] | 3 |
| 4 | Emma | [SomeValue] | 4 |
+---------+-----------+-------------+----------+
盛大Child
+--------------+----------------+-------------+---------+
| GrandChildId | GrandChildName | OtherField | ChildId |
+--------------+----------------+-------------+---------+
| 1 | Andrew | [SomeValue] | 1 |
| 2 | Isabelle | [SomeValue] | 2 |
| 3 | Lucas | [SomeValue] | 2 |
| 4 | Matthew | [SomeValue] | 4 |
+--------------+----------------+-------------+---------+
预期结果
+----------+------------+------------------+-----------------------+-------------------------+
| ParentId | ParentName | ParentOccupation | NumberOfGrandChildren | NamesOfGrandChildren |
+----------+------------+------------------+-----------------------+-------------------------+
| 1 | Mary | Teacher | 3 | Andrew, Isabelle, Lucas |
| 2 | Anne | Doctor | 0 | |
| 3 | Michael | Farmer | 0 | |
| 4 | Elizabeth | Police | 1 | Matthew |
| 5 | Andrew | Fireman | 0 | |
+----------+------------+------------------+-----------------------+-------------------------+
到目前为止我做了什么
LEFT OUTER JOINS - 获取所有列但没有聚合
var result1 = (from p in Parent
join c in Child on p.ParentId equals c.ParentId into pcj
from pc in pcj.DefaultIfEmpty()
join g in GrandChild on pc.ChildId equals g.ChildId into cgj
from cg in cgj.DefaultIfEmpty()
where [some criteria]
select new
{
ParentId = p.ParentId,
ParentName = p.ParentName,
ChildId = pc.ChildId,
ChildName = pc.ChildName,
GrandChildId = cg.GrandChildId,
GrandChildName = cg.GrandChildName
});
COUNTS - 包含聚合但并非所有 parent 列都在那里。另外 returns 计数为 1,而不是 0。
var result2 = (from p in Parent
join c in Child on p.ParentId equals c.ParentId into pcj
from pc in pcj.DefaultIfEmpty()
join g in GrandChild on pc.ChildId equals g.ChildId into cgj
from cg in cgj.DefaultIfEmpty()
where [some criteria]
group new { p } by new { p.ParentId } into r
select new
{
ParentId = r.Key.Id,
NumberOfGrandChildren = r.Count()
});
CONCATENATE COMMA SEPARATED ROW VALUES(用于孙辈的名字)- 在我解决上面的问题之前还没有尝试过,但请打开解决方案。
如何结合并实现上述结果?任何帮助表示赞赏!提前致谢。
编辑
题主发表的新评论表明Linq查询涉及EF Core。我最初的回答假设这是一个本地查询(Linq to Object)。事实上,它更像是一个解释查询(Linq to Entities)。
有关 Linq to object 和 Linq to entities 之间区别的解释,请参阅 linq to entities vs linq to objects - are they the same?。
既然如此,Robert McKee 的回答就更切题了。
出于好奇,Linqpad 显示此查询:
Parents
.Select(p => new
{
ParentId = p.Id,
ParentName = p.Name,
ParentOccupation = p.Occupation,
GrandChildrenCount = p.Children
.SelectMany(c => c.GrandChildren)
.Count(),
GranchildrenNames = string.Join(", ", p.Children
.SelectMany(c => c.GrandChildren)
.Select(gc => gc.Name))
});
将翻译成以下 SQL 查询:
SELECT "p"."Id", "p"."Name", "p"."Occupation", (
SELECT COUNT(*)
FROM "Children" AS "c"
INNER JOIN "GrandChildren" AS "g" ON "c"."Id" = "g"."ChildId"
WHERE "p"."Id" = "c"."ParentId"), "t"."Name", "t"."Id", "t"."Id0"
FROM "Parents" AS "p"
LEFT JOIN (
SELECT "g0"."Name", "c0"."Id", "g0"."Id" AS "Id0", "c0"."ParentId"
FROM "Children" AS "c0"
INNER JOIN "GrandChildren" AS "g0" ON "c0"."Id" = "g0"."ChildId"
) AS "t" ON "p"."Id" = "t"."ParentId"
ORDER BY "p"."Id", "t"."Id", "t"."Id0"
(使用 Sqlite,以及包含具有导航属性的实体 类 的自定义 EFCore 上下文)
原始答案 - 假设 Linq to object
您可以通过以下方式构建查询。
var Result = Parents
// Stage 1: for each parent, get its Chidren Ids
.Select(p => new
{
Parent = p,
ChildrenIds = Children
.Where(c => c.ParentId == p.Id)
.Select(c => c.Id)
.ToList()
})
// Stage 2: for each parent, get its Grandchildren, by using the childrenIds list constructed before
.Select(p => new
{
p.Parent,
GrandChildren = Grandchildren
.Where(gc => p.ChildrenIds.Contains(gc.ChildId))
.ToList()
})
// Stage 3: for each parent, count the grandchildren, and get their names
.Select(p => new
{
ParentId = p.Parent.Id,
ParentName = p.Parent.Name,
ParentOccupation = p.Parent.Occupation,
NumberOfGrandChildren = p.GrandChildren.Count(),
GranchildrenNames = string.Join(", ", p.GrandChildren.Select(gc => gc.Name))
});
这是一个完整的 LinqPad 脚本,可以随机生成数据,所以你可以试试看:
void Main()
{
var rnd = new Random();
var Parents = Enumerable
.Range(0, 10)
.Select(i => new Parent
{
Id = i,
Name = $"Parent-{i}",
Occupation = $"Occupation{i}"
})
.ToList();
var Children = Enumerable
.Range(0,15)
.Select(i => new Child
{
Id = i,
Name = $"Child{i}",
ParentId = rnd.Next(0, 10)
})
.ToList();
var GrandChildren = Enumerable
.Range(0, 25)
.Select(i => new GrandChildren
{
Id = i,
Name = $"GrandChild{i}",
ChildId = rnd.Next(0, 15)
})
.ToList();
var Result = Parents
// Stage 1: for each parent, get its Chidren Ids
.Select(p => new
{
Parent = p,
ChildrenIds = Children
.Where(c => c.ParentId == p.Id)
.Select(c => c.Id)
.ToList()
})
// Stage 2: for each parent, get its Grandchildren, by using the childrenIds list constructed before
.Select(p => new
{
p.Parent,
GrandChildren = GrandChildren
.Where(gc => p.ChildrenIds.Contains(gc.ChildId))
.ToList()
})
// Stage 3: for each parent, count the grandchildren, and get their names
.Select(p => new
{
ParentId = p.Parent.Id,
ParentName = p.Parent.Name,
ParentOccupation = p.Parent.Occupation,
NumberOfGrandChildren = p.GrandChildren.Count(),
GranchildrenNames = string.Join(", ", p.GrandChildren.Select(gc => gc.Name))
})
.Dump();
}
// You can define other methods, fields, classes and namespaces here
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public string Occupation { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
}
public class GrandChildren
{
public int Id { get; set; }
public string Name { get; set; }
public int ChildId { get; set; }
}
这是一组结果:
// Parents
0 Parent-0 Occupation0
1 Parent-1 Occupation1
2 Parent-2 Occupation2
3 Parent-3 Occupation3
4 Parent-4 Occupation4
5 Parent-5 Occupation5
6 Parent-6 Occupation6
7 Parent-7 Occupation7
8 Parent-8 Occupation8
9 Parent-9 Occupation9
// Children
0 Child0 1
1 Child1 5
2 Child2 8
3 Child3 6
4 Child4 9
5 Child5 3
6 Child6 0
7 Child7 4
8 Child8 9
9 Child9 7
10 Child10 8
11 Child11 2
12 Child12 7
13 Child13 7
14 Child14 8
// GrandChildren
0 GrandChild0 7
1 GrandChild1 11
2 GrandChild2 11
3 GrandChild3 14
4 GrandChild4 6
5 GrandChild5 0
6 GrandChild6 11
7 GrandChild7 6
8 GrandChild8 0
9 GrandChild9 12
10 GrandChild10 9
11 GrandChild11 7
12 GrandChild12 0
13 GrandChild13 3
14 GrandChild14 11
15 GrandChild15 9
16 GrandChild16 2
17 GrandChild17 12
18 GrandChild18 12
19 GrandChild19 12
20 GrandChild20 14
21 GrandChild21 12
22 GrandChild22 11
23 GrandChild23 14
24 GrandChild24 12
// Result
0 Parent-0 Occupation0 2 GrandChild4, GrandChild7
1 Parent-1 Occupation1 3 GrandChild5, GrandChild8, GrandChild12
2 Parent-2 Occupation2 5 GrandChild1, GrandChild2, GrandChild6, GrandChild14, GrandChild22
3 Parent-3 Occupation3 0
4 Parent-4 Occupation4 2 GrandChild0, GrandChild11
5 Parent-5 Occupation5 0
6 Parent-6 Occupation6 1 GrandChild13
7 Parent-7 Occupation7 8 GrandChild9, GrandChild10, GrandChild15, GrandChild17, GrandChild18, GrandChild19, GrandChild21, GrandChild24
8 Parent-8 Occupation8 4 GrandChild3, GrandChild16, GrandChild20, GrandChild23
9 Parent-9 Occupation9 0
假设您使用的是 EF,并且设置了导航属性,那么您的查询将如下所示:
var result = context.Parents
.Select(p => new {
p.ParentId,
p.ParentName,
p.ParentOccupation,
NumberOfGrandChildren = p.Children
.SelectMany(c => c.GrandChildren)
.Count(),
NamesOfGrandChildren = string.Join(", ", p.Children
.SelectMany(c => c.GrandChildren)
.Select(g => g.GrandChildName))
}).ToList();