Question

I have a users table, with following format :

users(id,name.....,settings)

settings field is of type number and contains a bitmask of settings.

I have to support(inter alia) queries like :

  • find all users which have setting1, setting23, setting125

Today such a query looks like :

 select * from users where bit_and(settings,2^1+2^23+2^125) = 2^1+2^23+2^125

Of course it is not a perfect implementation, but it already works in this way a lot of time.

The problem is that today we have 126 different settings and it is exactly the limit of oracle 11g for bitwise operations. That means that we can't add new settings anymore.

I'm trying to find an alternative solution to this issue. The obvious way is instead of setting field, create table of mapping (user-->setting), like :

user_id | setting
  128   |   1
  128   |   23
  128   |   125

But then the query like above will be like :

select * 
from users u1 join settings s1 on u1.id = s1.user_id and s1.setting  = 1
              join settings s2 on u1.id = s2.user_id and s2.setting  = 23
              join settings s3 on u1.id = s3.user_id and s3.setting  = 125

It doesn't look good...

So if someone can advise any solution/approach to this issue it will be very helpful...

Was it helpful?

Solution

Here's my answer to a related question.

You could easily simplify your query:

select * 
from  users     u 
join  settings  s 
on    u.id = s1.user_id 
and   s1.setting  in (1, 23, 125)

This gives you an "or" version of the query.

select u.userid, sum(s.setting) 
from  users     u 
join  settings  s 
on    u.id = s1.user_id 
and   s.setting  in (1, 23, 125)
group by u.userid
having sum(s.setting) = 149

Gives you the "and" version of the query.

OTHER TIPS

Your new design is fundamentally OK, but assuming the "get users of given settings" will be a predominant query, you can fine-tune it in the following way...

CREATE TABLE "user" (
    user_id INT PRIMARY KEY
    -- Other fields ...
);

CREATE TABLE user_setting (
    setting INT,
    user_id INT,
    PRIMARY KEY(setting, user_id),
    CHECK (setting BETWEEN 1 AND 125),
    FOREIGN KEY (user_id) REFERENCING "user" (user_id)
) ORGANIZATION INDEX COMPRESS;

Note the order of fields in PRIMARY KEY and the ORGANIZATION INDEX COMPRESS clause:

  • ORGANIZATION INDEX will cluster (store physically close together) the rows having the same setting.
  • COMPRESS will minimize the storage (and caching!) cost of repeated setting fields.

You can then get users connected to any of the given settings like this...

SELECT * FROM "user"
WHERE user_id IN (
    SELECT user_id FROM user_setting
    WHERE setting IN (1, 23, 125)
);

...which will be very quick thanks to the favorable indexing and minimized I/O.

You can also get users that have all of the give settings like this:

SELECT * FROM "user"
WHERE user_id IN (
    SELECT user_id
    FROM user_setting
    WHERE setting IN (1, 23, 125)
    GROUP BY user_id
    HAVING COUNT(setting) = 3
);

Using bitfield for all settings makes it awkward to query and hard to optimize for query performance (in your old design, every query is a table scan!). OTOH, the "column per setting" design would require a separate index per column for good performance and you'd still have some less-than-elegant queries.

Also, these approaches are inflexible, unlike your new design that can be easily extended to accept more settings or to to store additional information about each setting (instead of just number) by adding another table and referencing it from the user_setting.

Store each setting in it's own column.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top