使用 LINQ 对数据进行分组和聚合
解决的问题
能够根据某些标准聚合数据是很有帮助的。例如,如果我们昨天有一千个订单,那么查询数据库并知道这千个订单是来自一千个还是两千个不同的产品可能会非常有用:
SELECT COUNT(Product) as TOTAL, Product FROM Orders GROUP BY Product
这给了我们如下结果:
TOTAL Product
---------------------
983 Lead to Gold Transmuter
17 Selfie Stick
group by 语句在 LINQ 中为我们提供了同样的功能——通过对对象进行分组,我们可以对集合执行 count、sum、max 和 min 等聚合函数,并从数据中创建有意义的信息。
我们的场景
我们的任务是按员工位置组织来自使用 LINQ 选择和转换数据的员工数据(一组员工)。在我们的员工数据中,每个员工对象都有一个如下所示的组代码:
AA123
在此代码中,前两个字母表示位置代码,后三个数字表示办公室号码。创建任何查询的第一步是优化我们想要返回的字段 - 如果我们的问题质量很差,我们不太可能得到高质量的答案。我们想要返回的字段是:
- 位置 – 所讨论的位置
- 总计 – 每个地点的员工总数
每个结果将有一个 int 总数、一个数字和一个字符串 Location(表示员工所在位置的两个字母的代码)。为了实现这一点,我们将返回一个匿名类型。匿名类型允许我们返回在查询时定义并由编译器生成的类型。
返回所有内容的流畅 LINQ 查询如下所示:
from e in employees select employee
为了返回我们的匿名类型,我们将修改查询使其如下所示:
from e in employees select new {Location, Total};
编译器给出了两个错误:
_The name 'Location' does not exist in the current context_
_The name 'Total' does not exist in the current context_
这完全说得通——我们还没有定义这些元素。为了实现这一点,我们需要将内容分组在一起。为此,我们使用 group by语句:
from e in employees
group e by e.GroupCode.ToUpper().Substring(0, 2) into g
select new { Location, Total };
在这里,我们取 Employee 对象的 GroupCode 属性的前两个字符,并根据该值将集合分组到我们指定为g 的组集中。**但我们仍然遇到相同的两个编译器错误 - 系统还不知道 Location 或 Total 是什么意思。
此时,g的内容如下所示:
AA(GroupCode 以 AA 开头的员工对象列表)
ZZ(GroupCode 以 ZZ 开头的员工对象)
AA和ZZ显然是我们创建的位置字段的值。
e.GroupCode.ToUpper().Substring(0, 2)
列表包含与每个位置关联的员工。我们为分组创建的键(在本例中为组代码的前两个字符)在 g 对象的 Key 属性中可用:
var groups = from e in employees
group e by e.GroupCode.ToUpper().Substring(0, 2) into g
select new { Location = g.Key, Total };
现在我们已经定义了位置的来源,一个编译器错误就消失了。接下来,我们将 Total 定义为组g中与该键关联的对象的数量:
from e in employees
group e by e.GroupCode.ToUpper().Substring(0, 2) into g
select new { Location = g.Key, Total = g.Count() };
此处的 Count() 是一种方法,而不是属性。我们要求获取员工列表的大小,这就是总数。这给出了以下结果:
AA 2
1号
这正是我们想要的。
LINQ 中的 Having By 语句
假设你为你的老板制作了这份漂亮的报告,她说:“啊……你确实给了我我想要的东西。但我真正想要的是一份包含重复项的组的报告。如果他们只有一名员工,我不希望他们包含在结果中。”
如果您熟悉 T-SQL,您已经会说“我们需要一个 HAVING 子句”。HAVING 语句类似于聚合值的 WHERE 子句。在 T-SQL 中,我们会这样说:
SELECT COUNT(Location) as TOTAL, Location FROM Employees GROUP BY Location HAVING COUNT(Location) > 1
因此,您可以在 LINQ 中尝试一下:
var groups =
from e in employees
group e by e.GroupCode.ToUpper().Substring(0, 2) into g
having g.Count() > 1
select new { Location = g.Key, Total = g.Count() };
不行,编译器无法识别此上下文中的术语 _having _,因此会产生许多错误。好消息是,在 T-SQL 中,使用 WHERE 筛选值与使用 HAVING 筛选聚合是分开的。LINQ 中没有这样的分开,只有 WHERE:
var groups =
from e in employees
group e by e.GroupCode.ToUpper().Substring(0, 2) into g
where g.Count() > 1
select new { Location = g.Key, Total = g.Count() };
我们只需将“having”改为“where”即可。此查询将过滤掉仅有一名员工与位置相关的结果,并返回与之前格式相同的单个结果:
AA 2
其他聚合
GroupCode 是一个字符串,所以我们能用它做的事情不多。如果我们使用的是数字,例如 Salary,我们可以执行 SUM 之类的数字聚合:
from e in employees
group e by e.GroupCode.ToUpper().Substring(0, 2) into g
where g.Count() > 1
select new { Location = g.Key, Total = g.Count(), TotalSalary = g.Sum(x => x.Salary)};
这将告诉我们我们向特定地点的所有员工支付了多少工资。我们可以添加其他指标,例如:
from e in employees
group e by e.GroupCode.ToUpper().Substring(0, 2) into g
where g.Count() > 1
select new { Location = g.Key, Total = g.Count(),
TotalSalary = g.Sum(x => x.Salary),
AverageSalary = g.Average(x => x.Salary),
CheapestEmployee = g.Min(x => x.Salary),
MostExpensiveEmployee = g.Max(x => x.Salary)
};
这给出了一个结果 - 请记住,此查询仅返回具有重复项的位置 - 如下所示:
位置 = “AA”
总计 = 2
总工资 = 250
平均工资 = 125
最便宜的员工 = 100
最昂贵的员工 = 150
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~