How to handle the following cases in a friend request data model? - mysql

For a project that I am on, on of the requirements is users being able to send each other friend requests and either accepting them or declining them.
Side note: the project is built on Laravel.
At first my data model for those friendships was as such (leaving out things as timestamps etc. in this example):
pivot_friend_request
====================
sender_id
receiver_id
status
Of course sender_id and receiver_id have a foreign key constraint to id on the user table.
Here I ran into this case issue:
CASE:
User foo invites bar (table row: foo-bar-pending)
Meanwhile user bar never reads his notifications but searches user foo
User bar invites user foo (table row: bar-foo-pending)
Now I have two table rows for those two users that have sent friend requests to each other that are both pending.
What is the best way to check if foo-bar is already present before also inserting bar-foo (regardless of the request status)?
Then I asked my project manager what do to, resulting in him asking me why I was going about it like this. To have a more object oriented approach he told me to have separate tables for the requests and actual friendships because a request is a request and a friendship is a friendship.
I think he made a good point there but it confused me even more. Because:
Now I have to deal with the above mentioned case twice and I still don't have a solution to it.
Plus it made me think on how to handle the request table. Should the request be removed from it after accepting or should there still be a status of the request?
Can it be more efficient like this because I now see it like this:
Request is sent by bar
Check if foo-bar already exists in request table
If exists update pending to accepted else create new one with pending. (If already accepted return 'Already friends message')
Create new row in friendship table
Also... what if a friendship gets removed? I know have two tables where I should update that status? The request and the actual friendship table?
I really really need some pointers here it seems.

if friendship get removed you have to update both request and friendship table. for example,
bar invites foo.then in request table you can set statusas pending
once foo accepts the friend request add row to a friends table.
later foo unfriends bar. then friend table will be updated.
now bar wants to invite foo again. butbar cannot send a new friend request to foo since there is already a record in the request table. so you have to update request table at the 3rd step or 2nd step.
however this whole scenario depends on your requirements.

Related

Database design dilemma on connecting users with messages

Here's what I came up with but I'm not sure which one of these is "the best". Perhaps there's another, better one that I may not know of. Keep in mind that I have both inbox and outbox in my app and messages deleted by either sender or recipient should still be visible to other related users unless they delete it themselves.
Option 1 - simple ManyToMany:
Tables:
User - just user fields
Message - just message fields
User_Message - contains 2 foreign keys: user_id and message_id
Example: When user sends a message, ONE message row is added to the Message table, and TWO rows are added to User_Message, obviously connecting sender and recipient with the added message. Now, this might get a little problematic when let's say I want to fetch only inbox messages because ManyToMany will fetch all of them so I came up with option 2.
Option 2 - OneToMany:
Tables:
User - just user fields
MessageReceived - message fields AND foreign key to user_id
MessageSent - message fields AND foreign key to user_id
Example: When user sends a message, this message is added to both received and sent tables but with different user_id. Of course senders id will be in sent table and recipient id in received table.
Now, when I want to fetch only inbox messages, I'm fetching messages from MessageReceived table and while deleting for example inbox (MessageReceived) message, copy of it still stays in MessageSent and is available to sender so everything is fine, however I feel like there's something "not cool" about this one because I'm basically keeping ALMOST the same data in both tables.
Please, let me know what do you think about this and if there is any better way to do it, I'm also listening.Thanks for your time.
EDIT :
Both Madbreaks and Tab Alleman provided really good and somewhat similar solutions so thanks for that. I'm gonna go with Madbreaks one, simply because I prefer to delete the relations in join table instead of keeping a 'deleted' column but that's just my taste. Nevertheless, thank you both for your time and answers.
You shouldn't need to add 2 rows in user_messages for each message - have 3 columns in that table: sender_id, recipient_id, message_id.
EDIT
The deletion scenario you describe in your question, below, changes things. Instead of a n-to-n approach, you likely now have two 1-to-n relationships:
the relationship between sender and their many sent messages
the relationship between a recipient and their many received messages
I would probably have the messages table have a sender ID foreign key. I would then have a message_recipients table that maps user (recipient) ID to message ID.
Now, if a sender can delete a message but the recipients should still be able to access it (and know who the sender is), then you'll need four tables:
users
messages
message_sender (1-to-1 map) -- senders deleting sent messages deletes from her
message_recipients (1-to-n map) -- recipients deleting received messages deletes from here
It's not clear from your question whether or not this is a requirement, I only add it for completeness. You may want a trigger or a subsequent query to determine if/when there are no remaining relationships between the users and messages tables, and at that time (possibly) delete the message itself.
Here's what I would do (I am assuming a message can only have one sender, but multiple recipients)
UserTable - Contains UserID and Other info
MessageTable - Has MessageID, SenderID (FK to UserTable.UserID) and other info
MessageRecipientsTable - Has MessageID, RecipientID (FK to UserTable.UserID), and possibly other info like when/if it was received, etc.
If you want a recipient to be able to delete a message and still have it show for the sender (and other recipients), then you would add a "Deleted" column to the MessageRecipientsTable. You would never actually delete a row from the messages table, but when populating a recipients inbox you would filter out the rows where "Deleted" is true.

