Index and query documents
Learn how to use Redis Search with JSON and hash documents.
This example shows how to create a search index for JSON documents and run queries against the index. It then goes on to show the slight differences in the equivalent code for hash documents.
redis-rb uses query dialect 2 by default.
Redis Search methods such as search()
will explicitly request this dialect, overriding the default set for the server.
See
Query dialects
for more information.Initialize
Make sure that you have Redis Open Source
or another Redis server available. Also install the
redis-rb client library if you
haven't already done so.
Require the redis gem. The Query Engine classes live under the
Redis::Commands::Search namespace, so the example below aliases it to
Search to keep the code concise.
require 'redis'
# A short alias for the Query Engine namespace to keep the code below readable.
Search = Redis::Commands::Search
require 'redis'
# A short alias for the Query Engine namespace to keep the code below readable.
Search = Redis::Commands::Search
user1 = {
'name' => 'Paul John',
'email' => '[email protected]',
'age' => 42,
'city' => 'London'
}
user2 = {
'name' => 'Eden Zamir',
'email' => '[email protected]',
'age' => 29,
'city' => 'Tel Aviv'
}
user3 = {
'name' => 'Paul Zamir',
'email' => '[email protected]',
'age' => 35,
'city' => 'Tel Aviv'
}
r = Redis.new
begin
r.ft_dropindex('idx:users', delete_documents: true)
rescue Redis::CommandError
# Index doesn't exist, so there is nothing to drop.
end
r.del('user:1', 'user:2', 'user:3')
schema = Search::Schema.build do
text_field '$.name', as: 'name'
tag_field '$.city', as: 'city'
numeric_field '$.age', as: 'age'
end
definition = Search::IndexDefinition.new(
prefix: ['user:'],
index_type: Search::IndexType::JSON
)
index = r.create_index('idx:users', schema, definition: definition)
puts index.name # >>> idx:users
user1_set = r.json_set('user:1', '$', user1)
user2_set = r.json_set('user:2', '$', user2)
user3_set = r.json_set('user:3', '$', user3)
puts [user1_set, user2_set, user3_set].inspect # >>> ["OK", "OK", "OK"]
find_paul_result = index.search('Paul @age:[30 40]')
puts find_paul_result.total # >>> 1
# The index has the key prefix `user:`, so the client returns the
# logical document id with that prefix removed.
find_paul_result.each { |doc| puts doc.id } # >>> 3
cities_query = Search::Query.new('Paul').return_field('$.city', as_field: 'city')
cities_result = index.search(cities_query)
cities_result.documents.sort_by(&:id).each do |doc|
puts "#{doc.id}: #{doc['city']}"
end
# >>> 1: London
# >>> 3: Tel Aviv
request = Search::AggregateRequest.new('*')
.group_by('@city', Search::Reducers.count.as('count'))
agg_result = index.aggregate(request)
agg_result.rows.sort_by { |row| row['city'] }.each do |row|
puts "#{row['city']} - #{row['count']}"
end
# >>> London - 1
# >>> Tel Aviv - 2
begin
r.ft_dropindex('hash-idx:users', delete_documents: true)
rescue Redis::CommandError
# Index doesn't exist, so there is nothing to drop.
end
r.del('huser:1', 'huser:2', 'huser:3')
hash_schema = Search::Schema.build do
text_field 'name'
tag_field 'city'
numeric_field 'age'
end
hash_definition = Search::IndexDefinition.new(
prefix: ['huser:'],
index_type: Search::IndexType::HASH
)
hash_index = r.create_index('hash-idx:users', hash_schema, definition: hash_definition)
puts hash_index.name # >>> hash-idx:users
huser1_set = r.hset('huser:1', user1)
huser2_set = r.hset('huser:2', user2)
huser3_set = r.hset('huser:3', user3)
puts [huser1_set, huser2_set, huser3_set].inspect # >>> [4, 4, 4]
find_paul_hash_result = hash_index.search('Paul @age:[30 40]')
puts find_paul_hash_result.total # >>> 1
find_paul_hash_result.each do |doc|
puts "#{doc.id}: #{doc['name']}, #{doc['city']}"
end
# >>> 3: Paul Zamir, Tel Aviv
r.close
Create data
Create some test data to add to the database:
user1 = {
'name' => 'Paul John',
'email' => '[email protected]',
'age' => 42,
'city' => 'London'
}
user2 = {
'name' => 'Eden Zamir',
'email' => '[email protected]',
'age' => 29,
'city' => 'Tel Aviv'
}
user3 = {
'name' => 'Paul Zamir',
'email' => '[email protected]',
'age' => 35,
'city' => 'Tel Aviv'
}
Add the index
Connect to your Redis database. The code below shows the most
basic connection but see the
redis-rb guide
to learn more about the available connection options.
r = Redis.new
Delete any existing index called idx:users and any keys that start with user:.
begin
r.ft_dropindex('idx:users', delete_documents: true)
rescue Redis::CommandError
# Index doesn't exist, so there is nothing to drop.
end
r.del('user:1', 'user:2', 'user:3')
Create an index. In this example, only JSON documents with the key prefix user: are indexed. For more information, see Query syntax.
Build the schema with the field type helpers (text_field, tag_field,
numeric_field) inside a Search::Schema.build block. Each field's first
argument is the JSON path to
the value, and the as: option gives the field an alias you can refer to in
queries.
schema = Search::Schema.build do
text_field '$.name', as: 'name'
tag_field '$.city', as: 'city'
numeric_field '$.age', as: 'age'
end
definition = Search::IndexDefinition.new(
prefix: ['user:'],
index_type: Search::IndexType::JSON
)
index = r.create_index('idx:users', schema, definition: definition)
puts index.name # >>> idx:users
Add the data
Add the three sets of user data to the database as
JSON objects.
If you use keys with the user: prefix then Redis will index the
objects automatically as you add them:
user1_set = r.json_set('user:1', '$', user1)
user2_set = r.json_set('user:2', '$', user2)
user3_set = r.json_set('user:3', '$', user3)
puts [user1_set, user2_set, user3_set].inspect # >>> ["OK", "OK", "OK"]
Query the data
You can now use the index to search the JSON objects. The
query
below searches for objects that have the text "Paul" in any field
and have an age value in the range 30 to 40:
find_paul_result = index.search('Paul @age:[30 40]')
puts find_paul_result.total # >>> 1
# The index has the key prefix `user:`, so the client returns the
# logical document id with that prefix removed.
find_paul_result.each { |doc| puts doc.id } # >>> 3
Because the index has the key prefix user:, the client strips that prefix
from the returned document IDs and reports the logical ID (for example, 3
rather than user:3).
Use a Search::Query object to specify query options, such as returning only
the city field:
cities_query = Search::Query.new('Paul').return_field('$.city', as_field: 'city')
cities_result = index.search(cities_query)
cities_result.documents.sort_by(&:id).each do |doc|
puts "#{doc.id}: #{doc['city']}"
end
# >>> 1: London
# >>> 3: Tel Aviv
Use an aggregation query to count all users in each city.
request = Search::AggregateRequest.new('*')
.group_by('@city', Search::Reducers.count.as('count'))
agg_result = index.aggregate(request)
agg_result.rows.sort_by { |row| row['city'] }.each do |row|
puts "#{row['city']} - #{row['count']}"
end
# >>> London - 1
# >>> Tel Aviv - 2
Differences with hash documents
Indexing for hash documents is very similar to JSON indexing but you need to specify some slightly different options.
When you create the schema for a hash index, you don't need to
add aliases for the fields, since you use the basic names to access
the fields anyway. Also, you must use Search::IndexType::HASH for the
index_type: option of the IndexDefinition when you create the index. The code
below shows these changes with a new index called hash-idx:users, which is
otherwise the same as the idx:users index used for JSON documents in the
previous examples.
First, delete any existing index called hash-idx:users and any keys that start with huser:.
begin
r.ft_dropindex('hash-idx:users', delete_documents: true)
rescue Redis::CommandError
# Index doesn't exist, so there is nothing to drop.
end
r.del('huser:1', 'huser:2', 'huser:3')
Now create the new index:
hash_schema = Search::Schema.build do
text_field 'name'
tag_field 'city'
numeric_field 'age'
end
hash_definition = Search::IndexDefinition.new(
prefix: ['huser:'],
index_type: Search::IndexType::HASH
)
hash_index = r.create_index('hash-idx:users', hash_schema, definition: hash_definition)
puts hash_index.name # >>> hash-idx:users
Use hset() to add the hash
documents instead of json_set().
huser1_set = r.hset('huser:1', user1)
huser2_set = r.hset('huser:2', user2)
huser3_set = r.hset('huser:3', user3)
puts [huser1_set, huser2_set, huser3_set].inspect # >>> [4, 4, 4]
The query commands work the same here for hash as they do for JSON (but
the name of the hash index is different). The results are returned as
Document objects, as with JSON:
find_paul_hash_result = hash_index.search('Paul @age:[30 40]')
puts find_paul_hash_result.total # >>> 1
find_paul_hash_result.each do |doc|
puts "#{doc.id}: #{doc['name']}, #{doc['city']}"
end
# >>> 3: Paul Zamir, Tel Aviv
More information
See the Redis Search docs for a full description of all query features with examples.