Campos nulos
O Milvus suporta campos nulos, o que permite que o valor de um campo esteja em falta ou seja explicitamente definido como NULL. A possibilidade de um campo ser nulo é definida ao nível do esquema e aplica-se de forma consistente em todas as operações de ingestão de dados, indexação, pesquisa e consulta.
Utilize campos nulos quando:
- Os dados forem introduzidos a partir de sistemas externos que permitem valores em falta.
- Alguns metadados forem opcionais ou estiverem disponíveis apenas para uma parte do conjunto de dados.
- As representações vetoriais são geradas de forma assíncrona e inseridas posteriormente.
Limitações
Os campos vetoriais que permitem valores NULL não suportam as expressões de filtro «
IS NULL» ou «IS NOT NULL». Não é possível filtrar explicitamente entidades com base no facto de o valor de um campo vetorial ser NULL.A partir do Milvus 3.0.0, o campo StructArray pai pode ser nulo. Defina «
nullable=True» no campo StructArray pai, e não em subcampos individuais. O valor NULL aplica-se a todo o campo StructArray, e não a um elemento Struct individual, e o Milvus propaga internamente a possibilidade de o campo pai ser nulo aos seus subcampos. Um campo StructArray adicionado a uma coleção existente deve ser nulo, para que as entidades existentes possam devolver NULL para o novo campo. Para mais detalhes, consulte Limites do StructArray.O atributo de nulabilidade é definido quando um campo é criado e não pode ser modificado posteriormente. Não é possível ativar ou desativar a nulabilidade para um campo existente.
Os campos marcados como nulos não podem ser utilizados como chaves de partição. Os campos de chave de partição devem conter sempre valores válidos e não nulos. Para mais informações, consulte Utilizar chave de partição.
O que é um campo que permite valores nulos?
No Milvus, a possibilidade de um campo armazenar um valor NULL é controlada 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, o 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, `
None` em Python).
Se um campo não for definido como nulo (comportamento predefinido), todas as entidades devem 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 tanto para campos escalares como para campos vetoriais num esquema de coleção. A partir do Milvus 3.0.0, também é suportado no campo pai StructArray. Não configure os subcampos Struct como nulos de forma independente; defina a nulabilidade no campo pai StructArray e o Milvus propagará essa configuração para os seus subcampos internamente.
A nulabilidade 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 nulo 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 poderá armazenar esse valor em vez do valor nulo. Para mais detalhes, consulte Valores por defeito.
Definir um campo nulo no esquema da coleção
Para utilizar campos nulos, deve ativar o atributo «nullable» ao definir o esquema da coleção.
Neste exemplo, o esquema da coleção define um campo vetorial denominado « embedding » com « nullable=True ». Isto 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» está explicitamente marcado como nulo. - As entidades podem omitir o campo «
embedding» ou atribuir-lhe um valor NULL durante a inserção. - A decisão de permitir valores NULL é definida no momento da criação da coleção.
Para maior clareza, os exemplos seguintes centram-se num campo vetorial nulo (embedding). A definição de campos escalares nulos é opcional e não é necessária para seguir o resto deste guia.
Opcional: Definir um campo escalar nulo
Os campos escalares também podem ser definidos como nulos utilizando o mesmo atributo ` nullable ` e seguem 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 em falta ou NULL
Assim que um campo for definido como nulo no esquema da 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 nulo 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:
- A entidade id = 1 fornece um valor vetorial válido.
- A entidade com id = 2 atribui explicitamente um valor NULL ao campo «
embedding». - A entidade com id = 3 omite totalmente o campo «
embedding»; o Milvus armazena-o como NULL.
Comportamento do índice em campos que podem conter valores NULL
Após a inserção de dados, pode criar um índice num campo nulo, como habitualmente. A principal diferença reside na forma como o Milvus lida com 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.
No caso de um campo vetorial nulo, isto significa que apenas as entidades com vetores válidos passam a ser 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"}'
Nesta fase:
- 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 da pesquisa com campos que podem conter valores nulos
Quando se realizam operações de pesquisa num campo que pode assumir o valor nulo, o Milvus avalia apenas as entidades com valores não nulos para o campo utilizado na pesquisa. As entidades cujo campo vetorial seja NULL são automaticamente ignoradas.
Para um campo vetorial nulo, como embedding neste exemplo:
- Apenas as entidades com valores vetoriais válidos são avaliadas e classificadas.
- As entidades com vetores NULL não causam erros.
- Se o número de vetores válidos for inferior ao número de resultados solicitado (
topK,limit), o Milvus poderá devolver menos resultados do quelimit.
O exemplo seguinte realiza uma pesquisa vetorial no campo vetorial nulo 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 não nulos em «
embedding» são consideradas candidatas. - As entidades com valores NULL para
embeddingsão excluídas da avaliação. - O número de resultados devolvidos depende do número de vetores válidos existentes na coleção.
Implicações nas consultas e na filtragem
Os exemplos anteriores centram-se nos campos vetoriais. Esta secção descreve como os valores NULL se comportam em expressões de filtro escalares.
Os campos escalares podem ser definidos com nullable=True e seguem as mesmas regras de ingestão que os campos vetoriais. No entanto, os valores escalares NULL são sempre avaliados como falsos nas expressões de filtro.
Por exemplo, dado um campo escalar que pode assumir o valor NULL age, o filtro seguinte seleciona entidades cuja idade seja superior a 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"
As entidades em que age é NULL são excluídas dos resultados, uma vez que um valor NULL não satisfaz a condição do 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 nulos e valores por predefinição
Quando tanto « nullable » como « default_value » estão configurados para um campo, as regras seguintes determinam como o Milvus lida com entradas NULL ou valores de campo em falta durante a inserção.
| Nullável ativado | Valor por defeito | Entrada do utilizador (NULL ou omitida) | Resultado |
|---|---|---|---|
| Sim | Sim (não NULL) | NULL ou omitido | Utiliza o valor predefinido |
| Sim | Não | NULL ou omitido | Armazenado como NULL |
| Não | Sim (não NULL) | NULL ou omitido | Utiliza o valor predefinido |
| Não | Não | NULL ou omitido | Lança um erro |
| Não | Sim (NULL por predefinição) | NULL ou omitido | Gera um erro |
Pontos-chave:
- Quando um campo tem um valor por defeito diferente de NULL, esse valor é utilizado independentemente de a opção «
nullable» estar ativada. - Quando a opção «
nullable=True» está ativada, mas não está definido nenhum valor por defeito, o campo armazena NULL. - Quando a opção «
nullable=False» está ativada, mas não está definido nenhum valor predefinido, a inserção falha com um erro. - Definir um valor predefinido NULL num campo não nulo é inválido e provoca um erro.
Para exemplos completos e informações sobre a utilização da API para valores por defeito, consulte Valores por defeito.