Campos anulables
Milvus admite campos anulables, que permiten que un valor de campo falte o se establezca explícitamente como NULL. La anulabilidad se define a nivel de esquema y se aplica de forma coherente en todas las operaciones de ingesta, indexación, búsqueda y consulta de datos.
Utilice campos anulables cuando:
- Los datos se ingieren desde sistemas externos que permiten valores omitidos.
- Algunos metadatos son opcionales o sólo están disponibles para una parte del conjunto de datos.
- Las incrustaciones vectoriales se generan de forma asíncrona y se insertan posteriormente.
Límites
Los campos vectoriales que permiten valores NULL no admiten las expresiones de filtrado
IS NULLoIS NOT NULL. No es posible filtrar explícitamente entidades en función de si el valor de un campo vectorial es NULL.Los camposArray of Structs no admiten valores NULL. No se puede marcar un campo Array of Structs ni ningún campo anidado dentro de él como anulable.
El atributo nullable se define cuando se crea un campo y no puede modificarse posteriormente. No es posible activar o desactivar la anulabilidad de un campo existente.
Los campos marcados como anulables no pueden utilizarse como claves de partición. Los campos de clave de partición deben contener siempre valores válidos no nulos. Para más información, consulte Utilizar clave de partición.
¿Qué es un campo anulable?
En Milvus, la posibilidad de que un campo almacene un valor NULL está controlada por un atributo de campo a nivel de esquema llamado nullable.
Cuando un campo se define con nullable=True, Milvus permite que falte el valor del campo durante la ingestión de datos. En la práctica, Milvus trata las dos entradas siguientes como equivalentes y almacena el valor del campo como NULL:
- El campo se omite en la entidad de entrada.
- El campo se define explícitamente como NULL (por ejemplo,
Noneen Python).
Si un campo no está definido como anulable (el comportamiento por defecto), cada entidad debe proporcionar un valor válido para ese campo. Si se omite el campo o se le asigna explícitamente un valor NULL, la operación de inserción o importación fallará.
El atributo nullable se admite tanto para campos escalares como vectoriales en un esquema de colección. Sin embargo, los campos Array of Structs no admiten el atributo nullable.
La anulabilidad determina si puede faltar el valor de un campo; no define qué valor se utiliza cuando falta un campo.
- Si se configura un campo anulable sin un valor por defecto, al omitir el campo se almacena un valor NULL.
- Si se configura un valor por defecto, Milvus puede almacenar el valor por defecto en su lugar. Para más detalles, consulte Valores por defecto.
Definir un campo anulable en el esquema de la colección
Para utilizar campos anulables, debe activar el atributo anulable al definir el esquema de la colección.
En este ejemplo, el esquema de la colección define un campo vectorial denominado embedding con nullable=True. Esto permite a las entidades de la colección omitir el valor del vector o establecerlo explícitamente como NULL durante la ingestión de datos.
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
]
}
}"
En este esquema
- El campo
embeddingse marca explícitamente como anulable. - Las entidades pueden omitir el campo
embeddingo asignarle un valor NULL durante la inserción. - La decisión de permitir valores NULL se fija en el momento de la creación de la colección.
Para mayor claridad, los siguientes ejemplos se centran en un campo vectorial anulable (embedding). La definición de campos escalares anulables es opcional y no es necesaria para seguir el resto de esta guía.
Opcional: Definir un campo escalar anulable
Los campos escalares también pueden definirse como anulables utilizando el mismo atributo nullable y seguir las mismas reglas durante la ingesta. Por ejemplo
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 }
Comportamiento de la inserción con valores perdidos o NULL
Una vez que un campo se define como anulable en el esquema de la colección, Milvus permite que el valor del campo falte o se establezca explícitamente como NULL durante la ingesta de datos.
El siguiente ejemplo inserta tres entidades en la colección creada en Definir un campo anulable en el esquema de la colección, demostrando estos 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}
]
}'
En este ejemplo
- Entidad id = 1 proporciona un valor vectorial válido.
- Entity id = 2 asigna explícitamente un valor NULL al campo
embedding. - Entity id = 3 omite el campo
embeddingpor completo; Milvus lo almacena como NULL.
Comportamiento del índice en campos anulables
Después de insertar los datos, puede crear un índice en un campo anulable de la forma habitual. La diferencia clave es cómo maneja Milvus los valores NULL durante la construcción del índice:
- Sólo las entidades con valores no nulos se añaden al índice.
- Las entidades con valores NULL se omiten y no participan en la construcción del índice.
Para un campo vectorial anulable, esto significa que sólo las entidades con vectores válidos se pueden buscar por similitud vectorial.
# 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"}'
En este punto
- Las entidades con valores de incrustación válidos se indexan y están listas para la búsqueda.
- Las entidades cuya incrustación es NULL permanecen en la colección, pero no se incluyen en el índice vectorial.
Comportamiento de la búsqueda con campos anulables
Cuando se realizan operaciones de búsqueda en un campo anulable, Milvus sólo evalúa las entidades con valores no nulos para el campo utilizado en la búsqueda. Las entidades cuyo campo vectorial es NULL se omiten automáticamente.
Para un campo vectorial anulable como embedding en este ejemplo:
- Sólo se evalúan y clasifican las entidades con valores vectoriales válidos.
- Las entidades con vectores NULL no provocan errores.
- Si el número de vectores válidos es inferior al solicitado
topK(limit), Milvus puede devolver menos resultados quelimit.
El siguiente ejemplo realiza una búsqueda vectorial en el campo vectorial nulable 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"]
}'
En esta búsqueda
- Sólo se consideran candidatas las entidades con valores
embeddingno nulos. - Las entidades con valores NULL para
embeddingse excluyen de la evaluación. - El número de resultados devueltos depende de cuántos vectores válidos existan en la colección.
Implicaciones de la consulta y el filtrado
Los ejemplos anteriores se centran en los campos vectoriales. En esta sección se describe cómo se comportan los valores NULL en las expresiones de filtro escalares.
Los campos escalares pueden definirse con nullable=True y siguen las mismas reglas de ingestión que los campos vectoriales. Sin embargo, los valores escalares NULL siempre se evalúan como false en las expresiones de filtro.
Por ejemplo, dado un campo escalar nulable age, el siguiente filtro selecciona las entidades cuya edad es superior a 18 años:
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"
Las entidades en las que age es NULL se excluyen de los resultados porque un valor NULL no satisface la condición del filtro.
Del mismo modo, las comprobaciones de igualdad no coinciden con los valores NULL. Por ejemplo:
expr = 'status == "active"'
String filter = "status == \"active\"";
const expr = 'status == "active"';
filter := `status == "active"`
# "filter": "status == \"active\""
Las entidades en las que status es NULL se excluyen de los resultados.
Campos anulables y valores por defecto
Cuando tanto nullable como default_value están configurados para un campo, las siguientes reglas determinan cómo Milvus maneja la entrada NULL o los valores de campo faltantes durante la inserción.
| Campo nulo habilitado | Valor por defecto | Entrada del usuario (NULL u omitido) | Resultado |
|---|---|---|---|
| Sí | Sí (no NULL) | NULL u omitido | Utiliza el valor por defecto |
| Sí | No | NULL u omitido | Se almacena como NULL |
| No | Sí (no NULL) | NULL u omitido | Utiliza el valor por defecto |
| No | No | NULL u omitido | Lanza un error |
| No | Sí (NULL por defecto) | NULL u omitido | Lanza un error |
Puntos clave:
- Cuando un campo tiene un valor por defecto no NULL, ese valor se utiliza independientemente de si
nullableestá activado. - Cuando
nullable=Truepero no se establece un valor por defecto, el campo almacena NULL. - Cuando
nullable=Falsey no se establece ningún valor por defecto, la inserción falla con un error. - Establecer un valor por defecto NULL en un campo no anulable no es válido y provoca un error.
Para ver ejemplos completos y el uso de la API para valores por defecto, consulte Valores por defecto.