Descripción
Genera un documento nuevo para cada elemento de un array.
Los documentos nuevos contienen todos los campos de la entrada junto con un elemento diferente del array. El elemento del array se almacena en el alias proporcionado, lo que podría reemplazar cualquier valor preexistente con el mismo nombre de campo.
De manera opcional, se puede especificar el argumento index_field. Cuando está presente, incluye el índice basado en cero del elemento del array de origen en los documentos de salida.
Esta etapa se comporta de manera similar a CROSS JOIN UNNEST(...) en muchos sistemas SQL.
Sintaxis
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
.execute();
Comportamiento
Alias y campo de índice
alias y index_field opcional reemplazarán los campos originales si ya existen en el documento de entrada. Si no se proporciona el index_field, los documentos de salida no contendrán este campo.
Por ejemplo, para la siguiente colección:
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});
La etapa unnest se puede usar para extraer cada puntuación individual por usuario.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
.execute();
En este caso, se sobrescriben userScore y attempt.
{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}
Ejemplos adicionales
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();
Valores que no son de matriz
Si la expresión de entrada se evalúa como un valor que no es un array, esta etapa devolverá el documento de entrada tal como está con index_field establecido en NULL, si se especifica.
Por ejemplo, para la siguiente colección:
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}});
La etapa unnest se puede usar para extraer cada puntuación individual por usuario.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
.execute();
Esto produce los siguientes documentos con attempt establecido en NULL.
{name: "foo", scores: 1, attempt: null}
{name: "bar", scores: null, attempt: null}
{name: "qux", scores: {backupScores: 1}, attempt: null}
Valores de array vacíos
Si la expresión de entrada se evalúa como un array vacío, no se devolverá ningún documento para ese documento de entrada.
Por ejemplo, para la siguiente colección:
Node.js
await db.collection('users').add({name: "foo", scores: [5, 4]});
await db.collection('users').add({name: "bar", scores: []});
La etapa unnest se puede usar para extraer cada puntuación individual por usuario.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field('scores').as('userScore'), /* index_field= */ 'attempt')
.execute();
Esto produce los siguientes documentos sin el usuario bar en el resultado.
{name: "foo", scores: [5, 4], userScore: 5, attempt: 0}
{name: "foo", scores: [5, 4], userScore: 4, attempt: 1}
Para devolver también documentos con arrays vacíos, puedes incluir el valor sin anidar en un array. Por ejemplo:
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();
Ahora se devolverá el documento con el usuario 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}
Ejemplos adicionales
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}
Unnest anidado
En el caso de que la expresión se evalúe como un array anidado, se deben usar varias etapas de unnest para aplanar cada nivel anidado.
Por ejemplo, para la siguiente colección:
Node.js
await db.collection('users').add({name: "foo", record: [{scores: [5, 4], avg: 4.5}, {scores: [1, 3], old_avg: 2}]});
La etapa unnest se puede usar de forma secuencial para extraer el array más interno.
Node.js
const userScore = await db.pipeline()
.collection("/users")
.unnest(field('record').as('record'))
.unnest(field('record.scores').as('userScore'), /* index_field= */ 'attempt')
.execute();
Esto produce los siguientes documentos:
{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}