الحقول القابلة للقيمة الفارغة

يدعم Milvus الحقول القابلة للخلو، والتي تسمح بغياب قيمة الحقل أو تعيينها صراحةً إلى NULL. يتم تعريف قابلية الخلو على مستوى المخطط وتُطبق بشكل متسق عبر عمليات استيعاب البيانات والفهرسة والبحث والاستعلام.

استخدم الحقول القابلة للقيمة الفارغة في الحالات التالية:

  • يتم استيعاب البيانات من أنظمة خارجية تسمح بوجود قيم مفقودة.
  • بعض البيانات الوصفية اختيارية أو متاحة فقط لجزء من مجموعة البيانات.
  • يتم إنشاء التضمينات المتجهة بشكل غير متزامن وإدراجها لاحقًا.

القيود

  • الحقول المتجهة التي تسمح بقيم NULL لا تدعم تعبيرات التصفية من نوع " IS NULL " أو " IS NOT NULL ". لا يمكنك تصفية الكيانات بشكل صريح بناءً على ما إذا كانت قيمة الحقل المتجه هي NULL أم لا.

  • بدءًا من Milvus 3.0.0، يمكن أن يكون حقل StructArray الأصلي قابلاً للقيمة NULL. قم بتعيين nullable=True على حقل StructArray الأصلي، وليس على الحقول الفرعية الفردية. تنطبق القيمة NULL على حقل StructArray بأكمله، وليس على عنصر Struct فردي، ويقوم Milvus بنقل قابلية الحقل الأصلي للقيمة NULL إلى حقوله الفرعية داخليًا. يجب أن يكون حقل StructArray المضاف إلى مجموعة موجودة قابلاً للقيمة NULL حتى تتمكن الكيانات الموجودة من إرجاع القيمة NULL للحقل الجديد. لمزيد من التفاصيل، راجع حدود StructArray.

  • يتم تعريف السمة «nullable» عند إنشاء الحقل ولا يمكن تعديلها لاحقًا. لا يمكنك تمكين أو تعطيل قابلية القيمة «null» لحقل موجود.

  • لا يمكن استخدام الحقول التي تم وضع علامة "قابلة للقيمة null" عليها كمفاتيح تقسيم. يجب أن تحتوي حقول مفاتيح التقسيم دائمًا على قيم صالحة وغير فارغة. لمزيد من المعلومات، راجع استخدام مفتاح التقسيم.

ما هو الحقل القابل للقيمة "null"؟

في Milvus، يتم التحكم في ما إذا كان يُسمح لحقل ما بتخزين قيمة NULL من خلال سمة حقل على مستوى المخطط تُسمى « nullable ».

عندما يتم تعريف حقل بـ nullable=True ، يسمح Milvus بفقدان قيمة الحقل أثناء استيعاب البيانات. عمليًا، يعامل Milvus المدخلتين التاليتين على أنهما متكافئتان ويخزن قيمة الحقل كـ NULL:

  • تم حذف الحقل من الكيان المدخل.
  • تعيين الحقل صراحةً إلى NULL (على سبيل المثال، None في لغة Python).

إذا لم يتم تعريف الحقل على أنه قابل للفراغ (السلوك الافتراضي)، فيجب أن يوفر كل كيان قيمة صالحة لهذا الحقل. سيؤدي حذف الحقل أو تعيين قيمة NULL صراحةً إلى فشل عملية الإدراج أو الاستيراد.

يتم دعم السمة "nullable" لكل من الحقول القياسية والمتجهة في مخطط المجموعة. بدءًا من Milvus 3.0.0، يتم دعمها أيضًا في الحقل الأصلي StructArray. لا تقم بتكوين الحقول الفرعية لـ Struct على أنها قابلة للفراغ بشكل مستقل؛ قم بتعريف قابلية الفراغ في الحقل الأصلي StructArray وسيقوم Milvus بنشر هذا الإعداد إلى حقوله الفرعية داخليًا.

تحدد خاصية «nullable» ما إذا كان من الممكن أن تكون قيمة الحقل مفقودة؛ وهي لا تحدد القيمة التي يتم استخدامها عند فقدان الحقل.

  • إذا تم تكوين حقل قابل للقيمة الفارغة بدون قيمة افتراضية، فإن حذف الحقل يؤدي إلى تخزين قيمة NULL.
  • إذا تم تكوين قيمة افتراضية، فقد يقوم Milvus بتخزين القيمة الافتراضية بدلاً من ذلك. لمزيد من التفاصيل، راجع القيم الافتراضية.

