Description
Génère un document pour chaque élément d'un tableau.
Les nouveaux documents contiennent tous les champs de l'entrée, ainsi qu'un élément différent du tableau. L'élément de tableau est stocké dans l'alias fourni, ce qui peut écraser toute valeur préexistante portant le même nom de champ.
Vous pouvez également spécifier l'argument index_field. Lorsqu'il est présent, il inclut l'index de base zéro de l'élément du tableau source dans les documents de sortie.
Cette étape se comporte de la même manière que CROSS JOIN UNNEST(...) dans de nombreux systèmes SQL.
Exemples
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field("scores").as("userScore"), /* index_field= */ "attempt")
.execute();
Comportement
Alias et champ d'index
alias et index_field facultatif écrasent les champs d'origine s'ils existent déjà dans le document d'entrée. Si index_field n'est pas fourni, les documents de sortie ne contiendront pas ce champ.
Par exemple, pour la collection suivante :
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});
L'étape unnest peut être utilisée pour extraire chaque score individuel par utilisateur.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field("scores").as("userScore"), /* index_field= */ "attempt")
.execute();
Dans ce cas, userScore et attempt sont tous deux écrasés.
{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}
Exemples supplémentaires
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() )
Java
Pipeline.Snapshot results = firestore .pipeline() .database() .unnest("arrayField", "unnestedArrayField", new UnnestOptions().withIndexField("index")) .execute() .get();
Go
snapshot := client.Pipeline(). Database(). UnnestWithAlias("arrayField", "unnestedArrayField", firestore.WithUnnestIndexField("index")). Execute(ctx)
Valeurs non-tableau
Si l'expression d'entrée correspond à une valeur non-tableau, cette étape renvoie le document d'entrée tel quel avec index_field défini sur NULL, si spécifié.
Par exemple, pour la collection suivante :
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}});
L'étape unnest peut être utilisée pour extraire chaque score individuel par utilisateur.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field("scores").as("userScore"), /* index_field= */ "attempt")
.execute();
Les documents suivants sont générés avec attempt défini sur NULL.
{ name: "foo", scores: 1, attempt: null }
{ name: "bar", scores: null, attempt: null }
{ name: "qux", scores: { backupScores: 1 }, attempt: null }
Valeurs de tableau vides
Si l'expression d'entrée correspond à un tableau vide, aucun document n'est renvoyé pour ce document d'entrée.
Par exemple, pour la collection suivante :
Node.js
await db.collection("users").add({name: "foo", scores: [5, 4]});
await db.collection("users").add({name: "bar", scores: []});
L'étape unnest peut être utilisée pour extraire chaque score individuel par utilisateur.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field("scores").as("userScore"), /* index_field= */ "attempt")
.execute();
Les documents suivants sont générés, mais l'utilisateur bar est absent de la sortie.
{name: "foo", scores: [5, 4], userScore: 5, attempt: 0}
{name: "foo", scores: [5, 4], userScore: 4, attempt: 1}
Pour renvoyer également des documents avec des tableaux vides, vous pouvez encapsuler la valeur non imbriquée dans un tableau. Exemple :
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();
Le document est désormais renvoyé avec l'utilisateur 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}
Exemples supplémentaires
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}
Java
// Input // { "identifier" : 1, "neighbors": [ "Alice", "Cathy" ] } // { "identifier" : 2, "neighbors": [] } // { "identifier" : 3, "neighbors": "Bob" } Pipeline.Snapshot results = firestore .pipeline() .database() .unnest("neighbors", "unnestedNeighbors", new UnnestOptions().withIndexField("index")) .execute() .get(); // 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}
Go
// Input // { "identifier" : 1, "neighbors": [ "Alice", "Cathy" ] } // { "identifier" : 2, "neighbors": [] } // { "identifier" : 3, "neighbors": "Bob" } results, err := client.Pipeline(). Database(). UnnestWithAlias("neighbors", "unnestedNeighbors", firestore.WithUnnestIndexField("index")). Execute(ctx).Results().GetAll() if err != nil { fmt.Fprintf(w, "GetAll failed: %v", err) return err } // Output // { "identifier": 1, "neighbors": [ "Alice", "Cathy" ], // "unnestedNeighbors": "Alice", "index": 0 } // { "identifier": 1, "neighbors": [ "Alice", "Cathy" ], // "unnestedNeighbors": "Cathy", "index": 1 } // { "identifier": 3, "neighbors": "Bob", "index": nil}
Désimbrication imbriquée
Si l'expression correspond à un tableau imbriqué, plusieurs étapes unnest(...) doivent être utilisées pour aplatir chaque niveau imbriqué.
Par exemple, pour la collection suivante :
Node.js
await db.collection("users").add({name: "foo", record: [{scores: [5, 4], avg: 4.5}, {scores: [1, 3], old_avg: 2}]});
L'étape unnest(...) peut être utilisée de manière séquentielle pour extraire le tableau le plus imbriqué.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field("record").as("record"))
.unnest(field("record.scores").as("userScore"), /* index_field= */ "attempt")
.execute();
Les documents suivants sont générés :
{ 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 }