SQL - Hard time building a "complex" request - mysql

I've got three tables :
conversations (contains private conversations)
conversations-members - structure : convId | userId (contains all ids of members participating to a conversation so multiple people can talk together. One user participating to a conversation equals one row)
users (users table, classic)
What I am trying to do is :
Users have friends. So, the user browsing my application can open a conversation from his friends' ids.
So, first, I want to look up in conversations-members if there is an existing conversation ONLY between user's Id and his friend's Id and then, pick up the conversation id in conversations table that conversations-members gave me.
Is it possible to do this in one request? If I have to do two requests, I don't even know how to build the first one (find the rows that contain user's Id and friend's Id that have conversation Id in common).
My first idea was to make a single conversations table which would also contain member's Id in the form of a string like "55,105,85,22" and then parse it to get an array, but I think the way I want to do it gives me more options and could be simpler if I manage to handle the SQL requests I need.

To find conversations involving just two particular users, you can do something like this.
SELECT convId, COUNT(*)
FROM `conversations-members`
WHERE userId IN (FIRST_USER, SECOND_USER)
GROUP BY convId
HAVING COUNT(*) = 2
The HAVING line filters out any conversation with any users besides the two you want.
You can use that as a subquery:
SELECT whatever
FROM conversations
WHERE convId IN (
SELECT convId
FROM `conversations-members`
WHERE userId IN (FIRST_USER, SECOND_USER)
GROUP BY convId
HAVING COUNT(*) = 2
)
If you wanted conversations with your two users and any other users, you could change the subquery to HAVING COUNT(*) >= 2.
Pro tip: SQL thinks hyphens - mean subtraction. Avoid them in column and table names.

