설명
배열의 각 요소에 대해 새 문서를 생성합니다.
새 문서에는 입력의 모든 필드와 배열의 다른 요소가 포함됩니다. 배열 요소는 지정된 alias에 저장되며, 필드 이름이 동일한 기존 값이 덮어쓰여질 수 있습니다.
선택적으로 index_field 인수를 지정할 수 있습니다. 있는 경우 출력 문서에 소스 배열의 0부터 시작하는 요소 색인이 포함됩니다.
이 단계는 여러 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();
이 경우 userScore와 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}
추가 예
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() )
자바
Pipeline.Snapshot results = firestore .pipeline() .database() .unnest("arrayField", "unnestedArrayField", new UnnestOptions().withIndexField("index")) .execute() .get();
배열이 아닌 값
입력 표현식이 배열이 아닌 값으로 평가되면 이 단계에서는 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}
자바
// 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 단계를 사용해야 합니다.
다음 컬렉션을 예로 들어보겠습니다.
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}