ยกเลิกการซ้อน

คำอธิบาย

สร้างเอกสารใหม่สำหรับแต่ละองค์ประกอบในอาร์เรย์

เอกสารใหม่จะมีฟิลด์ทั้งหมดจากอินพุตพร้อมกับ องค์ประกอบที่แตกต่างกันจากอาร์เรย์ ระบบจะจัดเก็บองค์ประกอบอาร์เรย์ไปยัง alias ที่ระบุ ซึ่งอาจเขียนทับค่าที่มีอยู่ก่อนแล้วซึ่งมีชื่อฟิลด์เดียวกัน

หรือจะระบุอาร์กิวเมนต์ index_field ก็ได้ (ไม่บังคับ) หากมีอยู่ จะรวมดัชนีฐาน 0 ขององค์ประกอบจากอาร์เรย์แหล่งที่มาไว้ในเอกสารเอาต์พุต

โดยขั้นตอนนี้จะทำงานคล้ายกับ CROSS JOIN UNNEST(...) ในระบบ SQL หลายระบบ

ตัวอย่าง

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()
)
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)

ค่าที่ไม่ใช่อาร์เรย์

หากนิพจน์อินพุตประเมินเป็นค่าที่ไม่ใช่อาร์เรย์ ขั้นตอนนี้จะ แสดงผลเอกสารอินพุตตามเดิมโดยตั้งค่า 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}
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}

Nested Unnest

ในกรณีที่นิพจน์ประเมินเป็นอาร์เรย์ที่ซ้อนกัน คุณต้องใช้ 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 }