Redis - ZRANGEBYLEX return empty set - redis

I have a set stored in redis like the following:
127.0.0.1:6379> zrange my_set 0 -1
1) "ABC20180108131627044829:XYZ20180108131627044857"
2) "ABC20180108131627044829:XYZ20180108131627044858"
3) "ABC20180108131627044829:XYZ20180108131627044859"
4) "ABC20180108131627044830:XYZ20180108131627044830"
5) "ABC20180108131627044830:XYZ20180108131627044831"
They were added to the set using
ZADD my_set 0 ABC20180108131627044829:XYZ20180108131627044857
ZADD my_set 0 ABC20180108131627044829:XYZ20180108131627044858
ZADD my_set 0 ABC20180108131627044829:XYZ20180108131627044859
ZADD my_set 0 ABC20180108131627044830:XYZ20180108131627044830
ZADD my_set 0 ABC20180108131627044830:XYZ20180108131627044831
I thought I can use the following to get back all the item contains ABC20180108131627044829, but I'm getting an empty list here.
127.0.0.1:6379> ZRANGEBYLEX my_set - [ABC20180108131627044829
(empty list or set)

Your looking for an autocomplete behavior. Here's a ZRANGEBYLEX query that will give you only elements that starts with a string:
ZRANGEBYLEX my_set [STRING [STRING\xff
And for your example:
ZRANGEBYLEX my_set [ABC20180108131627044829 [ABC20180108131627044829\xff
1) "ABC20180108131627044829:XYZ20180108131627044857"
2) "ABC20180108131627044829:XYZ20180108131627044858"
3) "ABC20180108131627044829:XYZ20180108131627044859"
Note, all scores must be equal.

You can't specify partial values in the limits of the ZRANGEBYLEX command, you have to input an entire string, but you can take advantage of lexicographical rules.
This would work:
ZRANGEBYLEX my_set [ABC20180108131627044829 [B
As you see the beginning of the interval seems a partial keyword, but in fact it's not: for example, ABCD comes after ABC in these rules, and B comes after ABC, so you have to tweak your criteria to adapt to this.
Also, a reminder: ZRANGEBYLEX works only for members which have same sorted set score!
EDIT
ZRANGEBYLEX my_set - (ABC20180108131627044830
should work for your example

Related

Redis - Sort and filter hash store using string attribute

I have a redis hash store that looks like Item:<id>, with attribute name. I want to filter the hash store by a prefix for name attribute.
What I'm trying to do is store the name (lowercased) in a separate Z-set called Item:::name while setting the score to 0. By doing this, I'm successfully able to get the desired result using ZRANGEBYLEX however I'm unable to map the results back to the original Items. How should I go about implementing something like this?
I've seen multiple autocomplete examples for Redis which require the same functionality but without linking the words back to an actual Item (hash in this case)
In sorted sets the member can't be duplicated, it has to be unique. So different users with the same name will cause problem.
My suggestion requires application layer coding to parse response array and executing hash commands (it will be like secondary indexes);
127.0.0.1:6379> HSET user:1 name jack
(integer) 1
127.0.0.1:6379> HSET user:2 name john
(integer) 1
127.0.0.1:6379> HSET user:3 name keanu
(integer) 1
127.0.0.1:6379> HSET user:4 name jack
(integer) 1
127.0.0.1:6379> ZADD item:names 0 jack::user:1 0 john::user:2 0 keanu::user:3 0 jack::user:4
(integer) 4
127.0.0.1:6379> ZRANGE item:names 0 -1 WITHSCORES
1) "jack::user:1"
2) "0"
3) "jack::user:4"
4) "0"
5) "john::user:2"
6) "0"
7) "keanu::user:3"
8) "0"
127.0.0.1:6379> ZRANGEBYLEX item:names [jack [jo
1) "jack::user:1"
2) "jack::user:4"
At the end you will have name::hash-key formatted array elements. At application layer if you separate each element to two substrings by using ::(any other string such as !!! or || etc) you will have user:1 and user:4.
127.0.0.1:6379> HGETALL user:1
1) "name"
2) "jack"
127.0.0.1:6379> HGETALL user:4
1) "name"
2) "jack"
127.0.0.1:6379>

Why I cannot use ZRANGEBYLEX on this sorted set in redis?

