Aggregation and other more complex RediSearch queries
- Business Value Statement
- Modules Needed
- Vector Similarity Search
- Advanced Search Queries
- Aggregation
Redis provides the following additional advanced search capabilities to derive further value of Redis-held data:
- Vector Similarity Search - Store and search by ML-generated encodings of text and images
- Search + JSON Filtering - Combine the power of search with JSONPath filtering of search results
- Aggregation - Create processing pipelines of search results to extract analytic insights.
using StackExchange.Redis;
using NRedisStack;
using NRedisStack.RedisStackCommands;
using NRedisStack.Search;
using NRedisStack.Search.Literals.Enums;
using NRedisStack.Search.Aggregation;
db.HashSet("vec:1", new HashEntry[]
{
new("vector", (new float[] { 1f, 1f, 1f, 1f }).SelectMany(BitConverter.GetBytes).ToArray()),
new("tag", "A")
});
db.HashSet("vec:2", new HashEntry[]
{
new("vector", (new float[] { 2f, 2f, 2f, 2f }).SelectMany(BitConverter.GetBytes).ToArray()),
new("tag", "A")
});
db.HashSet("vec:3", new HashEntry[]
{
new("vector", (new float[] { 3f, 3f, 3f, 3f }).SelectMany(BitConverter.GetBytes).ToArray()),
new("tag", "B")
});
db.HashSet("vec:4", new HashEntry[]
{
new("vector", (new float[] { 4f, 4f, 4f, 4f }).SelectMany(BitConverter.GetBytes).ToArray()),
new("tag", "A")
});
SearchCommands ft = db.FT();
try {ft.DropIndex("vss_idx");} catch {};
Console.WriteLine(ft.Create("vss_idx", new FTCreateParams().On(IndexDataType.HASH).Prefix("vec:"),
new Schema()
.AddTagField("tag")
.AddVectorField("vector", VectorField.VectorAlgo.FLAT,
new Dictionary<string, object>()
{
["TYPE"] = "FLOAT32",
["DIM"] = "4",
["DISTANCE_METRIC"] = "L2"
}
)));
True
float[] vec = new[] { 2f, 3f, 3f, 3f};
var res = ft.Search("vss_idx",
new Query("*=>[KNN 2 @vector $query_vec]")
.AddParam("query_vec", vec.SelectMany(BitConverter.GetBytes).ToArray())
.SetSortBy("__vector_score")
.Dialect(2));
foreach (var doc in res.Documents) {
foreach (var item in doc.GetProperties()) {
if (item.Key == "__vector_score") {
Console.WriteLine($"id: {doc.Id}, score: {item.Value}");
}
}
}
id: vec:2, score: 2
id: vec:3, score: 2
float[] vec = new[] { 2f, 3f, 3f, 3f};
var res = ft.Search("vss_idx",
new Query("@tag:{A}=>[KNN 2 @vector $query_vec]")
.AddParam("query_vec", vec.SelectMany(BitConverter.GetBytes).ToArray())
.SetSortBy("__vector_score")
.Dialect(2));
foreach (var doc in res.Documents) {
foreach (var item in doc.GetProperties()) {
if (item.Key == "__vector_score") {
Console.WriteLine($"id: {doc.Id}, score: {item.Value}");
}
}
}
id: vec:2, score: 3
id: vec:4, score: 7
vec:3 is not returned because it has tag B
{
"city": "Boston",
"location": "42.361145, -71.057083",
"inventory": [
{
"id": 15970,
"gender": "Men",
"season":["Fall", "Winter"],
"description": "Turtle Check Men Navy Blue Shirt",
"price": 34.95
},
{
"id": 59263,
"gender": "Women",
"season": ["Fall", "Winter", "Spring", "Summer"],
"description": "Titan Women Silver Watch",
"price": 129.99
},
{
"id": 46885,
"gender": "Boys",
"season": ["Fall"],
"description": "Ben 10 Boys Navy Blue Slippers",
"price": 45.99
}
]
},
{
"city": "Dallas",
"location": "32.779167, -96.808891",
"inventory": [
{
"id": 51919,
"gender": "Women",
"season":["Summer"],
"description": "Nyk Black Horado Handbag",
"price": 52.49
},
{
"id": 4602,
"gender": "Unisex",
"season": ["Fall", "Winter"],
"description": "Wildcraft Red Trailblazer Backpack",
"price": 50.99
},
{
"id": 37561,
"gender": "Girls",
"season": ["Spring", "Summer"],
"description": "Madagascar3 Infant Pink Snapsuit Romper",
"price": 23.95
}
]
}
JsonCommands json = db.JSON();
json.Set("warehouse:1", "$", new {
city = "Boston",
location = "-71.057083, 42.361145",
inventory = new[] {
new {
id = 15970,
gender = "Men",
season = new[] {"Fall", "Winter"},
description = "Turtle Check Men Navy Blue Shirt",
price = 34.95
},
new {
id = 59263,
gender = "Women",
season = new[] {"Fall", "Winter", "Spring", "Summer"},
description = "Titan Women Silver Watch",
price = 129.99
},
new {
id = 46885,
gender = "Boys",
season = new[] {"Fall"},
description = "Ben 10 Boys Navy Blue Slippers",
price = 45.99
}
}
});
json.Set("warehouse:2", "$", new {
city = "Dallas",
location = "-96.808891, 32.779167",
inventory = new[] {
new {
id = 51919,
gender = "Women",
season = new[] {"Summer"},
description = "Nyk Black Horado Handbag",
price = 52.49
},
new {
id = 4602,
gender = "Unisex",
season = new[] {"Fall", "Winter"},
description = "Wildcraft Red Trailblazer Backpack",
price = 50.99
},
new {
id = 37561,
gender = "Girls",
season = new[] {"Spring", "Summer"},
description = "Madagascar3 Infant Pink Snapsuit Romper",
price = 23.95
}
}
});
SearchCommands ft = db.FT();
try {ft.DropIndex("wh_idx");} catch {};
Console.WriteLine(ft.Create("wh_idx", new FTCreateParams()
.On(IndexDataType.JSON)
.Prefix("warehouse:"),
new Schema().AddTextField(new FieldName("$.city", "city"))));
True
Find all inventory ids from all the Boston warehouse that have a price > $50.
foreach (var doc in ft.Search("wh_idx",
new Query("@city:Boston")
.ReturnFields(new FieldName("$.inventory[?(@.price>50)].id", "result"))
.Dialect(3))
.Documents.Select(x => x["result"]))
{
Console.WriteLine(doc);
}
[59263]
Find all inventory items in Dallas that are for Women or Girls
foreach (var doc in ft.Search("wh_idx",
new Query("@city:(Dallas)")
.ReturnFields(new FieldName("$.inventory[?(@.gender==\"Women\" || @.gender==\"Girls\")]", "result"))
.Dialect(3))
.Documents.Select(x => x["result"]))
{
Console.WriteLine(doc);
}
[{"id":51919,"gender":"Women","season":["Summer"],"description":"Nyk Black Horado Handbag","price":52.49},{"id":37561,"gender":"Girls","season":["Spring","Summer"],"description":"Madagascar3 Infant Pink Snapsuit Romper","price":23.95}]
{
"title": "System Design Interview",
"year": 2020,
"price": 35.99
},
{
"title": "The Age of AI: And Our Human Future",
"year": 2021,
"price": 13.99
},
{
"title": "The Art of Doing Science and Engineering: Learning to Learn",
"year": 2020,
"price": 20.99
},
{
"title": "Superintelligence: Path, Dangers, Stategies",
"year": 2016,
"price": 14.36
}
json.Set("book:1", "$", new {
title = "System Design Interview",
year = 2020,
price = 35.99
});
json.Set("book:2", "$", new {
title = "The Age of AI: And Our Human Future",
year = 2021,
price = 13.99
});
json.Set("book:3", "$", new {
title = "The Art of Doing Science and Engineering: Learning to Learn",
year = 2020,
price = 20.99
});
json.Set("book:4", "$", new {
title = "Superintelligence: Path, Dangers, Stategies",
year = 2016,
price = 14.36
});
Console.WriteLine(ft.Create("book_idx", new FTCreateParams()
.On(IndexDataType.JSON)
.Prefix("book:"),
new Schema().AddTextField(new FieldName("$.title", "title"))
.AddNumericField(new FieldName("$.year", "year"))
.AddNumericField(new FieldName("$.price", "price"))));
True
Find the total number of books per year
var request = new AggregationRequest("*").GroupBy("@year", Reducers.Count().As("count"));
var result = ft.Aggregate("book_idx", request);
for (var i=0; i<result.TotalResults; i++)
{
var row = result.GetRow(i);
Console.WriteLine($"{row["year"]}: {row["count"]}");
}
2021: 1
2020: 2
2016: 1
Sum of inventory dollar value by year
request = new AggregationRequest("*").GroupBy("@year", Reducers.Sum("@price").As("sum"));
result = ft.Aggregate("book_idx", request);
for (var i=0; i<result.TotalResults; i++)
{
var row = result.GetRow(i);
Console.WriteLine($"{row["year"]}: {row["sum"]}");
}
2021: 13.99
2020: 56.98
2016: 14.36