Unnest

설명

배열의 각 요소에 대해 새 문서를 생성합니다.

새 문서에는 입력의 모든 필드와 배열의 다른 요소가 포함됩니다. 배열 요소는 지정된 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();

이 경우 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()
)
자바
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();

이렇게 하면 attemptNULL로 설정된 다음 문서가 생성됩니다.

  {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}