Optimal solution for: Keeping state of entity in database

Problem
When I design structure of a database, I often work with tables that can have a state assigned. For example a response for an offer - this response table can, for instance, have these states:
waiting - response was created and waits for approval by owner of offer
cancelled - response was cancelled by its author
approved - response was approved by the author of offer
rejected - response was rejected by the author of offer
expired - response expired together with associated offer
I am considering these two solutions
1. Solution
Create table response_state and keep its key in the response table as foreign key
Pros:
All states are together in one table
New state can be added easily
Cons:
Synchronization of response_state foreign key value with other response's columns is necessary. For example, for expiration - when expiration day is reached, state has to be changed to "expired".
2. Solution
Put approved/rejected/cancelled logical value columns into response and create view view_response_state that will contain a column with state name according to values in these columns and the expiration date.
For example, if approved is false, rejected is false, cancelled is false and expiration_date < today, then state is "waiting" etc.
Pros:
No synchronization needed, all data are kept once in the db
Cons:
When I want to add a new state I have to change table response and provide view_response_state with logic of identification of such state
Question
My question is, which approach would you choose? Or is there a better approach?
In my opinion you need three tables.
One is to be called "response_state". It contains five rows, one for each of your response names. If you need to add a new response name, just INSERT it to this table. It has the column "response_state_id." Little tables like this are often called codelist tables.
Another is to be called "offer". It will have an offer_id and other information as needed about the offer.
The third is "response." It contains the following columns.
response_id pk, autoincrement
offer_id fk to offer table
response_state_id fk to response_state table
response_timestamp
(other columns relating to the response as needed)
This table works as follows: Anytime the state of a response changes, you INSERT a row to this table showing the new state. You never UPDATE these rows. You might DELETE old ones in a purge process for completed transactions.
When you need to find the current state of an offer you give a query like this. It pulls only the most recent response to each offer from the table.
SELECT r.offer_id, r.response_state_id, rs.response_state_name
FROM response AS r
JOIN response_state AS rs ON r.response_state_id = rs.response_state_id
JOIN (
SELECT MAX(response_id) as latest_id,
offer_id
FROM resp
GROUP BY offer_id
) AS recent ON r.response_id = resp.latest_id
This is a really cool way to handle this because it retains the history of responses to each offer. Because it's an INSERT-only solution it's inherently robust against various kinds of race conditions if lots of responses come in on top of each other.

Friends table for a social networking site

