ヌル可能フィールド

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

以下のような場合は、NULL可能フィールドを使用してください:

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

制限

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

  • Array of Structsフィールドは NULL 値をサポートしません。Array of Structs フィールドや、その内部に入れ子になっているフィールドを NULLable としてマークすることはできません。

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

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

ヌル可能フィールドとは何ですか?

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

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

  • 入力エンティティからフィールドが省略されている。
  • フィールドが明示的にNULLに設定されている(例えば、PythonではNone )。

フィールドがNULL可能として定義されていない場合(デフォルトの動作)、すべてのエンティティはそのフィールドに有効な値を提供しなければなりません。フィールドを省略したり、明示的にNULL値を代入したりすると、挿入やインポート操作は失敗します。

nullable 属性は、コレクション・スキーマのスカラー・フィールドとベクトル・フィールドの両方でサポートされています。しかし、Array of Structs フィールドは nullable 属性をサポートしていません。

Nullable属性は、フィールドの値が欠落しているかどうかを決定します。

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

コレクションスキーマでのNULL可能フィールドの定義

NULL可能フィールドを使用するには、コレクションスキーマを定義するときにNULL可能属性を有効にする必要があります。

この例では、コレクション・スキーマはnullable=Trueembedding という名前のベクトル・フィールドを定義します。これにより、コレクション内のエンティティは、データ取り込み時にベクトル値を省略するか、明示的に 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可能なスカラー・フィールドの定義はオプションであり、このガイドの残りの部分に従う必要はありません。

オプション:ヌル可能なスカラー・フィールドの定義

スカラー・フィールドも、同じ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に設定することを許可します。

以下の例では、Define a nullable field in the collection schemaで作成したコレクションに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}
    ]
  }'

この例では

  • エンティティid = 1は有効なベクトル値を提供する。
  • エンティティID = 2はembedding フィールドに明示的にNULL値を割り当てています。
  • Milvus はembedding フィールドを 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フィールドでの検索動作

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

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

  • 有効なベクトル値を持つエンティティだけが評価され、ランク付けされます。
  • NULL ベクトルを持つエンティティはエラーになりません。
  • 有効なベクトルの数が要求されたtopK (limit) よりも少ない場合、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可能なフィールドとデフォルト値

nullabledefault_value の両方がフィールドに設定されている場合、Milvusが挿入時にNULL入力やフィールド値がない場合にどのように処理するかは以下のルールで決まります。

NULL可能デフォルト値ユーザ入力(NULLまたは省略)結果
はいはい(非NULL)NULLまたは省略デフォルト値を使用
はいなしNULLまたは省略NULLとして格納
いいえはい(非NULL)NULLまたは省略デフォルト値を使用
いいえいいえNULLまたは省略エラーをスローする
いいえあり(デフォルトNULL)NULLまたは省略エラーをスローする

重要なポイント

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

デフォルト値の完全な例とAPIの使用法については、デフォルト値を参照してください。