I think my title is kinda unclear but I don't konw how to tell that otherwise.
My problem is:
We have users that belong to groups, there are many types of groups and any user belong to exaclty one group for each type.
Example: With group types A, B and C, containing respectively the groups (A1; A2; A3), (B1; B2) and (C1; C2; C3)
Every User must have a list of groups like [A1, B1, C1] or [A1, B2, C3] but never [A1, A2, B1] or [A1, C2]
We have messages that target to certain groups but not just a union, it can be more complex collection operations
Example: we can have message intended to [A1, B1, C3], [A1, *, *], [A1|A2, *, *] or even like ([A1, B1, C2] | [A2, B2, C1])
(* = any group of the type, | = or)
Messages are stored in a SQL DB, and users can retrieve all messages intended to their groups
How may I store messages and make my Query to reproduce this behavior ?
CodePudding user response:
So you have 3 main tables:
- Messages
- Users
- Groups
You then create 2 relationship tables:
- Message-Group
- User-Group
If you want to limit users to have access to just "their" messages then you join: User > User-Group > Message-Group > Message
CodePudding user response:
An option could be to encode both the user groups and the message targets in a (big) integer built on the powers of 2, and then base your query on a bitwise AND
between user group code and message target code.
The idea is, group 1 is 1, group 2 is 2, group 3 is 4 and so on.
Level 1:
Assumptions:
- you know in advance how many group types you have, and you have very few of them
- you don't have more than 64 groups per type (assuming you work with 64-bit integers)
- the message has only one target:
A1|A2,B..,C...
is ok,A*,B...,C...
is ok,(A1,B1,C1)|(A2,B2,C2)
is not.
Solution:
- Encode each user group as the corresponding power of 2
- Encode each message target as the sum of the allowed values: if groups 1 and 3 are allowed (
A1|A3
) the code will be1 4=5
, if all groups are allowed (A*
) the code will be2**64-1
- you will have a
User
table and aMessage
table, and both will have one field for each group type code - The query will be
WHERE (u.g1 & m.g1) * (u.g2 & m.g2) * ... * (u.gN & m.gN) <> 0
Level 2:
Assumptions:
- you have some more group types, and/or you don't know in advance how many they are, or how they are composed
- you don't have more than 64 groups in total (e.g. 10 for the first type, 12 for the second, ...)
- the message still has only one target as above
Solution:
- encode each user group and each message target as a single integer, taking care of the offset: if the first type has 10 groups they will be encoded from 1 to 1023 (
2**10-1
), then if the second type has 12 groups they will go from 1024 (2**10
) to 4194304 (2**(10 12)-1
), and so on - you will still have a
User
table and aMessage
table, and both will have one single field for the cumulative code - you will need to define a function which is able to check the user group vs the message target separately by each range; this can be difficult to do in SQL, and depends on which engine you are using
- following is a Python implementation of both the encoding and the check
class IdEncoder:
def __init__(self, sizes):
self.sizes = sizes
self.grouplimits = {}
offset = 0
for i,size in enumerate(sizes):
self.grouplimits[i] = (2**offset, 2**(offset size)-1)
offset = size
def encode(self, vals):
n = 0
for i, val in enumerate(vals):
if val == '*':
g = self.grouplimits[i][1] - self.grouplimits[i][0] 1
else:
svals = val.split('|')
g = 0
for sval in svals:
g = 2**(int(sval)-1)
if i > 0:
g *= self.grouplimits[i][0]
print(g)
n = g
return n
def check(self, user, message):
res = False
for i,size in enumerate(self.sizes):
if user%2**size & message%2**size == 0:
break
if i < len(self.sizes)-1:
user >>= size
message >>= size
else:
res = True
return res
c = IdEncoder([10,12,10])
m3 = c.encode(['1|2','*','*'])
u1 = c.encode(['1','1','1'])
c.check(u1,m3)
True
u2=c.encode(['4','1','1'])
c.check(u2,m3)
False
Level 3:
Assumptions:
- you adopt one of the above solutions, but you need multiple targets for each message
Solution:
- You will need a third table,
MessageTarget
, containing the target code fields as above and a FK linking to the message - The query will search for all the
MessageTarget
rows compatible with theUser
group code(s) and show the relatedMessage
data