I have been working on a social networking site. Here a user requests another user to be his friend (friend request). I thought of a 'Friends' table which looks like
Table Name: Friends
Coloumns :
User1 - Int - FK
User2 - Int - FK
Request - Enum('0','1')
Time - DateTime
PK - (User1, User2)
In Request field '0' is stored when a request is made by User1 to User2 and '1' is stored when the request has been approved by User2.
The problem arises when i want to retrieve all friends of a user. I had to check whether the Request field is '0' or '1' each time. Is there another way to do so? Is it better if i have another table which stores all the details of Friend requests?
In a comment you basically state that an established friendship (as opposed to a request) is always symmetric in your setup. In that case, you have basically two options: you can either store it in two rows, or select it by matching either column. The former will yield simpler queries, but the latter will ensure that the symmetry is inherent in the database structure, and will avoid storing duplicate data as well. So I'd go for the latter, i.e. some form of WHERE (User1 = XX or User2 = XX ). The query might well take twice as long as a query for just one column would take on that same number of rows, but as the number of rows is only half that of the other storage scheme, the net effect in terms of performance should be negligible.
Whether you want a separate table for requests or established friendships depends on how similar those two are, both in terms of associated data and of the control flow in your application. So for example, if you want to present a single list to a user which shows both his established friendships and his pending requests, perhaps with different colors or whatever, but in the same list, then having a single table in the database would be more appropiate. If, on the other hand, you mostly treat requests and friendships separately, then having two tables would come more natural. And if, at some time, you decide that a freindship needs attributes like share_calendar whereas a request needs attributes like confirmation_key or whatever, then you'll be better off with different tables.
If you decide to make this a single table, I'd suggest more describtive values for the enum, like calling the column status and the values requested and established. I, for one, would at first glance interpret a value of request = 1 as ”this is a request only, not an established freindship”, exactly the opposite of the meaning you associate. This ambiguity can lead to errors when different people need to maintain the code. And in several years you'll be enough of a different person from who you are now that even you yourself might misinterpret your old code. So be descriptive there.
One more note: you may always use views to tweak the way your database appears to your queries. For example, you can create a view
CREATE VIEW SymmetricEstablishedFriends AS
SELECT User1 AS Me, User2 AS Friend, Time
FROM Friends
WHERE Status = 'established'
UNION
SELECT User2 AS Me, User1 AS Friend, Time
FROM Friends
WHERE Status = 'established'
This will restrict the data to established friendships only, and will take care of symmetrizing things for you. Using such views in your queries, you can avoid having to deal with all the details of the table structure in every query. And if you ever change those details, there will be less places to change.
I would break your data into requests and friendships. When a request is approved, convert it into a friendship. They really are two different objects, and should be treated as such.
Requests ::
requesting_user_id : int()
requested_user_id : int()
date_requested : datetime()
status_id : int()
Statuses ::
(Active, Declined, Accepted, Ignored)
Friendships ::
friendship_id : int()
user_id : int()
friend_id : int()
Maybe delete the request if it's declined, or have a column for it (to keep people from repeatedly requesting the same user's friendship). You'd have to convert the request to two friendships (one in each direction) for easy indexing
SELECT friend_id FROM friendships WHERE user_id = ?

Records Polling from MySQL table

What could be the best way to fetch records from a MySQL table for more than one clients connected, which are retrieving records concurrently and periodically.
So everyone gets the new messages as the new record enters the table but old messages should not retrieve again.
Current Table Structure:
MessageId, Message, DatePosted, MessageFromID
Thanks
Your problem can be translated to: How can each client know, which records to read and which records not.
There are two completly different approaches to that, with very different properties.
Let the client care for that
Let the server care for it
Model #1 would quite simply require, that you
Use something like an AUTO_INCREMENT on some field, if your MessageID is not guaranteed to be incrementing
On the server give each client not only the messages, but also the ID
Have the client keep this ID and use it as a filter for the next poll
Model #2 needs you to
Have another table with 'ClientID and MessageID'
Whenever a client gets a message, create a record there
Use non-existance of such a record as a polling filter

Facebook like notifications tracking (DB Design)

