Redis ZADD: update only when score is lower - redis

I need to store the lowest score for each key I add to the set, but when I do ZADD, Redis overwrites the score with the new value even if the score is higher.
ZADD myorderset 1 'one' 2 'two' 3 'three'
(integer) 3
ZRANGE myorderset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
ZADD myorderset 5 'three'
(integer) 0
ZRANGE myorderset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "5"
In the example case, I need the key 'three' not be updated since the new score is higher (5) than the existing (3). Is there a way to do this natively or do I need to create a script in Lua?
I've been researching ZADD modifiers (XX, NX, CH) but none of them do what I need.
Thank you very much!

A Lua script for this CAS use case would be the simplest and idiomatic solution:
127.0.0.1:6379> ZADD myorderset 1 'one' 2 'two' 3 'three'
(integer) 3
127.0.0.1:6379> EVAL "local s = redis.call('ZSCORE', KEYS[1], ARGV[2]) if not s or s > ARGV[1] then redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2]) end" 1 myorderset 5 'three'
(nil)
127.0.0.1:6379> ZRANGE myorderset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
127.0.0.1:6379> EVAL "local s = redis.call('ZSCORE', KEYS[1], ARGV[2]) if not s or s > ARGV[1] then redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2]) end" 1 myorderset 2 'three'
(nil)
127.0.0.1:6379> ZRANGE myorderset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "three"
4) "2"
5) "two"
6) "2"

There is no single command or command option to do it both. You can either use combination of ZSCORE with ZADD(in lua). Alternatively("It is/looks like over-engineered") you may use ZUNIONSTORE with the aggregate option MIN.
With the AGGREGATE option, it is possible to specify how the results of the union are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.
127.0.0.1:6379> ZADD base 1 a 2 b 5 c
(integer) 3
127.0.0.1:6379> ZADD new 3 c
(integer) 1
127.0.0.1:6379> ZUNIONSTORE base 2 base new AGGREGATE MIN
(integer) 3
127.0.0.1:6379> DEL new
(integer) 1
127.0.0.1:6379> ZRANGE base 0 -1 WITHSCORES
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
127.0.0.1:6379> ZADD new 5 b
(integer) 1
127.0.0.1:6379> ZUNIONSTORE base 2 base new AGGREGATE MIN
(integer) 3
127.0.0.1:6379> DEL new
(integer) 1
127.0.0.1:6379> ZRANGE base 0 -1 WITHSCORES
1) "a"
2) "1"
3) "b"
4) "2"
5) "c"
6) "3"
127.0.0.1:6379>
If you prefer,
You may generate new set name with random string in application level
Put EXPIRE to this new set, no need to DEL the new key manually after the ZUNIONSTORE, it will be expired eventually.
It can be done in MULTI/EXEC in a single transaction.

Related

Redis sort by score and date

I'm trying to strategies how to calculate position/rank based on score and date
I have a two pieces of data with the same score shown in the image below.
If i run the command
$ZADD usersScores 11 userId6
It will add it above userId5 which is not something I want
Is it possible to add it below userId1 maybe based on date?
This is my goal essentially where i would like to show the position based on the amount and the oldest would be ontop via date
Thanks!
Sorted Set ordering
Sorted sets are ordered by their scores first, and then lexigraphically by their members if the scores are equivalent. So in your case if you added the following keys:
127.0.0.1:6379> ZADD myset 11 user1
(integer) 1
127.0.0.1:6379> ZADD myset 11 user5
(integer) 1
127.0.0.1:6379> ZADD myset 24 user3
(integer) 1
127.0.0.1:6379> zadd myset 54 user2
(integer) 1
127.0.0.1:6379> zadd myset 11 user6
(integer) 1
and then did a ZRANGE, you'd get them in ascending order with the members ordered in alphabetical order:
127.0.0.1:6379> zrange myset 0 -1 WITHSCORES
1) "user1"
2) "11"
3) "user5"
4) "11"
5) "user6"
6) "11"
7) "user3"
8) "24"
9) "user2"
10) "54"
What it looks like, is your GUI is using, or are just passing in the rev argument to ZRANGE under the hood, which orders everything in reverse (scores descending, reverse alphabetical)
127.0.0.1:6379> ZRANGE myset 0 -1 WITHSCORES REV
1) "user2"
2) "54"
3) "user3"
4) "24"
5) "user6"
6) "11"
7) "user5"
8) "11"
9) "user1"
10) "11"
Second sort-by
There are a couple of ways you could handle the second sort by with a sorted set, let's assume you wanted to do this with a 32 bit unix timestamp
You could prepend the second sortby field to the member name so if user6's score came in at 1659579650, you'd just make the member name be 1659579650:user1, then when it orders it, it would do so lexigraphically
You could combine the score with the second, since 11 is the score, we could shift 11 32 bits to the left and add/or the timestamp to it, which comes out to 48,904,219,906, those will then order correctly, and then when you want to find the score, all you need to do is right-shift the score by 32 bits.

