Nullfähige Felder

Milvus unterstützt nullfähige Felder, bei denen ein Feldwert fehlen oder explizit auf NULL gesetzt werden kann. Die Nullfähigkeit wird auf Schemaebene definiert und gilt einheitlich für alle Vorgänge der Datenerfassung, Indizierung, Suche und Abfrage.

Verwenden Sie nullfähige Felder, wenn:

  • Daten aus externen Systemen eingelesen werden, die fehlende Werte zulassen.
  • Bestimmte Metadaten optional sind oder nur für einen Teil des Datensatzes verfügbar sind.
  • Vektor-Einbettungen asynchron generiert und später eingefügt werden.

Einschränkungen

  • Vektorfelder, die NULL-Werte zulassen, unterstützen keine Filterausdrücke vom Typ „ IS NULL “ oder „ IS NOT NULL “. Sie können Entitäten nicht explizit danach filtern, ob der Wert eines Vektorfelds NULL ist.

  • Ab Milvus 3.0.0 kann das übergeordnete StructArray-Feld nullfähig sein. Legen Sie „ nullable=True “ für das übergeordnete StructArray-Feld fest, nicht für einzelne Unterfelder. „NULL“ gilt für das gesamte StructArray-Feld, nicht für ein einzelnes Struct-Element, und Milvus überträgt die Nullfähigkeit des übergeordneten Feldes intern auf dessen Unterfelder. Ein StructArray-Feld, das einer bestehenden Sammlung hinzugefügt wird, muss nullfähig sein, damit bestehende Entitäten für das neue Feld NULL zurückgeben können. Weitere Informationen finden Sie unter „StructArray-Beschränkungen“.

  • Das Attribut „nullfähig“ wird beim Anlegen eines Feldes festgelegt und kann anschließend nicht mehr geändert werden. Sie können die Nullfähigkeit für ein bestehendes Feld nicht aktivieren oder deaktivieren.

  • Als nullfähig markierte Felder können nicht als Partitionsschlüssel verwendet werden. Partitionsschlüsselfelder müssen immer gültige, nicht-nullwerte Werte enthalten. Weitere Informationen finden Sie unter „Partitionsschlüssel verwenden“.

Was ist ein nullfähiges Feld?

In Milvus wird durch ein Feldattribut auf Schemaebene namens „ nullable “ gesteuert, ob ein Feld einen NULL-Wert speichern darf.

Wenn ein Feld mit „ nullable=True “ definiert ist, lässt Milvus zu, dass der Feldwert bei der Dateneingabe fehlt. In der Praxis behandelt Milvus die folgenden beiden Eingaben als gleichwertig und speichert den Feldwert als NULL:

  • Das Feld wird in der Eingabeentität weggelassen.
  • Das Feld wird explizit auf NULL gesetzt (z. B. ` None ` in Python).

Wenn ein Feld nicht als nullfähig definiert ist (Standardverhalten), muss jede Entität einen gültigen Wert für dieses Feld bereitstellen. Das Weglassen des Feldes oder die explizite Zuweisung eines NULL-Werts führt dazu, dass der Einfüge- oder Importvorgang fehlschlägt.

Das Attribut „nullable“ wird sowohl für Skalar- als auch für Vektorfelder in einem Sammlungsschema unterstützt. Ab Milvus 3.0.0 wird es auch für das übergeordnete StructArray-Feld unterstützt. Konfigurieren Sie Struct-Unterfelder nicht separat als nullable; definieren Sie die Nullbarkeit auf dem übergeordneten StructArray, und Milvus überträgt diese Einstellung intern auf dessen Unterfelder.

Die Nullbarkeit legt fest, ob ein Feldwert fehlen darf; sie definiert jedoch nicht, welcher Wert verwendet wird, wenn ein Feld fehlt.

  • Wenn ein nullfähiges Feld ohne Standardwert konfiguriert ist, führt das Weglassen des Feldes dazu, dass ein NULL-Wert gespeichert wird.
  • Wenn ein Standardwert konfiguriert ist, speichert Milvus möglicherweise stattdessen den Standardwert. Weitere Informationen finden Sie unter „Standardwerte“.

Definieren Sie ein nullfähiges Feld im Sammlungsschema

Um nullfähige Felder zu verwenden, müssen Sie bei der Definition des Sammlungsschemas das Attribut „nullfähig“ aktivieren.

In diesem Beispiel definiert das Sammlungsschema ein Vektorfeld namens „ embedding “ mit der Eigenschaft „ nullable=True “. Dadurch können Entitäten in der Sammlung den Vektorwert weglassen oder ihn bei der Datenerfassung explizit auf NULL setzen.

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
      ]
    }
  }"

In diesem Schema:

  • Das Feld „ embedding “ ist explizit als nullfähig gekennzeichnet.
  • Entitäten können das Feld „ embedding “ weglassen oder ihm beim Einfügen einen NULL-Wert zuweisen.
  • Die Entscheidung, NULL-Werte zuzulassen, wird bei der Erstellung der Sammlung festgelegt.

Der Übersichtlichkeit halber konzentrieren sich die folgenden Beispiele auf ein nullfähiges Vektorfeld (embedding). Die Definition nullfähiger Skalarfelder ist optional und für die weitere Verwendung dieses Leitfadens nicht erforderlich.

Optional: Definieren eines nullfähigen Skalarfelds

Skalarfelder können ebenfalls mithilfe desselben Attributs „ nullable “ als nullfähig definiert werden und unterliegen bei der Erfassung denselben Regeln. Beispiel:

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 }

Einfügeverhalten bei fehlenden oder NULL-Werten

Sobald ein Feld im Sammlungsschema als nullfähig definiert ist, erlaubt Milvus, dass der Feldwert bei der Dateneingabe fehlt oder explizit auf NULL gesetzt wird.

