Querying with RedisVL#
In this notebook, we will explore more complex queries that can be performed withredisvl
Before running this notebook, be sure to
Have installed
redisvl
and have that environment active for this notebook.Have a running Redis instance with RediSearch > 2.4 running.
importpicklefromjupyterutilsimporttable_print,result_print# load in the example data and printing utilsdata=pickle.load(open("hybrid_example_data.pkl","rb"))table_print(data)
user | age | job | credit_score | office_location | user_embedding | last_updated |
---|---|---|---|---|---|---|
john | 18 | engineer | high | -122.4194,37.7749 | b'\xcd\xcc\xcc=\xcd\xcc\xcc=\x00\x00\x00?' | 1741627789 |
derrick | 14 | doctor | low | -122.4194,37.7749 | b'\xcd\xcc\xcc=\xcd\xcc\xcc=\x00\x00\x00?' | 1741627789 |
nancy | 94 | doctor | high | -122.4194,37.7749 | b'333?\xcd\xcc\xcc=\x00\x00\x00?' | 1710696589 |
tyler | 100 | engineer | high | -122.0839,37.3861 | b'\xcd\xcc\xcc=\xcd\xcc\xcc>\x00\x00\x00?' | 1742232589 |
tim | 12 | dermatologist | high | -122.0839,37.3861 | b'\xcd\xcc\xcc>\xcd\xcc\xcc>\x00\x00\x00?' | 1739644189 |
taimur | 15 | CEO | low | -122.0839,37.3861 | b'\x9a\x99\x19?\xcd\xcc\xcc=\x00\x00\x00?' | 1742232589 |
joe | 35 | dentist | medium | -122.0839,37.3861 | b'fff?fff?\xcd\xcc\xcc=' | 1742232589 |
schema={"index":{"name":"user_queries","prefix":"user_queries_docs","storage_type":"hash",# default setting -- HASH},"fields":[{"name":"user","type":"tag"},{"name":"credit_score","type":"tag"},{"name":"job","type":"text"},{"name":"age","type":"numeric"},{"name":"last_updated","type":"numeric"},{"name":"office_location","type":"geo"},{"name":"user_embedding","type":"vector","attrs":{"dims":3,"distance_metric":"cosine","algorithm":"flat","datatype":"float32"}}],}
fromredisvl.indeximportSearchIndex# construct a search index from the schemaindex=SearchIndex.from_dict(schema,redis_url="redis://localhost:6379")# create the index (no data yet)index.create(overwrite=True)
# use the CLI to see the created index!rvlindexlistall
13:00:56 [RedisVL] INFO Indices:13:00:56 [RedisVL] INFO 1. user_queries
# load data to rediskeys=index.load(data)
index.info()['num_docs']
7
Hybrid Queries#
Hybrid queries are queries that combine multiple types of filters. For example, you may want to search for a user that is a certain age, has a certain job, and is within a certain distance of a location. This is a hybrid query that combines numeric, tag, and geographic filters.
Tag Filters#
Tag filters are filters that are applied to tag fields. These are fields that are not tokenized and are used to store a single categorical value.
fromredisvl.queryimportVectorQueryfromredisvl.query.filterimportTagt=Tag("credit_score")=="high"v=VectorQuery(vector=[0.1,0.1,0.5],vector_field_name="user_embedding",return_fields=["user","credit_score","age","job","office_location","last_updated"],filter_expression=t)results=index.query(v)result_print(results)
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
# negationt=Tag("credit_score")!="high"v.set_filter(t)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
# use multiple tags as a listt=Tag("credit_score")==["high","medium"]v.set_filter(t)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
# use multiple tags as a set (to enforce uniqueness)t=Tag("credit_score")==set(["high","high","medium"])v.set_filter(t)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
What about scenarios where you might want to dynamically generate a list of tags? Have no fear. RedisVL allows you to do this gracefully without having to check for theempty case. Theempty case is when you attempt to run a Tag filter on a field with no defined values to match:
Tag("credit_score")==[]
An empty filter like the one above will yield a*
Redis query filter which implies the base case – there is no filter here to use.
# gracefully fallback to "*" filter if empty caseempty_case=Tag("credit_score")==[]v.set_filter(empty_case)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
Numeric Filters#
Numeric filters are filters that are applied to numeric fields and can be used to isolate a range of values for a given field.
fromredisvl.query.filterimportNumnumeric_filter=Num("age").between(15,35)v.set_filter(numeric_filter)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
# exact match querynumeric_filter=Num("age")==14v.set_filter(numeric_filter)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
# negationnumeric_filter=Num("age")!=14v.set_filter(numeric_filter)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
Timestamp Filters#
In redis all times are stored as an epoch time numeric however, this class allows you to filter with python datetime for ease of use.
fromredisvl.query.filterimportTimestampfromdatetimeimportdatetimedt=datetime(2025,3,16,13,45,39,132589)print(f'Epoch comparison:{dt.timestamp()}')timestamp_filter=Timestamp("last_updated")>dtv.set_filter(timestamp_filter)result_print(index.query(v))
Epoch comparison: 1742147139.132589
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
fromredisvl.query.filterimportTimestampfromdatetimeimportdatetimedt=datetime(2025,3,16,13,45,39,132589)print(f'Epoch comparison:{dt.timestamp()}')timestamp_filter=Timestamp("last_updated")<dtv.set_filter(timestamp_filter)result_print(index.query(v))
Epoch comparison: 1742147139.132589
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
fromredisvl.query.filterimportTimestampfromdatetimeimportdatetimedt_1=datetime(2025,1,14,13,45,39,132589)dt_2=datetime(2025,3,16,13,45,39,132589)print(f'Epoch between:{dt_1.timestamp()} -{dt_2.timestamp()}')timestamp_filter=Timestamp("last_updated").between(dt_1,dt_2)v.set_filter(timestamp_filter)result_print(index.query(v))
Epoch between: 1736880339.132589 - 1742147139.132589
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
Text Filters#
Text filters are filters that are applied to text fields. These filters are applied to the entire text field. For example, if you have a text field that contains the text “The quick brown fox jumps over the lazy dog”, a text filter of “quick” will match this text field.
fromredisvl.query.filterimportText# exact match filter -- document must contain the exact word doctortext_filter=Text("job")=="doctor"v.set_filter(text_filter)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
# negation -- document must not contain the exact word doctornegate_text_filter=Text("job")!="doctor"v.set_filter(negate_text_filter)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
# wildcard match filterwildcard_filter=Text("job")%"doct*"v.set_filter(wildcard_filter)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
# fuzzy match filterfuzzy_match=Text("job")%"%%engine%%"v.set_filter(fuzzy_match)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
# conditional -- match documents with job field containing engineer OR doctorconditional=Text("job")%"engineer|doctor"v.set_filter(conditional)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
# gracefully fallback to "*" filter if empty caseempty_case=Text("job")%""v.set_filter(empty_case)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
Use raw query strings as input. Below we use the~
flag to indicate that the full text query is optional. We also choose the BM25 scorer and return document scores along with the result.
v.set_filter("(~(@job:engineer))")v.scorer("BM25").with_scores()index.query(v)
[{'id': 'user_queries_docs:01JY4J5VC91SV4C91BM4D0FCV2', 'score': 0.9090908893868948, 'vector_distance': '0', 'user': 'john', 'credit_score': 'high', 'age': '18', 'job': 'engineer', 'office_location': '-122.4194,37.7749', 'last_updated': '1741627789'}, {'id': 'user_queries_docs:01JY4J5VC90DRSFJ0WKXXN49JT', 'score': 0.0, 'vector_distance': '0', 'user': 'derrick', 'credit_score': 'low', 'age': '14', 'job': 'doctor', 'office_location': '-122.4194,37.7749', 'last_updated': '1741627789'}, {'id': 'user_queries_docs:01JY4J5VC9QTPMCD60YP40Q6PW', 'score': 0.9090908893868948, 'vector_distance': '0.109129190445', 'user': 'tyler', 'credit_score': 'high', 'age': '100', 'job': 'engineer', 'office_location': '-122.0839,37.3861', 'last_updated': '1742232589'}, {'id': 'user_queries_docs:01JY4J5VC9FW7QQNJKDJ4Z7PRG', 'score': 0.0, 'vector_distance': '0.158808946609', 'user': 'tim', 'credit_score': 'high', 'age': '12', 'job': 'dermatologist', 'office_location': '-122.0839,37.3861', 'last_updated': '1739644189'}, {'id': 'user_queries_docs:01JY4J5VC940DJ9F47EJ6KN2MH', 'score': 0.0, 'vector_distance': '0.217882037163', 'user': 'taimur', 'credit_score': 'low', 'age': '15', 'job': 'CEO', 'office_location': '-122.0839,37.3861', 'last_updated': '1742232589'}, {'id': 'user_queries_docs:01JY4J5VC9D53KQD7ZTRP14KCE', 'score': 0.0, 'vector_distance': '0.266666650772', 'user': 'nancy', 'credit_score': 'high', 'age': '94', 'job': 'doctor', 'office_location': '-122.4194,37.7749', 'last_updated': '1710696589'}, {'id': 'user_queries_docs:01JY4J5VC9806MD90GBZNP0MNY', 'score': 0.0, 'vector_distance': '0.653301358223', 'user': 'joe', 'credit_score': 'medium', 'age': '35', 'job': 'dentist', 'office_location': '-122.0839,37.3861', 'last_updated': '1742232589'}]
Geographic Filters#
Geographic filters are filters that are applied to geographic fields. These filters are used to find results that are within a certain distance of a given point. The distance is specified in kilometers, miles, meters, or feet. A radius can also be specified to find results within a certain radius of a given point.
fromredisvl.query.filterimportGeo,GeoRadius# within 10 km of San Francisco officegeo_filter=Geo("office_location")==GeoRadius(-122.4194,37.7749,10,"km")v.set_filter(geo_filter)result_print(index.query(v))
score | vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|---|
0.4545454446934474 | 0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.4545454446934474 | 0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.4545454446934474 | 0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
# within 100 km Radius of San Francisco officegeo_filter=Geo("office_location")==GeoRadius(-122.4194,37.7749,100,"km")v.set_filter(geo_filter)result_print(index.query(v))
score | vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|---|
0.4545454446934474 | 0 | john | high | 18 | engineer | -122.4194,37.7749 | 1741627789 |
0.4545454446934474 | 0 | derrick | low | 14 | doctor | -122.4194,37.7749 | 1741627789 |
0.4545454446934474 | 0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.4545454446934474 | 0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.4545454446934474 | 0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.4545454446934474 | 0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 | 1710696589 |
0.4545454446934474 | 0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
# not within 10 km Radius of San Francisco officegeo_filter=Geo("office_location")!=GeoRadius(-122.4194,37.7749,10,"km")v.set_filter(geo_filter)result_print(index.query(v))
score | vector_distance | user | credit_score | age | job | office_location | last_updated |
---|---|---|---|---|---|---|---|
0.0 | 0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 | 1742232589 |
0.0 | 0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 | 1739644189 |
0.0 | 0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 | 1742232589 |
0.0 | 0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 | 1742232589 |
Combining Filters#
In this example, we will combine a numeric filter with a tag filter. We will search for users that are between the ages of 20 and 30 and have a job of “engineer”.
Intersection (“and”)#
t=Tag("credit_score")=="high"low=Num("age")>=18high=Num("age")<=100ts=Timestamp("last_updated")>datetime(2025,3,16,13,45,39,132589)combined=t&low&high&tsv=VectorQuery([0.1,0.1,0.5],"user_embedding",return_fields=["user","credit_score","age","job","office_location"],filter_expression=combined)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location |
---|---|---|---|---|---|
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 |
Union (“or”)#
The union of two queries is the set of all results that are returned by either of the two queries. The union of two queries is performed using the|
operator.
low=Num("age")<18high=Num("age")>93combined=low|highv.set_filter(combined)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location |
---|---|---|---|---|---|
0 | derrick | low | 14 | doctor | -122.4194,37.7749 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 |
Dynamic Combination#
There are often situations where you may or may not want to use a filter in agiven query. As shown above, filters will except theNone
type and revertto a wildcard filter essentially returning all results.
The same goes for filter combinations which enables rapid reuse of filters inrequests with different parameters as shown below. This removes the need fora number of “if-then” conditionals to test for the empty case.
defmake_filter(age=None,credit=None,job=None):flexible_filter=((Num("age")>age)&(Tag("credit_score")==credit)&(Text("job")%job))returnflexible_filter
# all parameterscombined=make_filter(age=18,credit="high",job="engineer")v.set_filter(combined)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location |
---|---|---|---|---|---|
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 |
# just age and credit_scorecombined=make_filter(age=18,credit="high")v.set_filter(combined)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location |
---|---|---|---|---|---|
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 |
# just agecombined=make_filter(age=18)v.set_filter(combined)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location |
---|---|---|---|---|---|
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 |
# no filterscombined=make_filter()v.set_filter(combined)result_print(index.query(v))
vector_distance | user | credit_score | age | job | office_location |
---|---|---|---|---|---|
0 | john | high | 18 | engineer | -122.4194,37.7749 |
0 | derrick | low | 14 | doctor | -122.4194,37.7749 |
0.109129190445 | tyler | high | 100 | engineer | -122.0839,37.3861 |
0.158808946609 | tim | high | 12 | dermatologist | -122.0839,37.3861 |
0.217882037163 | taimur | low | 15 | CEO | -122.0839,37.3861 |
0.266666650772 | nancy | high | 94 | doctor | -122.4194,37.7749 |
0.653301358223 | joe | medium | 35 | dentist | -122.0839,37.3861 |
Non-vector Queries#
In some cases, you may not want to run a vector query, but just use aFilterExpression
similar to a SQL query. TheFilterQuery
class enable this functionality. It is similar to theVectorQuery
class but solely takes aFilterExpression
.
fromredisvl.queryimportFilterQueryhas_low_credit=Tag("credit_score")=="low"filter_query=FilterQuery(return_fields=["user","credit_score","age","job","location"],filter_expression=has_low_credit)results=index.query(filter_query)result_print(results)
user | credit_score | age | job |
---|---|---|---|
derrick | low | 14 | doctor |
taimur | low | 15 | CEO |
Count Queries#
In some cases, you may need to use aFilterExpression
to execute aCountQuery
that simply returns the count of the number of entities in the pertaining set. It is similar to theFilterQuery
class but does not return the values of the underlying data.
fromredisvl.queryimportCountQueryhas_low_credit=Tag("credit_score")=="low"filter_query=CountQuery(filter_expression=has_low_credit)count=index.query(filter_query)print(f"{count} records match the filter expression{str(has_low_credit)} for the given index.")
2 records match the filter expression @credit_score:{low} for the given index.
Range Queries#
Range Queries are a useful method to perform a vector search where only results within a vectordistance_threshold
are returned. This enables the user to find all records within their dataset that are similar to a query vector where “similar” is defined by a quantitative value.
fromredisvl.queryimportRangeQueryrange_query=RangeQuery(vector=[0.1,0.1,0.5],vector_field_name="user_embedding",return_fields=["user","credit_score","age","job","location"],distance_threshold=0.2)# same as the vector query or filter queryresults=index.query(range_query)result_print(results)
vector_distance | user | credit_score | age | job |
---|---|---|---|---|
0 | john | high | 18 | engineer |
0 | derrick | low | 14 | doctor |
0.109129190445 | tyler | high | 100 | engineer |
0.158808946609 | tim | high | 12 | dermatologist |
We can also change the distance threshold of the query object between uses if we like. Here we will setdistance_threshold==0.1
. This means that the query object will return all matches that are within 0.1 of the query object. This is a small distance, so we expect to get fewer matches than before.
range_query.set_distance_threshold(0.1)result_print(index.query(range_query))
vector_distance | user | credit_score | age | job |
---|---|---|---|---|
0 | john | high | 18 | engineer |
0 | derrick | low | 14 | doctor |
Range queries can also be used with filters like any other query type. The following limits the results to only include records with ajob
ofengineer
while also being within the vector range (aka distance).
is_engineer=Text("job")=="engineer"range_query.set_filter(is_engineer)result_print(index.query(range_query))
vector_distance | user | credit_score | age | job |
---|---|---|---|---|
0 | john | high | 18 | engineer |
Advanced Query Modifiers#
See all modifier options available on the query API docs: https://docs.redisvl.com/en/latest/api/query.html
# Sort by a different field and change dialectv=VectorQuery(vector=[0.1,0.1,0.5],vector_field_name="user_embedding",return_fields=["user","credit_score","age","job","office_location"],num_results=5,filter_expression=is_engineer).sort_by("age",asc=False).dialect(3)result=index.query(v)result_print(result)
vector_distance | age | user | credit_score | job | office_location |
---|---|---|---|---|---|
0.109129190445 | 100 | tyler | high | engineer | -122.0839,37.3861 |
0 | 18 | john | high | engineer | -122.4194,37.7749 |
Raw Redis Query String#
Sometimes it’s helpful to convert these classes into their raw Redis query strings.
# check out the complex query from abovestr(v)
'@job:("engineer")=>[KNN 5 @user_embedding $vector AS vector_distance] RETURN 6 user credit_score age job office_location vector_distance SORTBY age DESC DIALECT 3 LIMIT 0 5'
t=Tag("credit_score")=="high"str(t)
'@credit_score:{high}'
t=Tag("credit_score")=="high"low=Num("age")>=18high=Num("age")<=100combined=t&low&highstr(combined)
'((@credit_score:{high} @age:[18 +inf]) @age:[-inf 100])'
The RedisVLSearchIndex
class exposes asearch()
method which is a simple wrapper around theFT.SEARCH
API.Provide any valid Redis query string.
results=index.search(str(t))forrinresults.docs:print(r.__dict__)
{'id': 'user_queries_docs:01JY4J5VC91SV4C91BM4D0FCV2', 'payload': None, 'user': 'john', 'age': '18', 'job': 'engineer', 'credit_score': 'high', 'office_location': '-122.4194,37.7749', 'user_embedding': '==\x00\x00\x00?', 'last_updated': '1741627789'}{'id': 'user_queries_docs:01JY4J5VC9D53KQD7ZTRP14KCE', 'payload': None, 'user': 'nancy', 'age': '94', 'job': 'doctor', 'credit_score': 'high', 'office_location': '-122.4194,37.7749', 'user_embedding': '333?=\x00\x00\x00?', 'last_updated': '1710696589'}{'id': 'user_queries_docs:01JY4J5VC9QTPMCD60YP40Q6PW', 'payload': None, 'user': 'tyler', 'age': '100', 'job': 'engineer', 'credit_score': 'high', 'office_location': '-122.0839,37.3861', 'user_embedding': '=>\x00\x00\x00?', 'last_updated': '1742232589'}{'id': 'user_queries_docs:01JY4J5VC9FW7QQNJKDJ4Z7PRG', 'payload': None, 'user': 'tim', 'age': '12', 'job': 'dermatologist', 'credit_score': 'high', 'office_location': '-122.0839,37.3861', 'user_embedding': '>>\x00\x00\x00?', 'last_updated': '1739644189'}
# Cleanupindex.delete()