Reset value for key in redis

I am storing some values in redis like for key: 1 the value will be
{"counter":1,"counter1":2}
Now I need to reset value of counter while the counter1 should be remaining same.
To increase counter I am use the command SETEX mykey 60 redis .
But it will also reset the value of counter1. So is there any way I can reset one value for a single key.
Let me know if I need to add some more info.
Instead of string you may use hash, then it will be easy. you can increment by some other value, delete counter etc etc. Each key in your json will be hash field.
127.0.0.1:6379> hset mykey counter 1 counter1 2
(integer) 2
127.0.0.1:6379> hgetall mykey
1) "counter"
2) "1"
3) "counter1"
4) "2"
127.0.0.1:6379> hset mykey counter 25
(integer) 0
127.0.0.1:6379> hgetall mykey
1) "counter"
2) "25"
3) "counter1"
4) "2"
127.0.0.1:6379> HINCRBY mykey counter 15
(integer) 40
127.0.0.1:6379> hgetall mykey
1) "counter"
2) "40"
3) "counter1"
4) "2"
127.0.0.1:6379>

Can we add Value in Redis List with Expiration Time in C#?

I am using Redis queue and adding the data using ListLeftPush and reading data using ListRightPop. It works fine I am able to get the data. But what if data has not popped out? Can we delete old data? or Can we add Value in Redis List with Expiration Time?
How to add time limit for each value while using ListLeftPush command in C#?
It is not possible to add expire time for individual value for the sake of keeping redis simple and fast.
you can only add expire time for individual keys i.e in ur case it is for whole list.
No redis doesn't support that. Expiration is available only for the top level keys. The closes data type/solution for your case would be sorted sets.
You put your expiration time(timestamp) as score while adding to sorted set(ZADD)
Instead of LPOP you use ZPOPMAX to get "to be last expired" element.
Periodically you may use ZREMRANGEBYSCORE to remove expired elements.
For the demonstration i used smaller numbers as expiration dates.
127.0.0.1:6379> ZADD myset 15 "a"
(integer) 0
127.0.0.1:6379> ZADD myset 25 "b"
(integer) 0
127.0.0.1:6379> ZADD myset 35 "c" 45 "d" 55 "e"
(integer) 0
127.0.0.1:6379> ZRANGE myset 0 -1 WITHSCORES
1) "a"
2) "15"
3) "b"
4) "25"
5) "c"
6) "35"
7) "d"
8) "45"
9) "e"
10) "55"
127.0.0.1:6379> ZPOPMAX myset
1) "e"
2) "55"
127.0.0.1:6379> ZREMRANGEBYSCORE myset -1 15
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1
1) "b"
2) "c"
3) "d"
127.0.0.1:6379>

Merge Redis set members values using a pattern

I have a simple zset named pets:
redis-cli zadd pets 1 cat1
redis-cli zadd pets 1 cat2
redis-cli zadd pets 1 cat3
redis-cli zadd pets 1 rat1
redis-cli zadd pets 1 rat2
The value is always 1. Now the set state is redis-cli zrange pets 0 -1 withscores:
1) "cat1"
2) "1"
3) "cat2"
4) "1"
5) "cat3"
6) "1"
7) "rat2"
8) "1"
9) "rat1"
10) "1"
Is there a way to merge set members based on a pattern? I want to sum all the members with the name starting with a prefix, in my case the pet type, so as to end up with the following:
1) "cat"
2) "3"
3) "rat"
4) "2"
I have tried zinterstore and zunionstore but they enforce having the same name for members of the sets.
Nope, there isn't a built-in Redis gimmick for that, but you could majik it with a Lua script (see the EVAL command).
That said, if what you need is count of pet by prefix, you should consider an alternative data model that can serve that.

Numerical operation in Redis sort

As in the doc http://redis.io/commands/sort
SORT mylist BY weight_*
What I would want to do is something like
SORT mylist BY (weight_* + vote_*)
Is that possible to do this just by Redis?
You can use Lua to build each sum_* key and sort by those:
redis 127.0.0.1:6379> sadd myset 1 2 3
(integer) 0
redis 127.0.0.1:6379> mset weight_1 1 weight_2 2 weight_3 3
OK
redis 127.0.0.1:6379> mset vote_1 1 vote_2 2 vote_3 0
OK
redis 127.0.0.1:6379> eval "for i in ipairs(redis.call('smembers', KEYS[1])) do redis.call('set', 'sum_' .. i, redis.call('get','weight_' .. i) + redis.call('get', 'vote_' .. i)) end;" 1 myset
(nil)
redis 127.0.0.1:6379> sort myset by sum_*
1) "1"
2) "3"
3) "2"