实现 SQL Connect 查询

Firebase SQL Connect 允许您为使用 Cloud SQL 管理的 PostgreSQL 实例创建连接器。这些连接器是查询和变更的组合,用于使用架构中的数据。

入门指南介绍了 PostgreSQL 的影评应用架构。

该指南还介绍了可部署的管理操作和临时管理操作,包括查询。

  • 可部署查询是指您通过定义的 API 端点实现从客户端应用调用的查询。您需要将它们捆绑到部署到服务器的连接器中。SQL Connect 工具会根据您的 API 生成客户端 SDK。已部署的查询不受 IAM 政策保护,因此请务必使用 SQL Connect @auth 指令保护这些查询。
  • 临时管理查询从特权环境运行,以读取数据。您可以在 Firebase 控制台中创建和执行这些查询,也可以使用我们的 SQL Connect VS Code 扩展程序在本地开发环境中创建和执行这些查询。

本指南将深入探讨可部署的查询。

SQL Connect 查询的功能

SQL Connect 允许您以 PostgreSQL 数据库所支持的所有方式执行基本查询。

不过,借助 SQL Connect 对 GraphQL 的扩展,您可以实现高级查询,从而打造更快速、更高效的应用:

  • 使用许多操作返回的键标量来简化对记录的重复操作
  • 在多步变更操作过程中执行查询以查找数据,从而节省代码行数和往返服务器的次数。

使用生成的字段构建查询

您的 SQL Connect 操作将扩展 SQL Connect 根据架构中的类型和类型关系自动生成的一组字段。每当您修改架构时,本地工具都会生成这些字段。

您可以使用生成的字段来实现日益复杂的查询,从检索单个记录或单个表中的多个记录,到检索相关表中的多个记录。

假设您的架构包含 Movie 类型和关联的 Actor 类型。SQL Connect 会生成 movie、movies、actors_on_movies 字段等。

使用
movie字段进行查询

movie 字段表示 Movie 表中的一条记录。

使用此字段可按键查询单个电影。

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}

使用
movies字段进行查询

movies 字段表示 Movie 表中的记录列表。

使用此字段可查询多部电影,例如,指定年份的所有电影。

query GetMovies($myYear: Int!) {
  movies(where: { year: { eq: $myYear } }) { title }
}

使用
actors_on_movies字段进行查询

actors_on_movies 字段表示连接 Actor 和 Movie 表的记录列表。使用此字段可查询与给定影片关联的所有演员。

使用此字段可查询与给定影片关联的所有演员。

  query GetActorsOnMovie($myKey: Movie_Key!) {
    actors_on_movies(where: { movie: { key: { eq: $myKey } } }) {
      actor { name }
    }
  }

查询的基本要素

SQL Connect 查询是具有 SQL Connect 扩展程序的 GraphQL 查询。与常规 GraphQL 查询一样,您可以定义操作名称和 GraphQL 变量列表。

SQL Connect 使用自定义指令(例如 @auth)扩展 GraphQL 查询。

因此,以下查询具有:

  • query 类型定义
  • ListMoviesByGenre 操作(查询)名称
  • 单个查询参数,此处为 String 类型的 $genre 变量
  • 一个指令 @auth。
  • 单个字段 movies。
query ListMoviesByGenre($genre: String!) @auth(level: PUBLIC) {
  movies(where: { genre: { eq: $genre } }) {
    id
    title
  }
}

每个查询参数都需要一个类型声明,例如 String 等内置类型,或 Movie 等自定义架构定义类型。

本指南将介绍越来越复杂的查询的签名。最后,您将学习如何使用强大、简洁的关系表达式来构建可部署的查询。

查询中的键标量

但首先,我们来了解一下关键标量。

SQL Connect 定义了一个特殊的键标量来表示每个表的主键,由 {TableType}_Key 标识。它是主键值的 JSON 对象。

您可以通过大多数自动生成的读取字段返回的响应来检索键标量,也可以通过查询来检索键标量,在查询中,您已检索构建标量键所需的所有字段。

单数自动查询(例如运行示例中的 movie)支持接受键标量的键参数。

您可以将键标量作为字面量传递。不过,您可以定义变量,以将关键标量作为输入传递。

查询

query GetMovie($myKey: Movie_Key!) {
  movie(key: $myKey) { title }
}
    

回答

{
  "data": {
    "movie": {
      "title": "Example Movie Title"
    }
  }
}
    