Das folgende Beispiel fügt drei Entitäten in die unter „Ein nullfähiges Feld im Sammlungsschema definieren“ erstellte Sammlung ein und veranschaulicht diese verschiedenen Fälle.

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}
    ]
  }'

In diesem Beispiel:

  • Entity id = 1 liefert einen gültigen Vektorwert.
  • Entität mit ID = 2 weist dem Feld „ embedding “ explizit den Wert NULL zu.
  • Entität mit ID = 3 lässt das Feld „ embedding “ vollständig weg; Milvus speichert es als NULL.

Verhalten des Indexes bei Feldern, die NULL-Werte zulassen

Nach dem Einfügen von Daten können Sie wie gewohnt einen Index auf einem Feld erstellen, das NULL-Werte zulässt. Der wesentliche Unterschied besteht darin, wie Milvus NULL-Werte während der Indexerstellung behandelt:

  • Nur Entitäten mit Nicht-NULL-Werten werden in den Index aufgenommen.
  • Entitäten mit NULL-Werten werden übersprungen und sind nicht am Indexaufbau beteiligt.

Bei einem nullfähigen Vektorfeld bedeutet dies, dass nur Entitäten mit gültigen Vektoren anhand der Vektorähnlichkeit durchsuchbar sind.

# 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"}'

Zu diesem Zeitpunkt:

  • Entitäten mit gültigen Einbettungswerten sind indiziert und für die Suche bereit.
  • Entitäten, deren Einbettung NULL ist, verbleiben in der Sammlung, werden jedoch nicht in den Vektorindex aufgenommen.

Suchverhalten bei Feldern, die NULL-Werte zulassen

Wenn Sie Suchvorgänge für ein nullfähiges Feld durchführen, wertet Milvus nur Entitäten aus, deren Wert für das in der Suche verwendete Feld nicht NULL ist. Entitäten, deren Vektorfeld NULL ist, werden automatisch übersprungen.

Bei einem nullfähigen Vektorfeld wie beispielsweise „ embedding “ in diesem Beispiel gilt:

  • werden nur Entitäten mit gültigen Vektorwerten ausgewertet und in die Rangliste aufgenommen.
  • Entitäten mit NULL-Vektoren verursachen keine Fehler.
  • Wenn die Anzahl der gültigen Vektoren kleiner ist als die angeforderte Anzahl von „ topK “ (limit), gibt Milvus möglicherweise weniger Ergebnisse zurück als unter limit.

Das folgende Beispiel führt eine Vektorsuche auf dem nullfähigen Vektorfeld „ embedding “ durch:

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"]
  }'

Bei dieser Suche:

  • werden nur Entitäten mit Nicht-Null-Werten für „ embedding “ als Kandidaten berücksichtigt.
  • Entitäten mit NULL-Werten für ` embedding ` werden von der Auswertung ausgeschlossen.
  • Die Anzahl der zurückgegebenen Ergebnisse hängt davon ab, wie viele gültige Vektoren in der Sammlung vorhanden sind.

Auswirkungen auf Abfragen und Filterung

Die vorherigen Beispiele konzentrieren sich auf Vektorfelder. In diesem Abschnitt wird beschrieben, wie sich NULL-Werte in skalaren Filterausdrücken verhalten.

Skalarfelder können mit ` nullable=True ` definiert werden und unterliegen denselben Erfassungsregeln wie Vektorfelder. Skalare NULL-Werte werden in Filterausdrücken jedoch immer als „false“ ausgewertet.

Beispiel: Bei einem nullfähigen Skalarfeld „ age “ wählt der folgende Filter Entitäten aus, deren Alter größer als 18 ist:

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"

Entitäten, bei denen ` age ` den Wert `NULL` hat, werden aus den Ergebnissen ausgeschlossen, da ein `NULL`-Wert die Filterbedingung nicht erfüllt.

Ebenso stimmen Gleichheitsprüfungen nicht mit NULL-Werten überein. Beispiel:

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

Entitäten, bei denen „ status “ NULL ist, werden aus den Ergebnissen ausgeschlossen.

Nullfähige Felder und Standardwerte

Wenn für ein Feld sowohl „ nullable “ als auch „ default_value “ konfiguriert sind, bestimmen die folgenden Regeln, wie Milvus bei der Einfügung mit NULL-Eingaben oder fehlenden Feldwerten umgeht.

Null-Zulassung aktiviertStandardwertBenutzereingabe (NULL oder weggelassen)Ergebnis
JaJa (nicht NULL)NULL oder nicht angegebenVerwendet den Standardwert
JaNeinNULL oder ausgelassenWird als NULL gespeichert
NeinJa (nicht NULL)NULL oder ausgelassenVerwendet den Standardwert
NeinNeinNULL oder weggelassenLöst einen Fehler aus
NeinJa (Standardwert: NULL)NULL oder ausgelassenLöst einen Fehler aus

Wichtige Erkenntnisse:

  • Wenn ein Feld einen Nicht-NULL-Standardwert hat, wird dieser Wert unabhängig davon verwendet, ob „ nullable “ aktiviert ist.
  • Wenn „ nullable=True “ aktiviert ist, aber kein Standardwert festgelegt wurde, wird im Feld „NULL“ gespeichert.
  • Wenn „ nullable=False “ aktiviert ist und kein Standardwert festgelegt wurde, schlägt das Einfügen mit einer Fehlermeldung fehl.
  • Das Festlegen eines NULL-Standardwerts für ein nicht-NULL-fähiges Feld ist ungültig und führt zu einem Fehler.

Ausführliche Beispiele und Informationen zur Verwendung der API für Standardwerte finden Sie unter „Standardwerte“.