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