这些可以在请求 JSON 中提供,如下所示(或其他序列化格式):

{
  # …
  "variables": {
    "myKey": {"id": "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"}
  }
}

得益于自定义标量解析,您还可以使用对象语法(可能包含变量)构建 Movie_Key。如果您出于某种原因想要将各个组件拆分为不同的变量,此功能会非常有用。

编写基本查询

您可以开始编写查询,以从数据库中获取单个记录,或列出表中的记录,并可选择限制和排序结果。

检索个别记录

最简单的查询是按 ID 获取单个记录。您的查询将使用自动生成的 movie 字段。

查询

query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    id
    title
    imageUrl
    genre
  }
}
    

回答

{
  "data": {
    "movie": {
      "id": "some-uuid",
      "title": "Example Movie Title",
      "imageUrl": "https://example.com/movie.jpg",
      "genre": "Action"
    }
  }
}
    

检索表中的所有记录

如需从 Movies 表中检索完整电影列表的部分字段,您的查询将使用自动生成的 movies 字段,您的实现可能如下所示。

查询

query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    genre
  }
}
    

回答

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "imageUrl": "https://example.com/movie.jpg",
        "genre": "Action"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title",
        "imageUrl": "https://example.com/another-movie.jpg",
        "genre": "Comedy"
      }
    ]
  }
}
    

使用 orderBy、limit 和 offset 运算符

当然,列出表中的所有记录,其用处有限。

您可以对结果进行排序和分页。这些参数会被接受,但不会在结果中返回。

在这里,查询会获取评分最高的前 10 部电影的片名。

查询

query MoviesTop10 {
  movies(orderBy: [{ rating: DESC }], limit: 10) {
    # graphql: list the fields from the results to return
    title
  }
}
    

回答

{
  "data": {
    "movies": [
      { "title": "Top Movie 1" },
      { "title": "Top Movie 2" },
      { "title": "Top Movie 3" }
      // ... other 7 movies
    ]
  }
}
    

您可能需要从偏移量中提取行,例如按评分排序的电影 11-20。

查询

query Movies11to20 {
  movies(orderBy: [{ rating: DESC }], limit: 10, offset: 10) {
    # graphql: list the fields from the results to return
    title
  }
}
    

回答

{
  "data": {
    "movies": [
      { "title": "Movie 11" },
      { "title": "Movie 12" },
      { "title": "Movie 13" }
      // ... other 7 movies
    ]
  }
}
    

在查询中使用别名

SQL Connect 支持在查询中使用 GraphQL 别名。使用别名,您可以重命名查询结果中返回的数据。单个 SQL Connect 查询可以在一个高效的服务器请求中应用多个过滤条件或其他查询操作,从而有效地一次发出多个“子查询”。为避免返回的数据集中出现名称冲突,您可以使用别名来区分这些子查询。

以下是一个查询,其中表达式使用了别名 mostPopular 和 leastPopular。

查询

query ReviewPopularitySpread($genre: String) {
  mostPopular: review(
    first: {
      where: {genre: {eq: $genre}},
      orderBy: {popularity: DESC}
    }
  ),
  leastPopular: review(
    last: {
      where: {genre: {eq: $genre}},
      orderBy: {popularity: DESC}
    }
  )
}
    

回答

{
  "data": {
    "mostPopular": [
      { "popularity": 9 }
    ],
    "leastPopular": [
      { "popularity": 1 }
    ]
  }
}
    

使用查询过滤条件

SQL Connect 查询会映射到所有常见的 SQL 过滤和排序操作。

使用 where 和 orderBy 运算符进行过滤

返回表中所有匹配的行(以及嵌套关联)。如果没有与过滤条件匹配的记录,则返回空数组。

查询

query MovieByTopRating($genre: String) {
  mostPopular: movies(
    where: { genre: { eq: $genre } },
    orderBy: { rating: DESC }
  ) {
    # graphql: list the fields from the results to return
    id
    title
    genre
    description
  }
}
    

回答

{
  "data": {
    "mostPopular": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title",
        "genre": "Action",
        "description": "A great movie"
      }
    ]
  }
}
    

通过测试 null 值进行过滤

您可以使用 isNull 运算符测试 null 值。

查询

query ListMoviesWithoutDescription {
  movies(where: { description: { isNull: true }}) {
    id
    title
  }
}
    

回答

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

如需了解更多运算符,请参阅输入对象类型参考指南。

使用值比较进行过滤