In the redis documentation, there is this example of ZRANGEBYLEX
ZADD myindex 0 0056:0028.44:90
ZADD myindex 0 0034:0011.00:832
ZRANGEBYLEX myindex [0056:0010.00 [0056:0030.00
1) "0056:0028.44:90"
It is very straightforward.
However if I want to apply the same technique to the following example,
127.0.0.1:6379> zadd feedbacks 1 feedback1 2 feedback2 3 feedback3 1 feedback4
(integer) 4
127.0.0.1:6379> ZRANGEBYLEX feedbacks [feed [feed
(empty list or set)
I get an empty set.
I would expect to see the four values (feedback1 to feedback4)
Why ZRANGEBYLEX failed on my test sample?
It fails because they have different scores. ZRANGEBYLEX works only on same-score subsets.
See https://redis.io/commands/ZRANGEBYLEX
When all the elements in a sorted set are inserted with the same
score, in order to force lexicographical ordering, this command
returns all the elements in the sorted set at key with a value between
min and max.
If the elements in the sorted set have different scores, the returned
elements are unspecified.
Sorted sets have the property of being lexicographically ordered within same-score subsets. This gives them a second use-case, a lexicographically sorted set, but in this case, you add all elements with the same score.
So, you have to choose how to use your sorted set:
Sorted by score (with same-score sorted lex, for predictable order)
Sorted lexicographically, all elements given the same score
You cannot have both. You'd need two sorted sets then.

Redis - ZRANGEBYSCORE with key matching a regex

I'm trying to get the value of the best key in a sorted set.
This is my query at the moment:
ZREVRANGEBYSCORE genre1 +inf -inf WITHSCORES LIMIT 0 1
This is an example of an add in my set:
ZADD "genre1|genre2|genre3" 3.25153 "film"
I'd like to use the query in a way like this
ZREVRANGEBYSCORE *genre1* +inf -inf WITHSCORES LIMIT 0 1
to match keys containing "...|genre1|..." and not only keys like "genre1".
Any help will be appreciated
This can be accomplished in two or three steps:
1) Use SCAN or KEYS to find the keys matching your pattern.
SCAN 0 MATCH "*genre1*"
1) "9"
2) 1) "genre1|genre2|genre3"
2) "genre1|genre4"
2) For each key, use TYPE to test if it is a Sorted Set. This is only important if you may have other genre1 keys on the db
TYPE "genre1|genre4"
zset
3) Run your ZREVRANGEBYSCORE <key> +inf -inf WITHSCORES LIMIT 0 1 for each key.
See this answer on how you can SCAN for a given type. You can modify the Lua script to include the ZREVRANGEBYSCORE and get your results atomically on a single call.
Finally, consider reviewing if storing the genre combinations is optimal in your case. You may use a sorted set per genre, and then use ZUNIONSTORE or ZINTERSTORE to get scored combinations.

In Redis, is it possible to predicate the inclusion of a member in the result of ZRANGE based on its existence in a different ZSET?

I'm tracking members in multiple Sorted Sets in Redis as a way to do multi-column indexing on the members. As an example, let's say I have two Sorted Sets, lastseen (which is epoch time) and points, and I store usernames as members in these Sorted Sets.
I'm wanting to use ZREVRANGEBYSCORE against lastseen to get a list of users who were recently seen, but I only want a user to be included in the results if its also present in the points Sorted Set.
I looked at using ZINTERSTORE to help me do this, but the available AGGREGATE options are not conducive to my scenario. If one of the AGGREGATE options was to use the score from a specific set provided to the ZINTERSTORE command, then it would work, but that's not an option.
---EDIT---
Example:
redis> ZADD lastseen 12345 "foo"
redis> ZADD lastseen 12346 "bar"
redis> ZADD lastseen 12347 "sucka"
redis> ZADD points 5 "foo"
redis> ZADD points 9 "bar"
Now, if I run:
redis> ZREVRANGEBYSCORE lastseen +inf -inf
1) "sucka"
2) "bar"
3) "foo"
What I'm looking for is a way to only get "foo" and "bar" returned by the call to ZREVRANGEBYSCORE for lastseen since "sucka" isn't resent in the points Sorted Set.
I can do:
redis> ZINTERSTORE out 2 lastseen points
redis> ZRANGE out 0 -1 WITHSCORES
1) "foo"
2) "12350"
3) "bar"
4) "12355"
As you can see, the ZINTERSTORE does cut out the "sucka" member, but the scores for the other two members get aggregated using SUM. There's two other aggregation options (MIN and MAX) available for use with ZINTERSTORE. In this case, using MAX would give me what I'm looking for, but that wouldn't be the case if a user's points were greater than the epoch value for the last time they were seen.
You can get what you're asking by setting the WEIGHT of points to 0 and of lastseen to 1 when running ZINTERSTORE so that only the weight of lastseen is considered:
redis> ZINTERSTORE out 2 lastseen points WEIGHTS 1 0
(integer) 2
Then, depending on what ordering you're going for, you can either:
redis> ZRANGE out 0 -1 WITHSCORES
1) "foo"
2) "12345"
3) "bar"
4) "12346"
or:
redis> ZREVRANGE out 0 -1 WITHSCORES
1) "bar"
2) "12346"
3) "foo"
4) "12345"

Redis: How to intersect a "normal" set with a sorted set?

Assume I have a set (or sorted set or list if that would be better) A of 100 to 1000 strings.
Then I have a sorted set B of many more strings, say one million.
Now C should be the intersection of A and B (of the strings of course).
I want to have every tuple (X, SCORE_OF_X_IN_B) where X is in C.
Any Idea?
I got two ideas:
Interstore
store A a sorted set with every score being 0
interstore to D
get every item of D
delete D
Simple loop in client
loop over A in my client programm
get zscore for every string
While 1. has way too much overhead on the redis side (Has to write for example. The redis page states quite a high time complexity, too http://redis.io/commands/zinterstore), 2. would have |A| database connections and won't be a good choice.
Maybe I could write a redis/lua script which will work like zscore but with an arbitrary number of strings, but I'm not sure if my hoster allows scripts...
So I just wanted to ask SO, if there is an elegant and fast solution available without scripting!
There is a simple solution to your problem: ZINTERSTORE will work with a SET and a ZSET. Try:
redis> sadd foo a
(integer) 1
redis> zadd bar 1 a
(integer) 1
redis> zadd bar 2 b
(integer) 1
redis> zinterstore baz 2 foo bar AGGREGATE MAX
(integer) 1
redis> zrange baz 0 -1 withscores
1) "a"
2) "1"
Edit: I added AGGREGATE MAX above, since redis will give each member of the (non-sorted) set foo a default score of 1, and SUM that with whatever score it has in the (sorted) set bar.