I am just trying to figure out how Facebook's database is structured for tracking notifications.
I won't go much into complexity like Facebook is. If we imagine a simple table structure for notificaitons:
notifications (id, userid, update, time);
We can get the notifications of friends using:
SELECT `userid`, `update`, `time`
FROM `notifications`
WHERE `userid` IN
(... query for getting friends...)
However, what should be the table structure to check out which notifications have been read and which haven't?
I dont know if this is the best way to do this, but since I got no ideas from anyone else, this is what I would be doing. I hope this answer might help others as well.
We have 2 tables
notification
-----------------
id (pk)
userid
notification_type (for complexity like notifications for pictures, videos, apps etc.)
notification
time
notificationsRead
--------------------
id (pk) (i dont think this field is required, anyways)
lasttime_read
userid
The idea is to select notifications from notifications table and join the notificationsRead table and check the last read notification and rows with ID > notificationid. And each time the notifications page is opened update the row from notificationsRead table.
The query for unread notifications I guess would be like this..
SELECT `userid`, `notification`, `time` from `notifications` `notificationsRead`
WHERE
`notifications`.`userid` IN ( ... query to get a list of friends ...)
AND
(`notifications`.`time` > (
SELECT `notificationsRead`.`lasttime_read` FROM `notificationsRead`
WHERE `notificationsRead`.`userid` = ...$userid...
))
The query above is not checked.
Thanks to the idea of db design from #espais
You could add another table...
tblUserNotificationStatus
-------------------------
- id (pk)
- notification_id
- user_id
- read_status (boolean)
If you wanted to keep a history, you could keep the X latest notifications and delete the rest that are older than your last notification in the list....
If, when you give notifications, you give all relevant notifications available at that time, you can make this simpler by attaching timestamps to notifiable events, and keeping track of when each user last received notifications. If you are in a multi-server environment, though, you do have to be careful about synchronization. Note that this approach doesn't require true date-time stamps, just something that increases monotonically.
I see no-one here addresses the fact, that notifications are usually re-occurring, aka. notification of an upcoming transaction is always going to be the same, but with a different transaction ID or Date in it. as so: { You have a new upcoming payment: #paymentID, with a due date of #dueDate }.
Having texts in a different table can also help with
If you want to change the notification text later on
Making the app multilingual is easier, because I can just layer the notifications table with a language code and retrieve the appropriate string
Thus I also made a table for those abstract notifications, which are just linked under the the user with a middle table, where one notification type can be sent to one user at multiple times. I also linked the notifications to the user not by a foreign key ID, but I made notification codes for all notifications and full_text indexed the varchar field of those codes, for faster read speeds. Due to the fact that these notifications need to be sent at specific times, it is also easier for the developer to write
NotificationService::sendNew( Notification::NOTE_NEW_PAYMENT, ['paymentId'] => 123, ['dueDate'] => Carbon::now(), 'userIdToSendTo' );
Now since my messages are going to have custom data in them, that is inserted into the string, as you can see from the second argument beforehand, then I will store them in a database blob. as such
$values = base64_encode(serialize($valuesInTextArray));
This is because I want to decouple the notifications from other tables and as such I dont want to crete unnessecary FK relations from and to the notifications table, so that I can for example say notification 234 is attached to transaction 23 and then join and get that transaction ID. Decoupling this takes away the overhead of managing these relations. The downside is, it is nigh impossible to delete notifications, when for example a transaction is deleted, but in my use case I decided, this is not needed anyway.
I will retrieve and fill the texts on the App side as follows. Ps. I am using someones vksprintf function (https://github.com/washingtonpost/datawrapper/blob/master/lib/utils/vksprintf.php), props to him!
$valuesToFillInString = unserialize(base64_decode($notification->values));
vksprintf( $notificationText->text, $valuesToFillInString )
Notice also which fields I index, because I am going to find or sort by them
My Database design is as follows
==============================
TABLE: Users
id (pk)
==============================
TABLE: Notifications
id (pk)
user_id (fk, indexed)
text_id (fk - NotificationTexts table)
values (blob) [containing the array of values, to input into the text string]
createdDateTime (DateTime)
read (boolean)
[ClusterIndex] => (user_id, createdDateTime)
==============================
TABLE: NotificationTexts
id (pk)
text_id (uniquem indexed)
text (varchar) [{ You have a new upcoming payment: #paymentID, with a due date of #dueDate }]
note (varchar, nullable) [notes for developers, informational column]
I am also trying to figure out how to design a notification system. Regarding notification status (read, unread, deleted, archived, ect) I think that it would be good a good candidate to for ENUM. I think it is possible that there will be more than two different types of status other than READ and UNREAD such as deleted, archived, seen, dismissed, ect.
That will allow you to expand as your needs evolve.
Also I think it may make sense (at least in my case) to have a field to store an action url or a link. Some notifications could require or prompt the user to follow a link.
It also may make sense to have a notification type as well if you want different types. I am thinking there could be system notifications (such as a verify email notification) and user prompted notifications (such as a friend request).
Here is the structure I think would be a minimum to have a decent notification system.
users
-------------
id
username
password
email
notifications
-------------
id
user_id (fk)
notification_type (enum)
notification_status (enum)
notification_action (link)
notification_text
date_created (timestamp)
Table are following
User
userId (Integer)
fullName(VarChar)
Notification
notificationId (Integer)
creationDate (Date)
notificationDetailUrl (VarChar)
isRead (bollean)
description (VarChar)
userId (F.K)