آشفتگی

توضیحات

برای هر عنصر در یک آرایه، یک سند جدید تولید می‌کند.

اسناد جدید شامل تمام فیلدهای ورودی به همراه یک عنصر متفاوت از آرایه هستند. عنصر آرایه در alias داده شده ذخیره می‌شود و به طور بالقوه هر مقدار از پیش موجود با همان نام فیلد را بازنویسی می‌کند.

به صورت اختیاری، می‌توان آرگومان index_field را مشخص کرد. در صورت وجود، این آرگومان شامل اندیس مبتنی بر صفر عنصر از آرایه منبع در اسناد خروجی می‌شود.

این مرحله در بسیاری از سیستم‌های SQL مشابه CROSS JOIN UNNEST(...) عمل می‌کند.

مثال‌ها

نود جی اس

const userScore = await db.pipeline()
    .collection("/users")
    .unnest(field("scores").as("userScore"), /* index_field= */ "attempt")
    .execute();

رفتار

فیلد نام مستعار و ایندکس

اگر فیلدها از قبل در سند ورودی وجود داشته باشند، alias و فیلد اختیاری index_field جایگزین فیلدهای اصلی می‌شوند. اگر فیلد index_field ارائه نشود، اسناد خروجی حاوی این فیلد نخواهند بود.

به عنوان مثال، برای مجموعه زیر:

نود جی اس

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 می‌تواند برای استخراج امتیاز هر کاربر به صورت جداگانه استفاده شود.

نود جی اس

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}

مثال‌های اضافی

سویفت
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();
پایتون
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();
برو
snapshot := client.Pipeline().
	Database().
	UnnestWithAlias("arrayField", "unnestedArrayField", firestore.WithUnnestIndexField("index")).
	Execute(ctx)

مقادیر غیر آرایه‌ای

اگر عبارت ورودی به مقداری غیر از آرایه ارزیابی شود، در این مرحله سند ورودی به همان شکلی که هست و مقدار index_field آن (در صورت مشخص بودن) برابر با NULL است، بازگردانده می‌شود.

به عنوان مثال، برای مجموعه زیر:

نود جی اس

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 می‌تواند برای استخراج امتیاز هر کاربر به صورت جداگانه استفاده شود.

نود جی اس

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 }

مقادیر آرایه خالی

اگر عبارت ورودی به یک آرایه خالی ارزیابی شود، هیچ سندی برای آن سند ورودی برگردانده نمی‌شود.

به عنوان مثال، برای مجموعه زیر:

نود جی اس

await db.collection("users").add({name: "foo", scores: [5, 4]});
await db.collection("users").add({name: "bar", scores: []});

مرحله unnest می‌تواند برای استخراج امتیاز هر کاربر به صورت جداگانه استفاده شود.

نود جی اس

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}

برای اینکه اسنادی با آرایه‌های خالی را نیز برگردانید، می‌توانید مقدار بدون تو در تو را در یک آرایه قرار دهید. برای مثال:

نود جی اس

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}

مثال‌های اضافی

نود جی اس
    // 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}
    
سویفت
// 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}
پایتون
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}
برو
// 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}

آشیانه تو در تو

در صورتی که عبارت به یک آرایه تودرتو ارزیابی شود، باید از چندین مرحله unnest(...) برای مسطح‌سازی هر سطح تودرتو استفاده شود.

به عنوان مثال، برای مجموعه زیر:

نود جی اس

await db.collection("users").add({name: "foo", record: [{scores: [5, 4], avg: 4.5}, {scores: [1, 3], old_avg: 2}]});

مرحله unnest(...) می‌تواند به صورت متوالی برای استخراج درونی‌ترین آرایه استفاده شود.

نود جی اس

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 }