Unique value in redis list/set - redis

I want to make a list of existing products in redis but I want to check if the product name already exists first (duplicate checking).
My list currently accepts duplicates, so: Can anyone help me to show how to add unique value in list?

Instead of using a list, use a set. Sets are containers for unique objects. Each object can only appear once in a set. Take a look at the set-related commands here: http://redis.io/commands/#set.
And an example using redis-cli (we attempt to add "Product One" twice, but it only appears once in the list of products):
$ redis-cli
127.0.0.1:6379> sadd products "Product One"
(integer) 1
127.0.0.1:6379> sadd products "Product Two"
(integer) 1
127.0.0.1:6379> sadd products "Product Three"
(integer) 1
127.0.0.1:6379> sadd products "Product One"
(integer) 0
127.0.0.1:6379> smembers products
1) "Product Three"
2) "Product One"
3) "Product Two"
127.0.0.1:6379>

Why not just call Redis.lrem before? So if it finds any occurences of the item, removes them, otherwise will do nothing. Something like this:
def push_item_to_the_list(LIST_KEY, item)
Redis.lrem(LIST_KEY, 0, item)
Redis.lpush(LIST_KEY, item)
end

This is my (reckless) solution to keep Redis List unique.
(implementation in Ruby)
def push_item_to_the_list(LIST_KEY, item)
insert_status = Redis.linsert(LIST_KEY, 'before', item, item)
if insert_status == -1
Redis.lpush(LIST_KEY, item)
else
Redis.lrem(LIST_KEY, 1, item)
end
end
Each time when you want to push or insert item to your list, check if LINSERT command will be able to put this item just after the very same item (this is the only way I know to check if the item is already in the redis list or not).
If LINSERT will return status -1, it means that it was not able to find item in your list - everything is ok (you can push it or insert it now).
If LINSERT will return other value (size of the list in other case) - it means that it was able to find item already and it was able to insert another item, just after the previous one. It means that you have (at least one) duplication of your item. You can delete one of them now.

In case you need to maintain the order and the uniqueness you can use a sorted set
127.0.0.1:6379> zadd products 1 "Product One"
(integer) 1
127.0.0.1:6379> zadd products 2 "Product Two"
(integer) 1
127.0.0.1:6379> zadd products 3 "Product Tree"
(integer) 1
127.0.0.1:6379> zadd products 4 "Product Four"
(integer) 1
127.0.0.1:6379> zrange products 0 -1
1) "Product One"
2) "Product Two"
3) "Product Tree"
4) "Product Four"

I propose an option that creates a unique list, the elements of which do not lose their position. This option works much faster than lpush, lrem, rpop. At the same time, it supports functional of sadd, spop.
PHP code example:
public function addJobs(array $jobs): int
{
foreach ($jobs as $key => $job) {
$hash = hash('md4', $job);
if (0 === $this->client->hsetnx('QUEUE-HASHES', $hash, 1)) {
unset($jobs[$key]);
}
}
return $this->client->rpush('QUEUE', $jobs);
}
public function popJobs(int $count): array
{
if ($count < 1) {
throw new \InvalidArgumentException('Jobs count must be greater than zero');
}
$index = $count - 1;
$jobs = $this->client->lrange('QUEUE', 0, $index);
if (\count($jobs)) {
$this->client->ltrim('QUEUE', $count, -1);
$hashes = [];
foreach ($jobs as $job) {
$hashes[] = hash('md4', $job);
}
$this->client->hdel('QUEUE-HASHES', $hashes);
}
return $jobs;
}

redis 5 ZPOPMIN https://redis.io/commands/zpopmin
Removes and returns up to count members with the lowest scores in the sorted set stored at key.
When left unspecified, the default value for count is 1. Specifying a count value that is higher than the sorted set's cardinality will not produce an error. When returning multiple elements, the one with the lowest score will be the first, followed by the elements with greater scores.

Related

Is there any way to get Redis keys sorted by number of occurences?

