ZetCode

F# 查询表达式

最后修改日期:2025 年 5 月 17 日

在本文中,我们将深入探讨 F# 中的查询表达式——这是一项强大的功能,能够以声明式和直观的方式编写查询。

F# 查询表达式提供了一种优雅的方式来从各种数据源检索和转换数据,包括集合、数据库和结构化数据集。它们提供了一种类似 SQL 的语法,可以与 F# 的函数式编程功能无缝集成,使开发人员能够构建高效且富有表现力的查询。通过利用查询表达式,F# 可以在保持代码的可读性和可组合性的同时,实现简洁的数据操作。

基本查询表达式

最简单的查询表达式从源集合中选择数据。F# 提供了 `select`、`where` 和 `sortBy` 等多个运算符来过滤和转换数据。

basic_query.fsx
open System

let vals = [| 1; 2; 3; 4; 5; 6|]

let lst = query {
    for e in vals do
    last
}

Console.WriteLine(lst)

let fst = query {
    for e in vals do
    head
}

Console.WriteLine(fst)

let n = query {
    for e in vals do
    nth 3
}

Console.WriteLine(n)

此示例演示了基本的查询操作。`last` 运算符获取最后一个元素,`head` 获取第一个元素,`nth` 获取特定索引处的元素。查询表达式包含在 `query { }` 块中。

λ dotnet fsi basic_query.fsx
6
1
4

使用 where 过滤

`where` 运算符根据条件过滤元素。它类似于 SQL 中的 WHERE 子句或函数式编程中的 filter 操作。

where_operator.fsx
open System

let vals = [| 1; 2; 3; 4; 5; 6|]
 
let res = query {
   for v in vals do
   where (v <> 3)
   select v
}

for e in res do
    Console.WriteLine(e)

Console.WriteLine(vals.GetType())

此代码从数组中过滤掉值 3。`where` 子句包含条件,`select` 指定要返回的内容。结果是一个我们可以迭代的 IEnumerable。

λ dotnet fsi where_operator.fsx
1
2
4
5
6
Microsoft.FSharp.Core.FSharpOption`1[System.Int32[]]

计数和选择元素

查询表达式可以计算元素并对自定义类型执行更复杂的操作。`count` 运算符返回匹配查询的元素数量。

count_operator.fsx
open System

type User = {
    Name: string
    Occupation: string
}

let users = [
    { Name = "John Doe"; Occupation = "gardener" }
    { Name = "Roger Roe"; Occupation = "driver" }
    { Name = "Thomas Monroe"; Occupation = "trader" }
    { Name = "Gregory Smith"; Occupation = "teacher" }
    { Name = "Lucia Bellington"; Occupation = "teacher" }
]

let n = query {
    for user in users do
    select user
    count
}

Console.WriteLine(n)

let last = query {
    for user in users do
    last
}

Console.WriteLine(last)

Console.WriteLine("teachers:")

let teachers = query {
    for user in users do
    where (user.Occupation = "teacher")
    select user
}

teachers |> Seq.iter Console.WriteLine

此示例处理 User 记录列表。我们计算所有用户,获取最后一个用户,并按职业过滤用户。`where` 子句过滤教师,`select` 返回匹配的用户。

λ dotnet fsi count_operator.fsx
5
{ Name = "Lucia Bellington"; Occupation = "teacher" }
teachers:
{ Name = "Gregory Smith"; Occupation = "teacher" }
{ Name = "Lucia Bellington"; Occupation = "teacher" }

数据排序

`sortBy` 和 `thenBy` 运算符允许按一个或多个字段对数据进行排序。`sortBy` 执行主要排序,而 `thenBy` 添加次要排序标准。

sorting.fsx
open System

type User = {
    FirstName: string
    LastName: string
    Salary: int
}

let users = [
    { FirstName = "Robert"; LastName = "Novak"; Salary = 1770 }
    { FirstName = "John"; LastName = "Doe"; Salary = 1230 }
    { FirstName = "Lucy"; LastName = "Novak"; Salary = 670 }
    { FirstName = "Ben"; LastName = "Walter"; Salary = 2050 }
    { FirstName = "Robin"; LastName = "Brown"; Salary = 2300 }
    { FirstName = "Amy"; LastName = "Doe"; Salary = 1250 }
    { FirstName = "Joe"; LastName = "Draker"; Salary = 1190 }
    { FirstName = "Janet"; LastName = "Doe"; Salary = 980 }
    { FirstName = "Peter"; LastName = "Novak"; Salary = 990 }
    { FirstName = "Albert"; LastName = "Novak"; Salary = 1930 }
]

let sorted = query {
    for user in users do
    sortBy user.LastName
    thenBy user.Salary
    select user
}

sorted |> Seq.iter Console.WriteLine

此代码首先按姓氏对用户进行排序,然后按薪水排序。结果是一个用户序列,按姓氏字母顺序排列,姓氏相同的用户按薪水排序。

λ dotnet fsi sorting.fsx
{ FirstName = "Robin"; LastName = "Brown"; Salary = 2300 }
{ FirstName = "John"; LastName = "Doe"; Salary = 1230 }
{ FirstName = "Amy"; LastName = "Doe"; Salary = 1250 }
{ FirstName = "Janet"; LastName = "Doe"; Salary = 980 }
{ FirstName = "Joe"; LastName = "Draker"; Salary = 1190 }
{ FirstName = "Lucy"; LastName = "Novak"; Salary = 670 }
{ FirstName = "Peter"; LastName = "Novak"; Salary = 990 }
{ FirstName = "Robert"; LastName = "Novak"; Salary = 1770 }
{ FirstName = "Albert"; LastName = "Novak"; Salary = 1930 }
{ FirstName = "Ben"; LastName = "Walter"; Salary = 2050 }

分组和聚合

查询表达式支持数据分组和执行总和、平均值、计数等聚合操作。`groupBy` 运算符按键对元素进行分组,并且可以将聚合函数应用于每个组。

grouping.fsx
open System.Linq

type Revenue =
    { Id: int
      Quarter: string
      Amount: int }

let revenues = [
    { Id = 1; Quarter = "Q1"; Amount = 2340 };
    { Id = 2; Quarter = "Q1"; Amount = 1200 };
    { Id = 3; Quarter = "Q1"; Amount = 980 };
    { Id = 4; Quarter = "Q2"; Amount = 340 };
    { Id = 5; Quarter = "Q2"; Amount = 780 };
    { Id = 6; Quarter = "Q3"; Amount = 2010 };
    { Id = 7; Quarter = "Q3"; Amount = 3370 };
    { Id = 8; Quarter = "Q4"; Amount = 540 }
]

query {
    for revenue in revenues do
        groupBy revenue.Quarter into g
        where (g.Count() = 2)
        select {| Quarter = g.Key
               Total = g.Sum(fun c -> c.Amount) |}
}
|> Seq.iter (fun e -> printfn "%A" e)

此示例按季度对收入进行分组,过滤掉只有 2 个条目的季度,并计算每个符合条件的季度的总金额。结果是一个具有季度和总金额的匿名记录。

λ dotnet fsi grouping.fsx
{ Quarter = "Q1"; Total = 4520 }
{ Quarter = "Q2"; Total = 1120 }
{ Quarter = "Q3"; Total = 5380 }

F# 查询表达式为处理数据提供了一种强大、声明式的方法。它们提供类似 SQL 的语法来过滤、排序、分组和转换数据,同时保留 F# 的类型安全和函数式编程优势。无论是在内存中的集合还是外部数据源,查询表达式都可以使您的数据处理代码更具可读性和可维护性。

作者

我叫 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。