Redis SORT with Jedis

In this post we will talk about the Redis SORT command.

Redis provides the SORT command that we can use to retrieve or store sorted values from a LIST, SET or ZSET.

The simplest form we can use the command over a KEY like the example below:

SORT numbers_list

This will sort the values contained in the key and return them. The command sorts the values as numbers. So, lets say we have a list with the following values:

1, 110, 5

The command above will return

1 5 110

We can specify to sort the values using alphabetically using the ALPHA modifier. There are a number of modifiers. We will take a look at some of them in the examples below. The examples will use the Jedis api.

For our examples let’s consider that we have an idea management system. We have a list containing all the usernames from the system:

all:users [junior, francisco, ribeiro, user4]

And for every username there will be a hash containing the user’s information:

user:

user:junior
  - name: "Junior User"
  - num_ideas : "5"
  - email:"fjunior@email.com"

user:francisco
 - name: "Francisco User"
 - num_ideas: "4"
 - email: "francisco@email.com"

...

We can see a class that will populate redis for our example:

package br.com.xicojunior.redistest;

import java.util.HashMap;
import java.util.Map;

import redis.clients.jedis.Jedis;

public class App 
{

    public static Jedis jedis = new Jedis("localhost");   
    public static void main( String[] args ){

        String names[] = new String[]{"junior", "francisco", "ribeiro", "user4"};
        for(String name: names){
            jedis.lpush("all:users", name);
        }
        addUserHash(names[0], "Junior User", "junior@junior.com", "5");
        addUserHash(names[1], "Francisco User", "francisco@francisco.com", "4");
        addUserHash(names[2], "Ribeiro User", "ribeiro@ribeiro.com", "3");
        addUserHash(names[3], "User 4", "user@user.com", "2");

        for(String name: names){
            System.out.println(jedis.hgetAll("user:".concat(name)));
        }

        System.out.println(jedis.lrange("all:users", 0, -1));



    }

    public static void addUserHash(String username, String name, String email, String numberOfIdeas){
        Map<String, String> userProp = new HashMap<String, String>();
        userProp.put("name",name);
        userProp.put("email", email);
        userProp.put("num_ideas", String.valueOf(numberOfIdeas));

        jedis.hmset("user:".concat(username), userProp);
    }
}

Let’s take a look at the code example below:

package br.com.xicojunior.redistest;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.SortingParams;

public class SortTest {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");

        //[1]sorting the usernames
        System.out.println(jedis.sort("all:users"));
        //[ribeiro, francisco, junior, user4]

        //[2]sorting the username alpha
        //jedis sort method receives a SortingParams instance for modifiers
        System.out.println(jedis.sort("all:users", new SortingParams().alpha()));
        //[francisco, junior, ribeiro, user4]

    }

}

In the example above we sort the key “all:users”. In the first try, it doesn’t seem to have sorted correctly because the default sorting considers numbers. On the second example, we use the ALPHA modifier. We can do this by using the overloaded version of the sort method. It receives an instance of SortingParams class. In this case we see the usernames being sorted correctly.

One nice feature of the SORT command is that we can sort the list using external values, values in other key(s). In the example below we will sort the all:users key by the number of ideas the user gave. It can be done using the “BY” modifier that receives the pattern of the keys to be used. Let’s see our example below:

package br.com.xicojunior.redistest;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.SortingParams;

public class SortTest {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");

        //[1] Sorting the usernames by the number of ideas
        System.out.println(jedis.sort("all:users", new SortingParams().by("user:*->num_ideas")));
        //[user4, ribeiro, francisco, junior]

        //[1] Sorting the usernames by the number of ideas DESC
        System.out.println(jedis.sort("all:users", new SortingParams().by("user:*->num_ideas").desc()));
    }

}

In this second example, we are sorting the usernames by an external value, in our case by the field “_numideas”. As in this case we are sorting by a hash field we used the following pattern “_user:->numideas”. With this pattern we are saying to look for the key “_user:” where this “*” will be replaced by the value from the list. As it is a hash we need to inform the field, we do this using the pattern “->fieldname_“. If we were sorting by a string key we could use the following pattern “_numideas*_” considering there was a key to store the number of ideas for each user.

In the first call it retrieved the values sorting them ASC, we can also tell redis to sort it _DESC _using the DESC modifier. With jedis BY and DESC are methods from SortingParams. As all methods returns the instance, we can chain all the callings and that makes easier to read the code.

With the _SORT _command we can also retrieve values from the external key or a field form an external hash. We can do this using the GET modifier, and we can use it many times. Let’s see some examples of this modifier below:

package br.com.xicojunior.redistest;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.SortingParams;

public class SortTest {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");

        //[1] Sorting the usernames by the number of ideas and retrieving the user name
        System.out.println(jedis.sort("all:users", new SortingParams().by("user:*->num_ideas").get("user:*->name")));
        //[User 4, Ribeiro User, Francisco User, Junior User]

        //[2] Retrieving the name and email
        System.out.println(jedis.sort("all:users", new SortingParams().by("user:*->num_ideas").get("user:*->name","user:*->email")));
        //[User 4, user@user.com, Ribeiro User, ribeiro@ribeiro.com, Francisco User, francisco@francisco.com, Junior User, junior@junior.com]


        //[3] Retrieve the value of the key being sorted - Special pattern #
        System.out.println(jedis.sort("all:users", new SortingParams().by("user:*->num_ideas").get("user:*->name","user:*->email","#")));
        //[User 4, user@user.com, user4, Ribeiro User, ribeiro@ribeiro.com, ribeiro, Francisco User, francisco@francisco.com, francisco, Junior User, junior@junior.com, junior]
    }

}

In the code above we can see the use of the GET modifier, in order to return a hash field we can use a pattern similar to the one we used in the BY modifier. In the first we return simply the name, as we said, we can use GET many times, in the second we retrieve the name and the email from the user. We can also, retrieve the value for the key that was sorted by using a special pattern “#”. The method get, receives a vararg so we can pass all the external keys we want to retrieve the value from.

Another thing we can do is to store the result from the sorting in a key. It is useful for cases when we want to cache the sort result, we can specify a dest key for the sort command. The result will be stored as a LIST.

package br.com.xicojunior.redistest;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.SortingParams;

public class SortTest {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");

        jedis.sort("all:users","dest_key1");

        System.out.println(jedis.lrange("dest_key1", 0, -1));
        //[ribeiro, francisco, junior, user4]

        jedis.sort("all:users", new SortingParams().alpha().desc(), "dest_key2");

        System.out.println(jedis.lrange("dest_key2", 0, -1));
        //[user4, ribeiro, junior, francisco]
    }

}

One very useful feature of the SORT command is that we can use it only to get values from related keys. There is a modifier indicating to do not the sort NOSORT

package br.com.xicojunior.redistest;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.SortingParams;

public class SortTest {

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");

        System.out.println(jedis.sort("all:users", new SortingParams().get("user:*->name","user:*->email").nosort()));
        //[User 4, user@user.com, Ribeiro User, ribeiro@ribeiro.com, Francisco User, francisco@francisco.com, Junior User, junior@junior.com]

    }

}

This piece of code basically retrieves the name and email for all users. In case we don’t use SORT command, we would need at least two commands to do the same:

LRANGE all:users 0 -1 //TO get all usernames

and then for each username call hmget for each one like below

HMGET user:junior name email //TO get the name and email from a user

We can find the command documentation in the redis site.

That’s it for today.

See you in the next post.