I have this set of keys and values that I need to eventually sort by the number of keys' occurences. I'm aware that Redis isn't suppose to work like this, but hoping there may be some smart workaround*.
Schema requirements:
Allow each key to occur any number of times.
Have each value expire after a certain amount of time (and the key with it).
Keep each full pair unique.
Known constraints:
No inbuilt way to expire values, just keys.
Keys can't be duplicated even when they have different values (or can they?)
Using sets or other methods doesn't allow easy counting either (tried that too...)
So apparently the requirements can only be met by grouping both key and value in the Redis key (while assigning them with null/random values and ttls), like this...
Input keys:
"apples:123"
"oranges:123"
"bananas:456"
"apples:456"
"oranges:789"
"apples:789"
[then maybe another hundred or so such pairs]
Expected output:
apples, oranges, bananas
[or apples(3), oranges(2), bananas(1) – but I'll then ditch the numbers anyway.]
* while it can be done in app's logic, I think it loses in efficiency as it needs to get all data at once and cycle through each item, when all I need is a rather limited subset.
So right now I'd have to do it like this (node.js)...
client.keys('*').then(response => {
let occurences = {}
response.forEach(function (pair){
let fruit = pair.split(':')[0]
occurences[fruit] = (occurences[fruit] || 0) + 1
})
let topfruits = Object.keys(occurences).sort((a, b) => occurences[a] - occurences[b]).reverse().slice(0, 3)
console.log(topfruits)
})
// (client.scan in production, which makes it more complicated and doesn't help that much for this use case)
...migrating from a SQL query that does it in one line:
let topfruits = 'SELECT fruit, number, count (fruit) AS occurences FROM fruits GROUP BY fruit ORDER BY occurences DESC LIMIT 3'
This is a great Redisearch Aggregation problem
you can have multiple rows of fruits and counts
They can be expired by using TTLs (EXPIRE command)
These can all be unique (I used order# but it could be a UUID or some other generated informatio)
127.0.0.1:6379> FT.CREATE fruitIndex ON HASH PREFIX 1 fruit_order: SCHEMA fruit TEXT quantity NUMERIC
OK
127.0.0.1:6379> HSET fruit_order:100 fruit bananas quantity 2
(integer) 2
127.0.0.1:6379> HSET fruit_order:101 fruit bananas quantity 200
(integer) 2
127.0.0.1:6379> HSET fruit_order:103 fruit apples quantity 12
(integer) 2
127.0.0.1:6379> FT.AGGREGATE fruitIndex "*" GROUPBY 1 #fruit REDUCE SUM 1 quantity as totals SORTBY 2 #totals DESC
1) (integer) 3
2) 1) "fruit"
2) "bananas"
3) "totals"
4) "202"
3) 1) "fruit"
2) "oranges"
3) "totals"
4) "25"
4) 1) "fruit"
2) "apples"
3) "totals"
4) "12"
127.0.0.1:6379> EXPIRE fruit_order:101 5
(integer) 1
## Wait 5 seconds and re-run the query and you can see that order drop out
127.0.0.1:6379> FT.AGGREGATE fruitIndex "*" GROUPBY 1 #fruit REDUCE SUM 1 quantity as totals SORTBY 2 #totals DESC
1) (integer) 3
2) 1) "fruit"
2) "oranges"
3) "totals"
4) "25"
3) 1) "fruit"
2) "apples"
3) "totals"
4) "12"
4) 1) "fruit"
2) "bananas"
3) "totals"
4) "2"

Redis - Check is a given set of ids are part of a redis list/hash

I have a large set of ids (around 100000) which I want to store in redis.
I am looking for the most optimal way through which I can check if a given list of ids, what are the ids that are part of my set.
If I use a redis set, I can use SISMEMBER to check if a id is part of my set, but in this case I want to check if, given a list of ids, which one is part of my set.
Example:
redis> SADD myset "1"
(integer) 1
redis> SADD myset "2"
(integer) 2
redis> MYCOMMAND myset "[1,2,4,5]"
(list) 1, 2
Does anything of this sort exist already ?
thanks !

How to get DIFF on sorted set

How do I get most weighted elements from a sorted set, but excluding those found in another set(or list or hash).
>zadd all 1 one
>zadd all 2 two
>zadd all 3 three
>sadd disabled 2
>sdiff all disabled
(error) WRONGTYPE Operation against a key holding the wrong kind of value
Is my only option is to get elements from the sorted set one-by-one and compare to the list of "disabled" items? Wouldn't that be very slow because of so many transactions to a server?
What is the approach here?
Note: I assume you've meant sadd disabled two
As you've found out, SDIFF does not operate on sorted sets - that is because defining the difference between sorted sets isn't trivial.
What you could do is first create a temporary set with ZUNIONSTORE and set the intersect's scores to 0. Then do a range excluding the 0, e.g.:
127.0.0.1:6379> ZADD all 1 one 2 two 3 three
(integer) 3
127.0.0.1:6379> SADD disabled two
(integer) 1
127.0.0.1:6379> ZUNIONSTORE tmp 2 all disabled WEIGHTS 1 0 AGGREGATE MIN
(integer) 3
127.0.0.1:6379> ZREVRANGEBYSCORE tmp +inf 1 WITHSCORES
1) "three"
2) "3"
3) "one"
4) "1"

ordered sets in redis: random output in case of score ties

I have an ordered set in Redis (I am actually using a python client https://github.com/andymccurdy/redis-py), for example:
zadd myset 1 key1
zadd myset 1 key2
zadd myset 1 key3
zadd myset 0 key4
Note that 3 keys have the same score.
Using ZRANGE, i would like to get the top 2 entries (i.e lowest scores). "key4" will always be the first result as it has a lower value, but I would like the second return value to be randomly selected between the ties: key1,key2,key3. ZRANGE actually returns the keys in the order they are indexed: "keys1" is always my second result:
zrange myset 0 -1 WITHSCORES
1) "key4"
2) "0"
3) "key1"
4) "1"
5) "key2"
6) "1"
7) "key3"
8) "1"
any idea?
thanks,
J.
As kindly requested by Linus G Thiel, here are more details about my usecase:
I would like to use zsets to perform a simple ranking system. I have a list of items, for each one a score representing the relevance of the item. For the cold start of my system, most of the scores will be identical (i.e 0), and I would like to randomly select among the items having the same score. Otherwise I will always return the exact same lexicographic ordering, which will introduce a bias in the system.
The solution you propose, using one specific set for each duplicated score value will work. I will give it a try.
Thanks,

Redis relationships between data

I have a a list in redis containing a sequence of Ids. Each id is unique for a single object which I am storing as a JSON string on a separate key.
So I have something like:
redis> LRANGE mylist 0 -1
1) "one"
2) "two"
3) "three"
And I have separate keys mylist:one, mylist:two, mylist:three.
I am saving the ids to a list in order to build a simple FIFO queue on my application.
What is the most efficient way to get all the ids in mylist and their matching values from each individual key? Is there a better way to go about it?
The most efficient way is probably to use the SORT command:
# Populate list
rpush mylist one two three
set mylist:one 1
set mylist:two 2
set mylist:three 3
# Retrieve all items with their corresponding values
sort mylist by nosort get # get mylist:*
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"