您可以使用 lt(小于)和 ge(大于或等于)等运算符在查询中比较值。

查询

query ListMoviesByRating($minRating: Int!, $maxRating: Int!) {
  movies(where: { rating: { ge: $minRating, lt: $maxRating }}) {
    id
    title
  }
}
    

回答

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      },
      {
        "id": "another-uuid",
        "title": "Another Movie Title"
      }
    ]
  }
}
    

使用 includes 和 excludes 运算符过滤数组字段

您可以测试数组字段是否包含指定项。

以下示例说明了 includes 运算符。

SQL Connect 支持 includesAll、excludes、excludesAll 等。如需查看适用于整数、字符串、日期和其他数据类型的此类运算符,请参阅参考文档中的 _ListFilter 标题。

查询

query ListMoviesByTag($tag: String!) {
  movies(where: { tags: { includes: $tag }}) {
    # graphql: list the fields from the results to return
    id
    title
  }
}
    

回答

{
  "data": {
    "movies": [
      {
        "id": "some-uuid",
        "title": "Example Movie Title"
      }
    ]
  }
}
    

使用字符串运算和正则表达式进行过滤

您的查询可以使用典型的字符串搜索和比较操作,包括正则表达式。请注意,为了提高效率,您在此处捆绑了多项操作,并使用别名来消除歧义。

query MoviesTitleSearch($prefix: String, $suffix: String, $contained: String, $regex: String) {
  prefixed: movies(where: {title: {startsWith: $prefix}}) {...}
  suffixed: movies(where: {title: {endsWith: $suffix}}) {...}
  contained: movies(where: {title: {contains: $contained}}) {...}
}

使用 _or、_and、_not 运算符逻辑进行过滤

使用 _or 可实现更复杂的逻辑。SQL Connect 还支持 _and 和 _not 运算符。

查询

query ListMoviesByGenreAndGenre($minRating: Int!, $genre: String) {
  movies(
    where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
  ) {
    # graphql: list the fields from the results to return
    title
  }
}
    

回答

{
  "data": {
    "movies": [
      { "title": "Movie Title 1" },
      { "title": "Movie Title 2" }
    ]
  }
}
    

编写关系查询

SQL Connect 查询可以根据表之间的关系访问数据。您可以使用架构中定义的对象(一对一)或数组(一对多)关系进行嵌套查询,即提取一种类型的数据以及嵌套或相关类型的数据。

此类查询在生成的读取字段中使用魔术 SQL Connect _on_ 和 _via 语法。

请务必查看示例架构。

多对一

现在,我们来看一个查询,以说明 _on_ 语法。

查询

query MyReviews @auth(level: USER) {
  user(key: {id_expr: "auth.uid"}) {
    reviews: reviews_on_user {
      movie { name }
      rating
    }
  }
}
    

回答

{
  "data": {
    "user": {
      "reviews": [
        {
          "movie": { "name": "Movie Title" },
          "rating": 5
        }
      ]
    }
  }
}
    

一对一

您可以使用 _on_ 语法编写一对一查询。

查询

query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    movieMetadatas_on_movie {
      director
    }
  }
}
    

回答

{
  "data": {
    "movie": {
      "movieMetadatas_on_movie": {
        "director": "Some Director"
      }
    }
  }
}
    

多对多

多对多查询使用 _via_ 语法。多对多查询可能会检索指定电影的演员。

查询

query MoviesActors($id: UUID!) @auth(level: USER) {
  movie(id: $id) {
     actors: actors_via_MovieActors {
        name
     }
  }
}
    

回答

{
  "data": {
    "movie": {
      "actors": [
        {
          "name": "Actor Name"
        }
      ]
    }
  }
}
    

不过,我们可以编写更复杂的查询,使用别名根据 role 进行过滤,以检索 mainActors 和 supportingActors 结果中的演员和关联电影。由于这是多对多关系,因此使用了 _via_ 语法。

查询

query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
  movie(id: $movieId) {
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      name
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      name
    }
  }
  actor(id: $actorId) {
    mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      title
    }
    supportingRoles: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      title
    }
  }
}
    

回答

{
  "data": {
    "movie": {
      "mainActors": [
        {
          "name": "Main Actor Name"
        }
      ],
      "supportingActors": [
        {
          "name": "Supporting Actor Name"
        }
      ]
    },
    "actor": {
      "mainRoles": [
        {
          "title": "Main Role Movie Title"
        }
      ],
      "supportingRoles": [
        {
          "title": "Supporting Role Movie Title"
        }
      ]
    }
  }
}
    

