Campos anuláveis
O Milvus suporta campos anuláveis, que permitem que um valor de campo esteja em falta ou seja explicitamente definido como NULL. A nulidade é definida ao nível do esquema e aplica-se de forma consistente nas operações de ingestão, indexação, pesquisa e consulta de dados.
Utilize campos anuláveis quando:
- Os dados são ingeridos a partir de sistemas externos que permitem valores ausentes.
- Alguns metadados são opcionais ou só estão disponíveis para parte do conjunto de dados.
- As incorporações de vetor são geradas de forma assíncrona e inseridas posteriormente.
Limites
Os campos vetoriais que permitem valores NULL não suportam expressões de filtro
IS NULLouIS NOT NULL. Não é possível filtrar explicitamente entidades com base no facto de um valor de campo de vetor ser NULL.Os camposArray of Structs não suportam valores NULL. Não é possível marcar um campo de Matriz de Estruturas ou qualquer campo aninhado dentro dele como anulável.
O atributo nullable é definido quando um campo é criado e não pode ser modificado posteriormente. Não é possível ativar ou desativar a anulabilidade de um campo existente.
Os campos marcados como anuláveis não podem ser utilizados como chaves de partição. Os campos de chave de partição devem sempre conter valores válidos e não nulos. Para obter mais informações, consulte Usar chave de partição.
O que é um campo anulável?
No Milvus, o facto de um campo poder armazenar um valor NULL é controlado por um atributo de campo ao nível do esquema denominado nullable.
Quando um campo é definido com nullable=True, o Milvus permite que o valor do campo esteja em falta durante a ingestão de dados. Na prática, Milvus trata as duas entradas seguintes como equivalentes e armazena o valor do campo como NULL:
- O campo é omitido da entidade de entrada.
- O campo é explicitamente definido como NULL (por exemplo,
Noneem Python).
Se um campo não for definido como nullable (o comportamento padrão), cada entidade deve fornecer um valor válido para esse campo. A omissão do campo ou a atribuição explícita de um valor NULL fará com que a operação de inserção ou importação falhe.
O atributo nullable é suportado para campos escalares e vectoriais num esquema de coleção. Contudo, os campos Array of Structs não suportam o atributo nullable.
A anulabilidade determina se um valor de campo pode estar em falta; não define qual o valor utilizado quando um campo está em falta.
- Se um campo anulável for configurado sem um valor predefinido, a omissão do campo resulta num valor NULL armazenado.
- Se for configurado um valor por defeito, o Milvus pode armazenar o valor por defeito. Para obter detalhes, consulte Valores padrão.
Definir um campo anulável no esquema de coleção
Para utilizar campos anuláveis, é necessário ativar o atributo anulável ao definir o esquema da coleção.
Neste exemplo, o esquema de coleção define um campo vetorial denominado embedding com nullable=True. Isso permite que as entidades na coleção omitam o valor do vetor ou o definam explicitamente como NULL durante a ingestão de dados.
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
]
}
}"
Neste esquema:
- O campo
embeddingé explicitamente marcado como anulável. - As entidades podem omitir o campo
embeddingou atribuir-lhe um valor NULL durante a inserção. - A decisão de permitir valores NULL é fixada no momento da criação da coleção.
Para maior clareza, os exemplos seguintes centram-se num campo vetorial anulável (embedding). A definição de campos escalares anuláveis é opcional e não é necessária para seguir o resto deste guia.
Opcional: Definir um campo escalar anulável
Os campos escalares também podem ser definidos como anuláveis usando o mesmo atributo nullable e seguir as mesmas regras durante a ingestão. Por exemplo:
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 }
Comportamento de inserção com valores ausentes ou NULL
Quando um campo é definido como anulável no esquema de coleção, o Milvus permite que o valor do campo esteja em falta ou seja explicitamente definido como NULL durante a ingestão de dados.
O exemplo abaixo insere três entidades na coleção criada em Definir um campo anulável no esquema da coleção, demonstrando estes diferentes casos.
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}
]
}'
Neste exemplo:
- Entidade id = 1 fornece um valor de vetor válido.
- Entidade id = 2 atribui explicitamente um valor NULL ao campo
embedding. - Entidade id = 3 omite totalmente o campo
embedding; Milvus armazena-o como NULL.
Comportamento do índice em campos anuláveis
Depois de inserir dados, pode construir um índice num campo anulável como habitualmente. A principal diferença é como o Milvus trata os valores NULL durante a construção do índice:
- Apenas as entidades com valores não nulos são adicionadas ao índice.
- As entidades com valores NULL são ignoradas e não participam na construção do índice.
Para um campo vetorial nulo, isto significa que apenas as entidades com vectores válidos se tornam pesquisáveis por semelhança vetorial.
# 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"}'
Neste ponto:
- As entidades com valores de incorporação válidos são indexadas e estão prontas para pesquisa.
- As entidades cuja incorporação é NULL permanecem na coleção, mas não são incluídas no índice vetorial.
Comportamento de pesquisa com campos anuláveis
Quando se efectuam operações de pesquisa num campo anulável, o Milvus avalia apenas as entidades com valores não nulos para o campo utilizado na pesquisa. As entidades cujo campo vetorial é NULL são automaticamente ignoradas.
Para um campo vetorial anulável, como embedding neste exemplo:
- Apenas as entidades com valores vetoriais válidos são avaliadas e classificadas.
- As entidades com vectores NULL não causam erros.
- Se o número de vectores válidos for inferior ao solicitado
topK(limit), o Milvus pode devolver menos resultados do quelimit.
O exemplo seguinte executa uma pesquisa de vectores no campo de vectores anuláveis 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"]
}'
Nesta pesquisa:
- Apenas as entidades com valores
embeddingnão nulos são consideradas candidatas. - As entidades com valores NULL para
embeddingsão excluídas da avaliação. - O número de resultados devolvidos depende de quantos vectores válidos existem na coleção.
Implicações da consulta e da filtragem
Os exemplos anteriores centram-se nos campos vectoriais. Esta secção descreve como os valores NULL se comportam em expressões de filtro escalar.
Os campos escalares podem ser definidos com nullable=True e seguem as mesmas regras de ingestão que os campos vectoriais. No entanto, os valores escalares NULL são sempre avaliados como falsos em expressões de filtro.
Por exemplo, dado um campo escalar anulável age, o seguinte filtro seleciona entidades cuja idade é superior a 18 anos:
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"
As entidades em que age é NULL são excluídas dos resultados porque um valor NULL não satisfaz a condição de filtro.
Da mesma forma, as verificações de igualdade não correspondem a valores NULL. Por exemplo:
expr = 'status == "active"'
String filter = "status == \"active\"";
const expr = 'status == "active"';
filter := `status == "active"`
# "filter": "status == \"active\""
As entidades em que status é NULL são excluídas dos resultados.
Campos anuláveis e valores padrão
Quando nullable e default_value estão configurados para um campo, as regras seguintes determinam a forma como o Milvus trata a introdução de valores NULL ou valores de campo em falta durante a inserção.
| Campo anulável ativado | Valor por defeito | Entrada do utilizador (NULL ou omitido) | Resultado |
|---|---|---|---|
| Sim | Sim (não NULL) | Nulo ou omitido | Utiliza o valor por defeito |
| Sim | Não | NULL ou omitido | Armazenado como NULL |
| Não | Sim (não NULL) | NULL ou omitido | Utiliza o valor por defeito |
| Não | Não | NULL ou omitido | Lança um erro |
| Não | Sim (NULL por defeito) | NULL ou omitido | Lança um erro |
Principais conclusões:
- Quando um campo tem um valor padrão não NULL, esse valor é usado independentemente de
nullableestar ativado. - Quando
nullable=Truemas nenhum valor padrão é definido, o campo armazena NULL. - Quando
nullable=Falsee nenhum valor padrão é definido, a inserção falha com um erro. - A definição de um valor por defeito NULL num campo não anulável é inválida e provoca um erro.
Para exemplos completos e utilização da API para predefinições, consulte Valores predefinidos.