تحديد حقل قابل للقيمة الفارغة في مخطط المجموعة

لاستخدام الحقول القابلة للفراغ، يجب تمكين السمة «nullable» عند تعريف مخطط المجموعة.

في هذا المثال، يحدد مخطط المجموعة حقل متجهًا باسم « embedding » بقيمة « nullable=True ». وهذا يسمح للكيانات في المجموعة بحذف قيمة المتجه أو تعيينها صراحةً إلى «NULL» أثناء استيعاب البيانات.

from pymilvus import MilvusClient, DataType

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

# Define schema fields
schema = client.create_schema()
schema.add_field("id", DataType.INT64, is_primary=True)  # Primary field
schema.add_field(
    field_name="embedding",
    datatype=DataType.FLOAT_VECTOR,
    dim=4,
    nullable=True,  # Enable the nullable attribute; defaults to False
)

client.create_collection(
    collection_name="my_collection",
    schema=schema,
)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
        .uri("http://localhost:19530")
        .token("root:Milvus")
        .build());

CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
        .build();

schema.addField(AddFieldReq.builder()
        .fieldName("id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("embedding")
        .dataType(DataType.FloatVector)
        .dimension(4)
        .isNullable(true)
        .build());

client.createCollection(CreateCollectionReq.builder()
        .collectionName("my_collection")
        .collectionSchema(schema)
        .build());
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";

const client = new MilvusClient({
  address: "http://localhost:19530",
  token: "root:Milvus",
});

await client.createCollection({
  collection_name: "my_collection",
  fields: [
    {
      name: "id",
      data_type: DataType.Int64,
      is_primary_key: true,
      autoID: false,
    },
    {
      name: "embedding",
      data_type: DataType.FloatVector,
      dim: 4,
      nullable: true,
    },
  ],
});
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v2/entity"
    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: "localhost:19530",
})
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
defer client.Close(ctx)

schema := entity.NewSchema()
schema.WithField(entity.NewField().
    WithName("id").
    WithDataType(entity.FieldTypeInt64).
    WithIsPrimaryKey(true),
).WithField(entity.NewField().
    WithName("embedding").
    WithDataType(entity.FieldTypeFloatVector).
    WithDim(4).
    WithNullable(true),
)

err = client.CreateCollection(ctx,
    milvusclient.NewCreateCollectionOption("my_collection", schema))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
export TOKEN="root:Milvus"
export CLUSTER_ENDPOINT="http://localhost:19530"

export pkField='{
  "fieldName": "id",
  "dataType": "Int64",
  "isPrimary": true
}'

export embeddingField='{
  "fieldName": "embedding",
  "dataType": "FloatVector",
  "typeParams": {"dim": "4"},
  "nullable": true
}'

curl --request POST \
  --url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
  --header "Authorization: Bearer ${TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Request-Timeout: 10" \
  -d "{
    \"collectionName\": \"my_collection\",
    \"schema\": {
      \"fields\": [
        $pkField,
        $embeddingField
      ]
    }
  }"

في هذا المخطط:

  • يتم تمييز الحقل embedding صراحةً على أنه قابل للقيمة الفارغة.
  • يمكن للكيانات حذف حقل embedding أو تعيين قيمة NULL له أثناء الإدراج.
  • يتم تحديد قرار السماح بقيم NULL عند إنشاء المجموعة.

للتوضيح، تركز الأمثلة التالية على حقل متجه قابل للقيمة NULL (embedding). يعد تعريف الحقول القياسية القابلة للقيمة NULL اختياريًا وليس مطلوبًا لمتابعة بقية هذا الدليل.

اختياري: تعريف حقل قياسي قابل للقيمة الفارغة

يمكن أيضًا تعريف الحقول العددية على أنها قابلة للقيمة NULL باستخدام نفس السمة nullable وتتبع نفس القواعد أثناء الاستيعاب. على سبيل المثال:

schema.add_field(
    field_name="age",
    datatype=DataType.INT64,
    nullable=True,
)
schema.addField(AddFieldReq.builder()
        .fieldName("age")
        .dataType(DataType.Int64)
        .isNullable(true)
        .build());