聚合查询

什么是汇总?为什么要使用汇总?

借助汇总字段,您可以对结果列表执行计算。借助汇总字段,您可以执行以下操作:

  • 查找评价的平均得分
  • 计算购物车中商品的总费用
  • 查找评分最高或最低的商品
  • 统计商店中的商品数量

聚合在服务器上执行,与在客户端计算相比,这具有许多优势:

  • 更快的应用性能(因为您避免了客户端计算)
  • 降低数据出站流量费用(因为您只需发送汇总结果,而无需发送所有输入)
  • 提高了安全性(因为您可以向客户端授予对汇总数据而非整个数据集的访问权限)

汇总表的示例架构

在本部分中,我们将切换到店面示例架构,该架构非常适合说明如何使用聚合:

  type Product @table {
    name: String!
    manufacturer: String!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

简单汇总

所有字段的 _count

最简单的汇总字段是 _count:它会返回与您的查询匹配的行数。对于类型中的每个字段,SQL Connect 会根据字段类型生成相应的聚合字段。

查询

query CountProducts {
  products {
    _count
  }
}

回答
one

例如,如果您的数据库中有 5 件商品,则结果将如下所示:

{
  "products": [
    {
    "_count": 5
    }
  ]
}

所有字段都有一个 <field>_count 字段,用于统计该字段中具有非 null 值的行数。

查询

query CountProductsWithExpirationDate {
  products {
    expirationDate_count
  }
}

回答
field_count

例如,如果您有 3 个具有失效日期的商品,则结果将如下所示:

{
  "products": [
    {
    "expirationDate_count": 3
    }
  ]
}
数字字段的 _min、_max、_sum 和 _avg

数值字段(int、float、int64)还具有 <field>_min、<field>_max、<field>_sum 和 <field>_avg。

查询

query NumericAggregates {
  products {
  quantityInStock_max
  price_min
  price_avg
  quantityInStock_sum
  }
}

回答
_min _max _sum _avg

例如,如果您有以下商品:

  • 产品 A:quantityInStock: 10,price: 2.99
  • 产品 B:quantityInStock: 5,price: 5.99
  • 产品 C:quantityInStock: 20,price: 1.99

结果如下:

{
  "products": [
    {
    "quantityInStock_max": 20,
    "price_min": 1.99,
    "price_avg": 3.6566666666666666,
    "quantityInStock_sum": 35
    }
  ]
}
日期和时间戳的 _min 和 _max

日期和时间戳字段具有 <field>_min 和 <field>_max。

查询

query DateAndTimeAggregates {
  products {
  expirationDate_max
  expirationDate_min
  }
}

回答
_min _maxdatetime

例如,如果您有以下失效日期:

  • 产品 A:2024-01-01
  • 产品 B:2024-03-01
  • 产品 C:2024-02-01

结果如下:

{
  "products": [
    {
    "expirationDate_max": "2024-03-01",
    "expirationDate_min": "2024-01-01"
    }
  ]
}

Distinct

借助 distinct 参数,您可以获取某个字段(或字段组合)的所有唯一值。例如:

查询

query ListDistinctManufacturers {
  products(distinct: true) {
    manufacturer
  }
}

回答
distinct

例如,如果您有以下制造商:

  • 产品 A:manufacturer: "Acme"
  • 产品 B:manufacturer: "Beta"
  • 产品 C:manufacturer: "Acme"

结果如下:

{
  "products": [
    { "manufacturer": "Acme" },
    { "manufacturer": "Beta" }
  ]
}

您还可以对汇总字段使用 distinct 参数,以改为汇总不同的值。例如:

查询

query CountDistinctManufacturers {
  products {
    manufacturer_count(distinct: true)
  }
}

回答
distinctonaggregate

例如,如果您有以下制造商:

  • 产品 A:manufacturer: "Acme"
  • 产品 B:manufacturer: "Beta"
  • 产品 C:manufacturer: "Acme"

结果如下:

{
  "products": [
    {
    "manufacturer_count": 2
    }
  ]
}

分组汇总

您可以通过在类型中选择汇总字段和非汇总字段的组合来执行分组汇总。这会将非汇总字段值相同的所有匹配行归为一组,并计算该组的汇总字段。例如:

查询

query MostExpensiveProductByManufacturer {
  products {
  manufacturer
  price_max
  }
}

回答
groupedaggregates

例如,如果您有以下商品:

  • 产品 A:manufacturer: "Acme",price: 2.99
  • 产品 B:manufacturer: "Beta",price: 5.99
  • 产品 C:manufacturer: "Acme",price: 1.99

结果如下:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}
having 和 where(含分组汇总)

