取消嵌套

说明

为数组中的每个元素生成一个新文档。

新文档包含输入中的所有字段以及数组中的不同元素。数组元素会存储到给定的 alias 中,可能会覆盖具有相同字段名称的任何预先存在的值。

您可以选择指定 index_field 实参。如果存在,则会在输出文档中包含相应元素在源数组中的从零开始的索引。

此阶段的行为与许多 SQL 系统中的 CROSS JOIN UNNEST(...) 类似。

语法

Node.js

const userScore = await db.pipeline()
    .collection("/users")
    .unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
    .execute();

行为

别名和索引字段

如果输入文档中已存在相应字段,alias 和可选的 index_field 将覆盖原始字段。如果未提供 index_field,则输出文档不会包含此字段。

例如,对于以下集合:

Node.js

await db.collection('users').add({name: "foo", scores: [5, 4], userScore: 0});
await db.collection('users').add({name: "bar", scores: [1, 3], attempt: 5});

unnest 阶段可用于提取每个用户的各个得分。

Node.js

const userScore = await db.pipeline()
    .collection("/users")
    .unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
    .execute();

在这种情况下,userScoreattempt 都会被覆盖。

  {name: "foo", scores: [5, 4], userScore: 5, attempt: 0}
  {name: "foo", scores: [5, 4], userScore: 4, attempt: 1}
  {name: "bar", scores: [1, 3], userScore: 1, attempt: 0}
  {name: "bar", scores: [1, 3], userScore: 3, attempt: 1}

更多示例

Swift
let results = try await db.pipeline()
  .database()
  .unnest(Field("arrayField").as("unnestedArrayField"), indexField: "index")
  .execute()

Kotlin

val results = db.pipeline()
    .database()
    .unnest(field("arrayField").alias("unnestedArrayField"), UnnestOptions().withIndexField("index"))
    .execute()

Java

Task<Pipeline.Snapshot> results = db.pipeline()
    .database()
    .unnest(field("arrayField").alias("unnestedArrayField"), new UnnestOptions().withIndexField("index"))
    .execute();
Python
from google.cloud.firestore_v1.pipeline_expressions import Field
from google.cloud.firestore_v1.pipeline_stages import UnnestOptions

results = (
    client.pipeline()
    .database()
    .unnest(
        Field.of("arrayField").as_("unnestedArrayField"),
        options=UnnestOptions(index_field="index"),
    )
    .execute()
)

非数组值

如果输入表达式的计算结果为非数组值,则此阶段将按原样返回输入文档,同时将 index_field 设置为 NULL(如果已指定)。

例如,对于以下集合:

Node.js

await db.collection('users').add({name: "foo", scores: 1});
await db.collection('users').add({name: "bar", scores: null});
await db.collection('users').add({name: "qux", scores: {backupScores: 1}});

unnest 阶段可用于提取每个用户的各个得分。

Node.js

const userScore = await db.pipeline()
    .collection("/users")
    .unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
    .execute();

这会生成以下文档,并将 attempt 设置为 NULL

  {name: "foo", scores: 1, attempt: null}
  {name: "bar", scores: null, attempt: null}
  {name: "qux", scores: {backupScores: 1}, attempt: null}

空数组值

如果输入表达式的计算结果为空数组,则不会为该输入文档返回任何文档。

例如,对于以下集合:

Node.js

await db.collection('users').add({name: "foo", scores: [5, 4]});
await db.collection('users').add({name: "bar", scores: []});

unnest 阶段可用于提取每个用户的各个得分。

Node.js

const userScore = await db.pipeline()
    .collection("/users")
    .unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
    .execute();

这会生成以下文档,其中输出中缺少用户 bar

  {name: "foo", scores: [5, 4], userScore: 5, attempt: 0}
  {name: "foo", scores: [5, 4], userScore: 4, attempt: 1}

为了也返回具有空数组的文档,您可以将取消嵌套的值封装在数组中。例如:

Node.js

const userScore = await db.pipeline()
    .collection("/users")
    .unnest(
      conditional(
        equal(field('scores'), []),
        array([field('scores')]),
        field('scores')
      ).as("userScore"),
    /* index_field= */ "attempt")
    .execute();

现在,此查询将返回用户为 bar 的文档。

  {name: "foo", scores: [5, 4], userScore: 5, attempt: 0}
  {name: "foo", scores: [5, 4], userScore: 4, attempt: 1}
  {name: "bar", scores: [], userScore: [], attempt: 0}