// Add to the fields array when calling createCollection:
// { name: "age", data_type: DataType.Int64, nullable: true },
schema.WithField(entity.NewField().
    WithName("age").
    WithDataType(entity.FieldTypeInt64).
    WithNullable(true),
)
# Add another field object to the schema "fields" array, for example:
# { "fieldName": "age", "dataType": "Int64", "nullable": true }

سلوك الإدراج مع القيم المفقودة أو القيم NULL

بمجرد تعريف حقل على أنه قابل للقيمة الفارغة في مخطط المجموعة، يسمح Milvus بأن تكون قيمة الحقل مفقودة أو محددة صراحةً بقيمة NULL أثناء استيعاب البيانات.

يُدرج المثال أدناه ثلاث كيانات في المجموعة التي تم إنشاؤها في «تحديد حقل قابل للقيمة الفارغة في مخطط المجموعة»، مما يوضح هذه الحالات المختلفة.

data = [
    {
        "id": 1,
        "embedding": [0.1, 0.2, 0.3, 0.4],
    },
    {
        "id": 2,
        "embedding": None,  # Explicitly set to NULL
    },
    {
        "id": 3,  # Field omitted → stored as NULL
    },
]

client.insert(
    collection_name="my_collection",
    data=data,
)
import com.google.gson.Gson;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;

import java.util.Arrays;
import java.util.List;

Gson gson = new Gson();

JsonObject row1 = new JsonObject();
row1.addProperty("id", 1);
row1.add("embedding", gson.toJsonTree(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f)));

JsonObject row2 = new JsonObject();
row2.addProperty("id", 2);
row2.add("embedding", JsonNull.INSTANCE); // Explicitly set to NULL

JsonObject row3 = new JsonObject();
row3.addProperty("id", 3); // Field omitted; stored as NULL

List<JsonObject> data = Arrays.asList(row1, row2, row3);

client.insert(InsertReq.builder()
        .collectionName("my_collection")
        .data(data)
        .build());
const data = [
  { id: 1, embedding: [0.1, 0.2, 0.3, 0.4] },
  { id: 2, embedding: null },
  { id: 3 },
];

await client.insert({
  collection_name: "my_collection",
  data: data,
});
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

// Assumes `client` is the Milvus client from the Go schema example above.
ctx := context.Background()

rows := []any{
    map[string]any{"id": int64(1), "embedding": []float32{0.1, 0.2, 0.3, 0.4}},
    map[string]any{"id": int64(2), "embedding": nil},
    map[string]any{"id": int64(3)},
}

_, err := client.Insert(ctx, milvusclient.NewRowBasedInsertOption("my_collection", rows...))
if err != nil {
    fmt.Println(err.Error())
}
curl --request POST \
  --url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
  --header "Authorization: Bearer ${TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Request-Timeout: 10" \
  -d '{
    "collectionName": "my_collection",
    "data": [
      {"id": 1, "embedding": [0.1, 0.2, 0.3, 0.4]},
      {"id": 2, "embedding": null},
      {"id": 3}
    ]
  }'

في هذا المثال:

  • يوفر الكيان id = 1 قيمة متجهة صالحة.
  • الكيان id = 2 يعين صراحةً قيمة NULL لحقل embedding.
  • الكيان ذو المعرف = 3 يحذف الحقل « embedding » بالكامل؛ ويقوم Milvus بتخزينه كقيمة NULL.

سلوك الفهرس في الحقول القابلة للقيمة NULL

بعد إدراج البيانات، يمكنك إنشاء فهرس على حقل قابل للقيمة NULL كالمعتاد. والفرق الرئيسي هو كيفية تعامل Milvus مع القيم NULL أثناء إنشاء الفهرس:

  • تُضاف إلى الفهرس فقط الكيانات ذات القيم غير الفارغة.
  • يتم تخطي الكيانات ذات القيم NULL ولا تشارك في إنشاء الفهرس.

بالنسبة لحقل متجه قابل للقيمة NULL، يعني هذا أن الكيانات التي تحتوي على متجهات صالحة هي فقط التي يمكن البحث عنها باستخدام تشابه المتجهات.

# Set index parameters
index_params = client.prepare_index_params()
index_params.add_index(
    field_name="embedding",
    index_type="AUTOINDEX",
    metric_type="COSINE",
)

