Reputation: 7573
Based on social networking design, I need to store whether a person has Liked
or Disliked
something. There are other actions they can take such as Share
as well.
What I need to do is make sure that when a person Like
s something, its value is stored as true
. But if they then change their mind and Dislike
the same thing, that the Like
'd value is set to NULL
and the Dislike
value is set to true
.
Initial approach would be a table like this:
UserID (int) | ItemID (int) | Like (bit) | Dislike (bit) | Share (bit) | ...
1 1 1 NULL NULL
1 2 NULL 1 NULL
1 3 1 NULL 1
The problem here is that if more actions can be taken on an item e.g. 'Favourited' then I have to add these columns to the table. This is breaking normalisation rules but it could work. The important business rule is that someone cannot Like
and Dislike
the same Item
at the same time. Its one or the other. I guess I would have to do this logical processing on the application end rather than DB end but if there's a better way please do let me know!
The other approach could be to have a table that stores user actions like this:
ActionID | ActionName
1 Like
2 Dislike
3 Share
And then have a linking table like this:
UserID | ItemID | ActionID
1 1 1
1 1 2
1 1 3
The problem with this second approach is that I think it will be hard to make the Actions mutually exclusive. The user has Liked an item, and then Disliked it, and then Shared it. So in my application I would have to work out which ActionID relates to a Like or Dislike and then depending on which one is chosen, delete the other. So here I would have to delete the row with an ActionID value of '1' because it is overwritten by the Dislike action with value of '2'
Could anyone please advise on what the best way to do this might be? Its the mutual exclusivity between certain actions that is causing me the most bother.
Upvotes: 0
Views: 232
Reputation: 9166
This is based on your first idea of a table having Boolean columns for separate actions that the user has taken regarding the item.
Use a CHECK
constraint on the table (change the table name to whatever your table is really named):
ALTER TABLE UserAction
ADD CONSTRAINT CK_LikeOrDisLike
CHECK (
(Like is null AND Dislike is null)
OR (Like = 1 AND Dislike is null)
OR (Like is null AND Dislike = 1)
)
Or perhaps:
ALTER TABLE UserAction
ADD CONSTRAINT CK_LikeOrDisLike
CHECK (
(ISNULL(Like, 0) = 0 AND ISNULL(Dislike, 0) = 0)
OR (Like = 1 AND ISNULL(Dislike, 0) = 0)
OR (ISNULL(Like, 0) = 0 AND Dislike = 1)
)
if you want to allow 0 = false
in addition to null = false
.
This could then further be changed to:
ALTER TABLE UserAction
ADD CONSTRAINT CK_LikeOrDisLike
CHECK (
ISNULL(Like, 0) = 0 OR ISNULL(Dislike, 0) = 0
)
This last one means that at least one of Like
and Dislike
must be either false or null.
This will of course not help you to make consistent updates from your program, you must still handle that separately. But IF your program tries to make an invalid insert or update, the database will reject it with an error message.
Upvotes: 0
Reputation: 1669
Alternatively, you could re-implement it as an integer column and allow (-1, 0, 1) to represent (dislike, neutral, like), which in turn may simplify ranking an item's popularity. For example, to list the top 10 most liked items:
select top (10)
ItemID, sum(ToLikeOrNotLike)
from Items
group by ItemID
order by sum(ToLikeOrNotLike) desc;
This takes cares of the mutual exclucivity as well.
On a suggestion from Spevy (see comments), the 0 (for no like or dislike) could be replaced with null, so that it's easier to determine overall activity on an item. For example:
select top (10)
ItemID, count(ToLikeOrNotLike)
from Items
group by ItemID
order by count(ToLikeOrNotLike) desc;
Would return how many likes or dislikes an item has received. This can be useful to distinguish between an item with 2 likes and 1 dislikes, and an item with 10,000 likes and 9,999 dislikes, both of which would be otherwise indistinguishable when summed.
Upvotes: 1