更多示例

Node.js
    // Input
    // { identifier : 1, neighbors: [ "Alice", "Cathy" ] }
    // { identifier : 2, neighbors: []                   }
    // { identifier : 3, neighbors: "Bob"                }

    const results = await db.pipeline()
      .database()
      .unnest(Field.of('neighbors'), 'unnestedNeighbors', 'index')
      .execute();

    // Output
    // { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Alice", index: 0 }
    // { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Cathy", index: 1 }
    // { identifier: 3, neighbors: "Bob", index: null}
    
Swift
// Input
// { identifier : 1, neighbors: [ "Alice", "Cathy" ] }
// { identifier : 2, neighbors: []                   }
// { identifier : 3, neighbors: "Bob"                }

let results = try await db.pipeline()
  .database()
  .unnest(Field("neighbors").as("unnestedNeighbors"), indexField: "index")
  .execute()

// Output
// { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Alice", index: 0 }
// { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Cathy", index: 1 }
// { identifier: 3, neighbors: "Bob", index: null}

Kotlin

// Input
// { identifier : 1, neighbors: [ "Alice", "Cathy" ] }
// { identifier : 2, neighbors: []                   }
// { identifier : 3, neighbors: "Bob"                }

val results = db.pipeline()
    .database()
    .unnest(field("neighbors").alias("unnestedNeighbors"), UnnestOptions().withIndexField("index"))
    .execute()

// Output
// { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Alice", index: 0 }
// { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Cathy", index: 1 }
// { identifier: 3, neighbors: "Bob", index: null}

Java

// Input
// { identifier : 1, neighbors: [ "Alice", "Cathy" ] }
// { identifier : 2, neighbors: []                   }
// { identifier : 3, neighbors: "Bob"                }

Task<Pipeline.Snapshot> results = db.pipeline()
    .database()
    .unnest(field("neighbors").alias("unnestedNeighbors"), new UnnestOptions().withIndexField("index"))
    .execute();

// Output
// { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Alice", index: 0 }
// { identifier: 1, neighbors: [ "Alice", "Cathy" ], unnestedNeighbors: "Cathy", index: 1 }
// { identifier: 3, neighbors: "Bob", index: null}
Python
from google.cloud.firestore_v1.pipeline_expressions import Field
from google.cloud.firestore_v1.pipeline_stages import UnnestOptions

# Input
# { "identifier" : 1, "neighbors": [ "Alice", "Cathy" ] }
# { "identifier" : 2, "neighbors": []                   }
# { "identifier" : 3, "neighbors": "Bob"                }

results = (
    client.pipeline()
    .database()
    .unnest(
        Field.of("neighbors").as_("unnestedNeighbors"),
        options=UnnestOptions(index_field="index"),
    )
    .execute()
)

# Output
# { "identifier": 1, "neighbors": [ "Alice", "Cathy" ],
#   "unnestedNeighbors": "Alice", "index": 0 }
# { "identifier": 1, "neighbors": [ "Alice", "Cathy" ],
#   "unnestedNeighbors": "Cathy", "index": 1 }
# { "identifier": 3, "neighbors": "Bob", "index": null}

嵌套取消嵌套

如果表达式的计算结果为嵌套数组,则必须使用多个 unnest 阶段来展平每个嵌套级别。

例如,对于以下集合:

Node.js

await db.collection('users').add({name: "foo", record: [{scores: [5, 4], avg: 4.5}, {scores: [1, 3], old_avg: 2}]});

unnest 阶段可以按顺序使用,以提取最内层的数组。

Node.js

const userScore = await db.pipeline()
    .collection("/users")
    .unnest(field('record').as('record'))
    .unnest(field('record.scores').as('userScore'), /* index_field= */ 'attempt')
    .execute();

这会生成以下文档:

  {name: "foo", record: [{scores: [5, 4], avg: 4.5}], userScore: 5, attempt: 0}
  {name: "foo", record: [{scores: [5, 4], avg: 4.5}], userScore: 4, attempt: 1}
  {name: "foo", record: [{scores: [1, 3], avg: 2}], userScore: 1, attempt: 0}
  {name: "foo", record: [{scores: [1, 3], avg: 2}], userScore: 3, attempt: 1}