# Create index
client.create_index(
    collection_name="my_collection",
    index_params=index_params,
)

# Load collection for future search operations
client.load_collection(collection_name="my_collection")
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.collection.request.LoadCollectionReq;
import io.milvus.v2.service.index.request.CreateIndexReq;

import java.util.Collections;

IndexParam indexParam = IndexParam.builder()
        .fieldName("embedding")
        .indexName("embedding_index")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .metricType(IndexParam.MetricType.COSINE)
        .build();

client.createIndex(CreateIndexReq.builder()
        .collectionName("my_collection")
        .indexParams(Collections.singletonList(indexParam))
        .build());

client.loadCollection(LoadCollectionReq.builder()
        .collectionName("my_collection")
        .build());
await client.createIndex({
  collection_name: "my_collection",
  field_name: "embedding",
  index_name: "embedding_idx",
  index_type: "AUTOINDEX",
  metric_type: "COSINE",
});

await client.loadCollection({
  collection_name: "my_collection",
});
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v2/entity"
    "github.com/milvus-io/milvus/client/v2/index"
    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

// Assumes `client` is the Milvus client from the Go schema example above.
ctx := context.Background()

indexOption := milvusclient.NewCreateIndexOption("my_collection", "embedding",
    index.NewAutoIndex(entity.COSINE))

_, err := client.CreateIndex(ctx, indexOption)
if err != nil {
    fmt.Println(err.Error())
}

_, err = client.LoadCollection(ctx, milvusclient.NewLoadCollectionOption("my_collection"))
if err != nil {
    fmt.Println(err.Error())
}
curl --request POST \
  --url "${CLUSTER_ENDPOINT}/v2/vectordb/indexes/create" \
  --header "Authorization: Bearer ${TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Request-Timeout: 10" \
  -d '{
    "collectionName": "my_collection",
    "indexParams": [
      {
        "fieldName": "embedding",
        "metricType": "COSINE",
        "indexType": "AUTOINDEX"
      }
    ]
  }'

curl --request POST \
  --url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/load" \
  --header "Authorization: Bearer ${TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Request-Timeout: 10" \
  -d '{"collectionName": "my_collection"}'

في هذه المرحلة:

  • يتم فهرسة الكيانات ذات قيم التضمين الصالحة وتصبح جاهزة للبحث.
  • تظل الكيانات التي تكون قيم التضمين الخاصة بها NULL موجودة في المجموعة، ولكنها لا تُدرج في فهرس المتجهات.

سلوك البحث مع الحقول القابلة للقيمة الفارغة

عند إجراء عمليات بحث على حقل قابل للقيمة NULL، يقوم Milvus بتقييم الكيانات التي تحتوي على قيم غير NULL فقط للحقل المستخدم في البحث. يتم تخطي الكيانات التي يكون حقلها المتجه NULL تلقائيًا.

بالنسبة لحقل متجه قابل للقيمة "null" مثل embedding في هذا المثال:

  • يتم تقييم وترتيب الكيانات ذات القيم المتجهة الصحيحة فقط.
  • لا تتسبب الكيانات ذات المتجهات NULL في حدوث أخطاء.
  • إذا كان عدد المتجهات الصحيحة أقل من عدد الكائنات المطلوبة لـ topK (limit)، فقد يعرض Milvus نتائج أقل من limit.

يقوم المثال التالي بإجراء بحث متجهي على الحقل المتجه القابل للقيمة الفارغة embedding:

res = client.search(
    collection_name="my_collection",
    data=[[0.1, 0.2, 0.3, 0.4]],
    anns_field="embedding",
    limit=3,
    search_params={"metric_type": "COSINE"},
    output_fields=["embedding"],
)

print(res)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;

import java.util.Arrays;
import java.util.Collections;

SearchResp res = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(new FloatVec(Arrays.asList(0.1f, 0.2f, 0.3f, 0.4f))))
        .annsField("embedding")
        .limit(3)
        .outputFields(Collections.singletonList("embedding"))
        .build());

System.out.println(res);
const res = await client.search({
  collection_name: "my_collection",
  data: [[0.1, 0.2, 0.3, 0.4]],
  anns_field: "embedding",
  limit: 3,
  search_params: { metric_type: "COSINE" },
  output_fields: ["embedding"],
});