It's not recommended to use a column with multiple values.
When you have a relation 1-N, normally you need to use a table with two keys, for example, ConvID - UserID. Your index are created by these two columns, for don't allow to generate a duplicate (an UserID two times on the same ConvID, for example).
But, in response to your question, yes, it's possible to do that search. You need to search for an entry on your SQL table where two different IDs are on the same line. You can do it with LIKE clause, but it's not a good option (I think it's the worst possible WHERE clause).
The syntax will be as follows:
SELECT convId FROM conversations-members WHERE userId LIKE '%id1%' AND userId LIKE '%id2%'
For some additional recommendation, if your conversations are always a pair of people, use two new fields on your "conversations" table. If your conversation can have from one to N members, use the "conversations-members" as suggested at the start of the answer.

Related

Best option for getting feed data from multiple tables?

I am having a database design issue and i'm still pretty new to MySQL so I thought I would ask here. What would be the best way to get data for a chronological feed from multiple tables? For example a user does many things, they vote, comment, rate, ask questions. I save all this information in their respective tables "tblVote", "tblRate" etc, now the tricky part. a user can follow a user or many, so say you follow 3-4 people. Following allows you to see their interactions, voting, rating, commenting, asking questions etc in your feed (like facebook or something similar).
What would be the best way to get all the information from all 5 tables for every person they follow and then sort all of that chronologically? I Am assuming my current method (foreach follower grab all votes, comments, ratings etc and sort all would be terrible)
My working theory, so my working idea is to create a Interaction table, that has a column for the users id, the id of the other tables entry, and a type reference. so for example
User ID | InteractionID | Type
9 1232 Comment
10 80 Rating
9 572 Vote
Then you could just go ahead and grab all Interactions for each of the people they follow, sort that and then say grab the top 10? and query the individual databases to get the full info (time of comment, text of comment etc)
A many to many relationship exists between User and Follower. Since, Follower is also another user, this becomes a recursive many-to-many. When you decompose this relationship, you get a Association table or a gerund.
User_Follower {id, userid_fk, followerid_fk}
Both the userid_fk and followerid_fk are referencing to the User table.
Now, I am assuming you have a One-to-many relationship between User-tblRate, User-tblVote, User-tblPost etc.
So, you can write a join something like this:
select p.postTitle, p.postTag, ...,
c.commentId, c.commentData, ...
from (tblUser u INNER JOIN tblPost p
ON (p.userid = u.userid)) INNER JOIN tblComment c
ON (c.userid =
u.userid)
where u.userid in
(select userid_fk from user_follower where followerid_fk = 100)
orderby p.datetime_col ASC, c.datetime_col ASC
LIMIT 10;
100 is the user you want to get the information for.
The idea is that you just have one association table linking the User and Follower, then use simple joins to get the data for all the followees

Best way to store ordered lists in a database?

What's the best way to store "ordered lists" in a database, so that updating them (adding, removing and changing the order of entries) is easily done?
Consider a database where you have a table for users and movies. Each user has a list of favorite movies.
Since many users can like the same movie, I made users and movies separate tables and uses a third table to connect them, usermovies.
usermovies contains an id of a user and a movie and an "order number". The order number is used to order the list of movies for users.
For example, user Josh might have the following list:
Prometheus
Men in Black 3
The Dictator
and user Jack might have a list like:
The Dictator
Prometheus
Battleship
Snow White and the Huntsman
So, they share some favorites, but not necessarily in the same order.
I can get the list of movie IDs for each user using a query:
SELECT movie_id FROM usermovies WHERE user_id =? ORDER BY order_number
Then, with the ordered movie_ids, I can get the list of movies using another query
SELECT name FROM movies WHERE id in (?,?,?) ORDER BY FIELD (id, ?,?,?)
So queries work, but updating the lists seems really complex now - are there better ways to store this information so that it would be easy to get the list of movies for user x, add movies, remove them and change the order of the list?
If you are not looking for a "move up / move down" kinda solution, and then defaulting to adding at the bottom of the list, here are a few more pointers:
Inserting new rows into a specific position can be done like this: (inserting at position 3)
UPDATE usermovies SET order_number = ordernumber + 1
WHERE ordernumber > 3 and user_id = ?;
INSERT INTO usermovies VALUES (?, 3, ?);
And you can delete in a similar fashion: (deleting position 6)
DELETE usermovies WHERE order_numer = 6 and user_id=?;
UPDATE usermovies SET order_number = ordernumber - 1
WHERE ordernumber > 6 and user_id = ?;
A junction/link table with additional columns for the attributes of the association between movies and users is the standard way of realizing a many-many association with an association class - so what you have done seems correct.
Regarding the ease of insert/update/delete, you'll have to manage the entire association (all rows for the user-movie FKs) every time you perform an insert/update/delete.
There probably isn't a magical/simpler way to do this.
Having said this, you'll also need to run these operations in a transaction and more importantly have a 'version' column on this junction table if your application is multi-user capable.
To retrieve user favourites movies you could use a single query:
SELECT um.order_number, m.name FROM movies m
INNER JOIN usermovies um ON m.id = um.movie_id
WHERE um.user_id = ?
ORDER BY um.order_number
To add/remove a favourite movie simply add/remove related record in usermovies table.
To alter a movie order simply change all order_number field in user_movies table related to user.
In addition to what others have said, reordering existing favorites can be done in a single UPDATE statement, as explained here.
The linked answer explains reordering of two items, but can be easily generalized to any number of items.

'Likes' system database

I am developing web application where I have to implement 'Likes' system as facebook has. Application will have a few categories of products that customer can 'like'. So I have started to create database, but I stuck on one obstacle. As I understand there are two ways of doing this:
First. Create one database table with fields of "id, user_id, item_category, item_id". When user click 'like' button information will be saved in this table with various categories of products (item_category).
Second. Create several tables for certain categories of item. For instance, "tbl_item_category_1, tbl_item_category_2, tbl_item_category_3" with fields of "user_id, item_id".
Would be great to get more insight about best practices of this kind database structures. Which works faster? and more logical/practical? I will use only several categories of items.
I would go with the first version with a table structure similar to this:
User Table: PK id
id
username
Category Table: PK id
id
categoryname
Like Table: PK both user_id and catgory_id
user_id
category_id
Here is a SQL Fiddle with demo of table structure with two sample queries to give the Total Likes by user and Total Likes by category
The second one - creating multiple tables is a terrible idea. If you have 50-100 categories trying to query those tables would be horrible. It would become completely unmanageable.
If you have multiple tables trying to get a the total likes would be:
Select count(*)
from category_1
JOIN category_2
ON userid = userid
join category_3
ON userid = userid
join .....
Use one table, no question.
The first method is the correct one. Never make multiple tables for item categories, it makes maintaining your code a nightmare, and makes queries ugly.
In fact, the general rule is that anything that is dynamic (i.e. it changes) should not be stored as a set of static objects (e.g. tables). If you think you might add a new type of 'something' later on, then you need a 'something' types table.
For example, imagine trying to get a count of how many items a user has liked. With the first method, you can just do SELECT COUNT(*) FROM likes WHERE user_id = 123, but in the second method you'd need to do a JOIN or UNION, which is bad for performance and bad for maintainability.
The first method is the correct one. Because you dont know how many categories you will be having and it is very difficult to get the data.

What is the proper way to store friendship associations in a mysql DB

I want to create a table where my users can associate a friendship between one another. Which at the same time this table will work in conjunction to what I would to be a one-to-many relation between various other tables I am attempting to work up.
Right now I am thinking of something like this
member_id, friend_id, active, date
member_id would be the column of the user making the call, friend_id would be the column of the friend they are attempting to tie to, active would be a toggle of sorts 0 = pending, 1 = active, date would just be a logged date of the last activity on that particular row.
Now my confusion is if I were to query I would typically query for member_id then base the rest of the query off of associated friend_id's to display data accordingly to the right people. So with this logic of sorts in mind, that makes me think I would have to have 2 rows per request. One where its the member_id who's requesting and the friend_id of the request inserted into the table, then one thats the opposite so I could query accordingly every time. So in essences its like double dipping for every one action requested to this particular table I need to make 2 like actions to make it work.
Which in all does not make sense to me as far as optimization goes. So in all my question is what is the proper way to handle data for relations like this? Or am I actually thinking sanely about this being an approach to handling it?
If a friendship is always mutual, then you can choose between data redundancy (i.e. both directions having a row) for the sake of simpler queries, or learn to live with slightly more complex queries. I'd personally avoid data redundancy unless there is a compelling reason otherwise - you're not just wasting space and performance, but you'll need to be careful when enforcing it - a simple CHECK is incapable of referencing other rows and depending on your DBMS a trigger may be limited in what it can do with a mutating table.
An easy way ensure to only one row per friendship is to always insert the lower value in member_id and higher value in friend_id (make a constraint CHECK (member_id < friend_id) to enforce it). Then, when you query, you'll have search in both directions - for example, finding all friends of the given person (identified by person_id) would look something like this:
SELECT *
FROM
person
WHERE
id <> :person_id
AND (
id IN (
SELECT friend_id
FROM friendship
WHERE member_id = :person_id
)
OR
id IN (
SELECT member_id
FROM friendship
WHERE friend_id = :person_id
)
)
BTW, in this scheme, you'd probably want to rename member_id and friend_id to, say, friend1_id and friend2_id...
Two ways to look at it:
WHERE ((friend_id = x AND member_id = y) OR (friend_id = y AND member_id = x))
would allow you to query by simply stating one side of the relationship. If both sides are added, this method would still work without causing duplicate rows to be returned.
Conversely, adding both sides of the relationship, so that your queries consist of
WHERE friend_id = x AND member_id = y
not only makes queries easier to write, but also easier to plan (meaning better DB performance).
My vote is for the latter option.
Beautiful - there's no problem with your table as-is.
ALSO:
I'm not sure if this cardinality is "one to many", or "many to many":
http://en.wikipedia.org/wiki/Cardinality_%28data_modeling%29
Q: I were to query I would typically query for member_id then base the
rest of the query off of associated friend_id's to display data
accordingly to the right people
A: Frankly, I don't see any problem querying "member to friend", or "friend to member" (or any other combinations - e.g. friends who share friends). Again, it looks good.
Introduce a helper table like:
users
user_id, name, ...
friendship
user_id, friend_id, ....
select u.name as user, u2.name as friend from users u
inner join friendship f on f.user_id = u.user_id
inner join users u2 on u2.user_id = f.friend_id
I think this is pretty similar to what you have, just putting a query as an example.

How do I select a record from one table in a mySQL database, based on the existence of data in a second?

Please forgive my ignorance here. SQL is decidedly one of the biggest "gaps" in my education that I'm working on correcting, come October. Here's the scenario:
I have two tables in a DB that I need to access certain data from. One is users, and the other is conversation_log. The basic structure is outlined below:
users:
id (INT)
name (TXT)
conversation_log
userid (INT) // same value as id in users - actually the only field in this table I want to check
input (TXT)
response (TXT)
(note that I'm only listing the structure for the fields that are {or could be} relevant to the current challenge)
What I want to do is return a list of names from the users table that have at least one record in the conversation_log table. Currently, I'm doing this with two separate SQL statements, with the one that checks for records in conversation_log being called hundreds, if not thousands of times, once for each userid, just to see if records exist for that id.
Currently, the two SQL statements are as follows:
select id from users where 1; (gets the list of userid values for the next query)
select id from conversation_log where userid = $userId limit 1; (checks for existing records)
Right now I have 4,000+ users listed in the users table. I'm sure that you can imagine just how long this method takes. I know there's an easier, more efficient way to do this, but being self-taught, this is something that I have yet to learn. Any help would be greatly appreciated.
You have to do what is called a 'Join'. This, um, joins the rows of two tables together based on values they have in common.
See if this makes sense to you:
SELECT DISTINCT users.name
FROM users JOIN conversation_log ON users.id = converation_log.userid
Now JOIN by itself is an "inner join", which means that it will only return rows that both tables have in common. In other words, if a specific conversation_log.userid doesn't exist, it won't return any part of the row, user or conversation log, for that userid.
Also, +1 for having a clearly worded question : )
EDIT: I added a "DISTINCT", which means to filter out all of the duplicates. If a user appeared in more than one conversation_log row, and you didn't have DISTINCT, you would get the user's name more than once. This is because JOIN does a cartesian product, or does every possible combination of rows from each table that match your JOIN ON criteria.
Something like this:
SELECT *
FROM users
WHERE EXISTS (
SELECT *
FROM conversation_log
WHERE users.id = conversation_log.userid
)
In plain English: select every row from users, such that there is at least one row from conversation_log with the matching userid.
What you need to read is JOIN syntax.
SELECT count(*), users.name
FROM users left join conversion_log on users.id = conversation_log.userid
Group by users.name
You could add at the end if you wanted
HAVING count(*) > 0