Поля, допускающие значение NULL

Milvus поддерживает поля, допускающие значение NULL, что позволяет не указывать значение поля или явно установить его равным NULL. Возможность установки значения NULL определяется на уровне схемы и последовательно применяется при импорте данных, индексировании, поиске и выполнении запросов.

Используйте поля, допускающие значение 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. Дополнительные сведения см. в разделе «Использование ключа разбиения».

Что такое поле, допускающее значение NULL?

В Milvus возможность хранения значения NULL в поле контролируется атрибутом поля на уровне схемы, называемым « nullable ».

Если поле определено с атрибутом ` nullable=True`, Milvus допускает отсутствие значения поля при загрузке данных. На практике Milvus рассматривает следующие два входных значения как эквивалентные и сохраняет значение поля как `NULL`:

  • Поле опущено в входной сущности.
  • Поле явно установлено в значение NULL (например, ` None ` в Python).

Если поле не определено как допускающее значение NULL (поведение по умолчанию), каждая сущность должна предоставлять допустимое значение для этого поля. Пропуск поля или явное присвоение ему значения NULL приведет к сбою операции вставки или импорта.

Атрибут «nullable» поддерживается как для скалярных, так и для векторных полей в схеме коллекции. Начиная с версии Milvus 3.0.0, он также поддерживается для родительского поля StructArray. Не настраивайте подполя Struct как допускающие NULL отдельно; определите возможность принятия значения NULL на родительском поле StructArray, и Milvus внутренне распространит этот параметр на его подполя.

Возможность принятия значения NULL определяет, может ли значение поля отсутствовать; она не определяет, какое значение используется, когда поле отсутствует.

  • Если поле с возможностью принятия значения NULL настроено без значения по умолчанию, пропуск поля приводит к сохранению значения NULL.
  • Если задано значение по умолчанию, Milvus может вместо этого сохранить значение по умолчанию. Подробности см. в разделе «Значения по умолчанию».

Определение поля, допускающего нулевые значения, в схеме коллекции

Чтобы использовать поля, допускающие значение null, необходимо включить атрибут «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

Скалярные поля также можно определить как допускающие значение 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 во время импорта данных.

В приведенном ниже примере в коллекцию, созданную в разделе «Определение поля, допускающего значение 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}
    ]
  }'

В этом примере:

  • Entity id = 1 предоставляет допустимое векторное значение.
  • Сущность с id = 2 явно присваивает значение NULL полю « embedding ».
  • Сущность с 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, Milvus учитывает только сущности со значениями, отличными от NULL, для поля, используемого в поиске. Сущности, векторное поле которых равно NULL, автоматически пропускаются.

Для векторного поля, допускающего значение NULL, такого как « embedding » в данном примере:

  • Оцениваются и ранжируются только сущности с допустимыми векторными значениями.
  • Энтитеты с векторами, равными NULL, не вызывают ошибок.
  • Если количество допустимых векторов меньше, чем запрашиваемое значение topK (limit), Milvus может вернуть меньше результатов, чем limit.

В следующем примере выполняется векторный поиск по полю embedding, допускающему значение NULL:

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.
  • Сущности со значениями ` 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 для значений по умолчанию см. в разделе «Значения по умолчанию».