console.log(res);
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v2/entity"
    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

// Assumes `client` is the Milvus client from the Go schema example above.
ctx := context.Background()

query := []float32{0.1, 0.2, 0.3, 0.4}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
    "my_collection",
    3,
    []entity.Vector{entity.FloatVector(query)},
).WithANNSField("embedding").
    WithOutputFields("embedding"))
if err != nil {
    fmt.Println(err.Error())
}
fmt.Println(resultSets)
curl --request POST \
  --url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
  --header "Authorization: Bearer ${TOKEN}" \
  --header "Content-Type: application/json" \
  --header "Request-Timeout: 10" \
  -d '{
    "collectionName": "my_collection",
    "data": [[0.1, 0.2, 0.3, 0.4]],
    "annsField": "embedding",
    "limit": 3,
    "searchParams": {"metricType": "COSINE"},
    "outputFields": ["embedding"]
  }'

في هذا البحث:

  • يتم اعتبار الكيانات التي تحتوي على قيم embedding غير فارغة فقط كمرشحات.
  • يتم استبعاد الكيانات التي تحتوي على قيم NULL لـ embedding من التقييم.
  • يعتمد عدد النتائج التي يتم إرجاعها على عدد المتجهات الصالحة الموجودة في المجموعة.

آثار الاستعلام والتصفية

تركز الأمثلة السابقة على الحقول المتجهة. يصف هذا القسم كيفية تصرف القيم NULL في تعبيرات التصفية القياسية.

يمكن تعريف الحقول القياسية باستخدام nullable=True وتتبع نفس قواعد الاستيعاب التي تتبعها الحقول المتجهة. ومع ذلك، تُقيَّم القيم القياسية NULL دائمًا على أنها false في تعبيرات التصفية.

على سبيل المثال، في حالة وجود حقل سكالاري قابل للقيمة «null» age ، فإن التصفية التالية تختار الكيانات التي يزيد عمرها عن 18 عامًا:

expr = "age > 18"
String filter = "age > 18";
const expr = "age > 18";
filter := "age > 18"
# Use in query/search filter parameter, for example:
# "filter": "age > 18"

يتم استبعاد الكيانات التي تكون فيها قيمة age هي NULL من النتائج لأن القيمة NULL لا تستوفي شرط التصفية.

وبالمثل، لا تتطابق عمليات فحص المساواة مع القيم NULL. على سبيل المثال:

expr = 'status == "active"'
String filter = "status == \"active\"";
const expr = 'status == "active"';
filter := `status == "active"`
# "filter": "status == \"active\""

يتم استبعاد الكيانات التي تكون قيمة status فيها NULL من النتائج.

الحقول القابلة للقيمة NULL والقيم الافتراضية

عندما يتم تكوين كل من nullable و default_value لحقل ما، تحدد القواعد التالية كيفية تعامل Milvus مع المدخلات NULL أو قيم الحقول المفقودة أثناء الإدراج.

تم تمكين القيم الفارغةالقيمة الافتراضيةمدخلات المستخدم (NULL أو محذوفة)النتيجة
نعمنعم (غير NULL)NULL أو تم تجاهلهايستخدم القيمة الافتراضية
نعملاNULL أو تم تجاهلهيتم تخزينه كـ NULL
لانعم (غير NULL)NULL أو محذوفيستخدم القيمة الافتراضية
لالاNULL أو تم حذفهيُحدث خطأً
لانعم (القيمة الافتراضية NULL)NULL أو تم حذفهيُحدث خطأً

النقاط الرئيسية:

  • عندما يكون للحقل قيمة افتراضية غير NULL، يتم استخدام تلك القيمة بغض النظر عما إذا كان الخيار " nullable " ممكّنًا أم لا.
  • عندما تكون ميزة " nullable=True " مفعّلة دون تعيين قيمة افتراضية، يخزن الحقل قيمة NULL.
  • عندما يتم تعيين " nullable=False " دون تحديد قيمة افتراضية، يفشل الإدراج ويظهر خطأ.
  • يُعد تعيين قيمة افتراضية NULL في حقل غير قابل للقيمة NULL غير صالح ويؤدي إلى حدوث خطأ.

للاطلاع على أمثلة كاملة واستخدام واجهة برمجة التطبيقات (API) للقيم الافتراضية، راجع القيم الافتراضية.