您还可以使用 having 和 where 实参,仅返回符合所提供条件的群组。

  • having 可让您按汇总字段过滤群组
  • where 可让您根据非聚合字段过滤行。

查询

query FilteredMostExpensiveProductByManufacturer {
  products(having: {price_max: {ge: 2.99}}) {
  manufacturer
  price_max
  }
}

回答
havingwhere

例如,如果您有以下商品:

  • 产品 A:manufacturer: "Acme",price: 2.99
  • 产品 B:manufacturer: "Beta",price: 5.99
  • 产品 C:manufacturer: "Acme",price: 1.99

结果如下:

{
  "products": [
    { "manufacturer": "Acme", "price_max": 2.99 },
    { "manufacturer": "Beta", "price_max": 5.99 }
  ]
}

跨表汇总

汇总字段可与生成的一对多关系字段结合使用,以回答有关数据的复杂问题。下面是一个修改后的架构,其中包含单独的表 Manufacturer,我们可以在示例中使用该架构:

  type Product @table {
    name: String!
    manufacturer: Manufacturer!
    quantityInStock: Int!
    price: Float!
    expirationDate: Date
  }

  type Manufacturer @table {
    name: String!
    headquartersCountry: String!
  }

现在,我们可以使用汇总字段来执行各种操作,例如查找制造商生产的产品数量:

查询

query GetProductCount($id: UUID) {
  manufacturers {
    name
    products_on_manufacturer {
      _count
    }
  }
}

回答
aggregatesacrosstables

例如,如果您有以下制造商:

  • 制造商 A:name: "Acme",products_on_manufacturer: 2
  • 制造商 B:name: "Beta",products_on_manufacturer: 1

结果如下:

{
  "manufacturers": [
    { "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
    { "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
  ]
}

查询枚举字段

您可以查询架构中的枚举字段。

假设我们的枚举实现如下:

enum AspectRatio {
   ACADEMY
   WIDESCREEN
   ANAMORPHIC
   IMAX
   "No information available on aspect ratio"
   UNAVAILABLE
}

例如,您可以列出所有采用您首选宽高比的电影。

query ListMoviesByAspectRatio($aspectRatio: AspectRatio!) @auth(level: PUBLIC) {
  movies(where:
    {originalAspectRatio: {eq: $aspectRatio}}
) {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
    otherAspectRatios
  }
}

过滤枚举字段

枚举具有可用的标准过滤操作:eq、ne、ge、gt、lt、le、in nin,以及适用于列表的 includes、excludes、includesAll 和 excludesAll。

在 order 中,大于和小于运算符使用枚举值的顺序进行有意义的执行。枚举开头的值小于枚举定义末尾的值。

例如,以下 where 子句将返回 ratio 小于输入宽高比的所有电影。

  • 在第一个示例中,如果 $aspectRatio 为 IMAX,则系统会选择 ratio 与 ACADEMY、WIDESCREEN 和 ANAMORPHIC 匹配的影片。
  • 在第二个示例中,系统只会返回同时提供 ACADEMY 和 WIDESCREEN 格式的电影。
  movies(where: {originalAspectRatio: {lt: $aspectRatio}})

  movies(where: {otherAspectRatios: {includesAll: [ACADEMY, WIDESCREEN]}})

对枚举字段执行聚合查询

枚举仅支持 _count。

以下查询会统计每种宽高比的电影总数。

query MovieCountByOriginalAspectRatio @auth(level: PUBLIC) {
  movies {
    _count
    originalAspectRatio
  }
}

从客户端代码调用枚举查询

如需了解如何从客户端使用枚举查询,以及如何设计客户端调用以最大限度地提高管理枚举的灵活性,请参阅 iOS、Android、Web 和 Flutter 的 SDK 指南。

编写高级查询:使用 query 字段在多步操作中读取数据

在许多情况下,您可能需要在执行变更期间读取数据库,以便在执行插入或更新等操作之前查找和验证现有数据。这些选项可节省往返操作,从而节省费用。

SQL Connect 支持此功能。 请参阅多步操作。

后续步骤

您可能感兴趣的内容: