NULL許可フィールド

MilvusはNULL許容フィールドをサポートしており、これによりフィールド値が欠落しているか、明示的にNULLに設定されることが可能になります。NULL許容性はスキーマレベルで定義され、データの取り込み、インデックス作成、検索、およびクエリ操作の全プロセスに一貫して適用されます。

以下の場合にNull許容フィールドを使用します:

  • 値が欠落していることを許容する外部システムからデータを取り込む場合。
  • 一部のメタデータがオプションであるか、データセットの一部でのみ利用可能な場合。
  • ベクトル埋め込みが非同期で生成され、後で挿入される場合。

制限事項

  • NULL値を許可するベクトルフィールドは、IS NULLIS NOT NULL のフィルタ式をサポートしていません。ベクトルフィールドの値がNULLであるかどうかに基づいて、エンティティを明示的にフィルタリングすることはできません。

  • Milvus 3.0.0 以降、親StructArrayフィールドは NULL 許容に設定できます。nullable=True は、個々のサブフィールドではなく、親 StructArray フィールドに設定してください。NULL は個々の Struct 要素ではなく StructArray フィールド全体に適用され、Milvus は親の NULL 許容性を内部的にそのサブフィールドに伝播します。 既存のコレクションに追加される StructArray フィールドは、既存のエンティティが新しいフィールドに対して NULL を返せるように、NULL 許容でなければなりません。詳細については、「StructArray の制限」を参照してください。

  • nullable属性はフィールドの作成時に定義され、事後の変更はできません。既存のフィールドに対してNULL許容性を有効または無効にすることはできません。

  • nullable としてマークされたフィールドは、パーティションキーとして使用できません。パーティションキーとなるフィールドには、常に有効で null ではない値が含まれている必要があります。詳細については、「パーティションキーの使用」を参照してください。

NULL 許容フィールドとは何ですか?

Milvus では、フィールドが NULL 値を格納できるかどうかは、nullable というスキーマレベルのフィールド属性によって制御されます。

フィールドが `nullable=True` で定義されている場合、Milvus はデータ取り込み時にそのフィールド値が欠落していることを許可します。実際には、Milvus は以下の 2 つの入力を同等とみなして、フィールド値を NULL として格納します:

  • 入力エンティティからそのフィールドが省略されている場合。
  • フィールドが明示的にNULLに設定されている場合(例:PythonでのNone )。

フィールドがNULL許可として定義されていない場合(デフォルトの動作)、すべてのエンティティはそのフィールドに対して有効な値を指定する必要があります。フィールドを省略したり、明示的にNULL値を割り当てたりすると、挿入またはインポート操作は失敗します。

nullable 属性は、コレクションスキーマ内のスカラーフィールドとベクトルフィールドの両方でサポートされています。Milvus 3.0.0 以降では、親 StructArray フィールドでもサポートされるようになりました。Struct サブフィールドを個別に nullable として設定しないでください。StructArray 親フィールドで nullability を定義すると、Milvus がその設定を内部的にサブフィールドに反映します。

NULL許容性は、フィールド値が欠落していてもよいかを決定するものであり、フィールドが欠落している場合にどの値が使用されるかを定義するものではありません。

  • Nullableフィールドがデフォルト値なしで設定されている場合、そのフィールドを省略すると、NULL値が格納されます。
  • デフォルト値が設定されている場合、Milvus はそのデフォルト値を代わりに格納することがあります。詳細については、「デフォルト値」を参照してください。

コレクションスキーマでヌル許容フィールドを定義する

Nullable フィールドを使用するには、コレクションスキーマを定義する際に 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 フィールドは明示的にNULL許容としてマークされています。
  • エンティティは、挿入時に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値の場合の挿入動作

コレクションスキーマでフィールドがNULL許容として定義されると、Milvusではデータ取り込み時に、そのフィールドの値が欠落しているか、明示的にNULLに設定されていることを許可します。

以下の例では、「コレクションスキーマでのヌル許容フィールドの定義」で作成したコレクションに3つのエンティティを挿入し、これらの異なるケースを示しています。

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

この例では:

  • Entityid = 1は有効なベクトル値を指定しています。
  • エンティティid = 2は、embedding フィールドに明示的にNULL値を割り当てています。
  • エンティティID = 3では、embedding フィールドを完全に省略しています。Milvus はこのフィールドを NULL として保存します。

NULL 許容フィールドにおけるインデックスの動作

データの挿入後、NULL 許容フィールドに対して通常通りインデックスを構築できます。主な違いは、インデックスの構築中に Milvus が NULL 値をどのように扱うかという点です:

  • 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許容フィールドでの検索動作

NULL 許容フィールドに対して検索操作を実行する場合、Milvus は検索に使用されたフィールドの値が NULL ではないエンティティのみを評価します。ベクトルフィールドが NULL のエンティティは自動的にスキップされます。

この例のembedding のようなNULL許容ベクトルフィールドの場合:

  • 有効なベクトル値を持つエンティティのみが評価され、ランク付けされます。
  • ベクトルがNULLのエンティティがあってもエラーは発生しません。
  • 有効なベクトルの数が、要求されたtopKlimit )よりも少ない場合、Milvusはlimit よりも少ない結果を返すことがあります。

次の例は、NULL 許容のベクトルフィールド `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であるエンティティは、評価の対象から除外されます。
  • 返される結果の数は、コレクション内に有効なベクトルがいくつ存在するかに依存します。

クエリおよびフィルタリングに関する注意点

これまでの例はベクトルフィールドに焦点を当てていました。このセクションでは、スカラーフィルタ式における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 または省略エラーが発生する

重要なポイント:

  • フィールドにNULL以外のデフォルト値が設定されている場合、nullable が有効かどうかにかかわらず、その値が使用されます。
  • nullable=True が有効で、デフォルト値が設定されていない場合、そのフィールドにはNULLが格納されます。
  • nullable=False が有効でデフォルト値が設定されていない場合、挿入はエラーとなり失敗します。
  • NULL ではないフィールドに NULL のデフォルト値を設定することは無効であり、エラーが発生します。

デフォルト値に関する完全な例および API の使用方法については、「デフォルト値」を参照してください。