How can I efficiently query 'Like' data from MySQL? - mysql

I am developing a social network iOS app using a PHP backend with a MySQL database. I have 3 tables in my database to store Posts, Comments and Likes.
Currently, the app requests up to 50 posts at a time which includes various specific data. I do this using a SELECT query from MySQL. My 'Likes' are stored in the 'Likes table' as 1 Like per row. This includes their username and which post they 'liked'.
When the app is refreshed, I need to gather the 'Likes' data on the posts that have just been loaded. My problem is that I do not know the best way to query MySQL to return all associated likes with that user. This means that when I load some posts, I want the user to see which posts they have already liked.
Here is a visual description of my problem:
Please understand that up to 50 of these posts will be shown at one time.

There is several way to do this and depending on your project you might want to optimise my answer.
I assume your Likes table has a structure of (int) id, (int) user_id, (int) post_id.
To answer directly to your question (about doing a query only for the refresh), the next MySQL query will give you the list of post_id liked by the user. You need to replace %user_id% by the id of the user and %post_ids% by the list of the 50 posts ids, separated by a comma:
SELECT post_id
FROM Likes
WHERE user_id = %user_id% AND
post_id IN (%post_ids%)
However, if the refresh implies the whole page (and not only an AJAX request or so on) I would consider implementing this directly in the query getting the posts. For example :
SELECT p.id, p.content, p...., l.id AS `Liked`
FROM Posts AS p
LEFT JOIN Likes AS l
ON p.id = l.post_id AND
l.user_id = %user_id%
WHERE ...
Please also consider adding indexes on the right columns.

As promised, my solution is here:
SELECT `posts`.`postnumber`, `posts`.`message`, `likes`.`didLike`
FROM `posts`
LEFT JOIN `likes`
ON `posts`.`postnumber`=`likes`.`post_id`
AND `likes`.`user_id`='USERNAME';

Related

Laravel Left Join with a tricky filter

I am assigning unique users to vouchers on my website.1 user may have more than one voucher assigned to them but cannot be assigned the same voucher twice.I have 2 mysql tables that I am fetching data from.
tbl_users
tbl_voucher_users
When a user click on a button on my website, they pass along a voucher_id with which I use to display eligible users that can be assigned this voucher ( I.e Users that have not been assigned to this voucher ).
Below is how I am getting the users where voucher_id = 8
$user_data = DB::table('users')
->leftJoin('voucher_users', 'users.id', '=', 'voucher_users.user_id')
->where('voucher_users.voucher_id','!=',8) //User not assigned this voucher
->select('users.*','users.id as userID','voucher_users.*')
->get();
My problem
I am able to left join without the where clause and get results from both Users table and Voucher_users table having eliminated all users assigned voucher_id=8.
However, the results also include users who are assigned other vouchers but also the voucher I am assigning.
i.e
Expected resulsts would be users: 8,11,12,13,14 having eliminated users: 1,4
But my current results are:4,8,11,12,13,14
How do I get rid of the user 4 to prevent double assignment?
Thanks to the suggestion above by #Kevin Lynch to use NOT EXIST .. I simplified the code to:
SELECT users.*
FROM
users
WHERE
NOT EXISTS(SELECT user_id FROM voucher_users WHERE voucher_users.user_id = users.id AND voucher_id=9)
It works so far, I can then covert it to Laravel style
NOT EXISTS would be a good solution if this were to remain a small project (or if you could put more constraints on the users to keep the dataset small perhaps by limiting based on user created date or similar).
However if you can't do such a thing, this query will eventually give you problems because under the hood, mysql will be running that sub-query for each record returned in the main query. You can check https://dev.mysql.com/doc/refman/5.6/en/subquery-materialization.html for more information.
A different solution which would handle the scaling quite a bit better would be to generate a temporary table of users that have the voucher you are looking to remove...
create temporary table tmp_voucher_user (user_id int not null, primary key (user_id)) as
select distinct user_id from voucher_users where voucher_id = 8;
Now that we have a table of users we which to remove, all we need to do is worry about a simple left join...
select users.*, user_voucher.*
from users
inner join user_voucher on users.id = user_voucher.user_id
left join tmp_voucher_user on users.id = tmp_voucher_user.user_id
where tmp_voucher_user.user_id is null -- this part is important, it's only going to grab users where there isn't a match on tmp_user_voucher
Unfortunately this isn't as clean as just doing a NOT EXISTS and I don't believe Laravel supports a way to build temporary tables outside of just writing a raw query but it should scale quite a bit better.

Filter out records with at least one association that doesn't meet given conditions

Let's say I have posts and categorizations.
Post(id)
Categorization(post_id, topic_id)
I'd like to fetch posts that don't belong to a specific topic id.
In my case I have to use an inner join when joining Post to Categorizations as i have other filters to execute.
How do I go about this?
I have tried the following:
Post.joins(:categorizations).where("categorizations.topic_id != ?", doomed_topic_id)
But this returns posts that still have OTHER topics. it only works with posts with just one single topic that happens to be the unwanted one.
For instance, if I have a post with 2 categories (the doomed topic_id AND another topic) this query fails and actually fetches it, instead of filtering it out.
Try:
Posts.where('not exists
(select * from categorizations
where post_id = posts.id and
topic_id = ?)', doomed_id)

Mysql Left join multiple 1 to many tables

I am trying to get the following data from 3/4 tables in 1 Mysql query, wondering if it is possible ? The tables are
TOPIC
topicid (FK)(PK)
groupid
topic
user
LIKED
likeid
topicid (FK)
user
COMMENT
commentid (PK)
topicid (FK)
comment
user
I write my topics and store in TOPIC Table with unique topicid. I group each topic using groupid.
Other tables may have 0 or more data per topicid.
I am trying to get each topic for a particular group and also get other datas from the concerned Tables. I checked How to left join multiple one to many tables in mysql? and got few idea but that is for the count while I wanted to get details from that table (users who like), and (user and their comment).
I have tried
SELECT t.topicid,
topic,
group_concat(DISTINCT likeid,l.user SEPARATOR '|'),
group_concat(DISTINCT commentid,comment,c.user SEPARATOR '|') AS comments
FROM TOPIC t
LEFT JOIN LIKE l ON l.topicid = t.topicid
LEFT JOIN COMMENT c ON c.topicid = t.topicid
WHERE t.groupid='some_value'
GROUP BY t.topicid
While this works partly e.g. I do get the details but only if there is one topic in a group. If there are 2 or more topics in a group then the concat details are stored in the first record only and the later topics show no likes and comments.
Can someone please help me to correct this or any particular Mysql function I am missing
I am very very sorry for wasting your time, after thorough re-check I found my table data were wrong (checked after making sqlfiddle thanks #Barmar).
I was by mistake inserting wrong data in like and comment table. So Likes and comments for 2nd topic topicid='2' of groupid='1' were inserted by mistake as topicid='1' that is why the details only showed in 1st topic and nothing came out for second topic.
The SQL above is absolutely correct and thankyou for helping me find the fault.
Extremely sorry for posting again.

Turn two queries into one

I'm having some problems with a query I'm writing. This seems like table structure that is very frequent so I'd love some help.
Let's say I have 3 tables similar to a facebook structure. Users, Wall Posts, and Comments. Users can make wall posts, and comment on other wall posts.
On a users page I would like to show a users wall posts and a count of how many comments that post has. This is what I have so far
I query the Wall Post table using the users id as an inner join to the User table. That gives me a result set of wall posts for that user's page. Then I loop through that result set, take the Wall Post id from each result set, and query the Comment table for the Count of comments for that Wall Post Id. This works, however I have to hit the db twice. Can anyone think of a way that I could do this with one query?
First Query Example:
SELECT wallPost.*, user.currentDefault, user.displayName, user.userName
FROM wallPost
INNER JOIN user ON user.id = wallPost.sourceUserId
WHERE wallPost.recipientId = ? ORDER BY wallPost.id DESC
Second Query Example:
SELECT COUNT(id) AS count
FROM comment
WHERE wallPostId = ?
I would add the count as a subquery and join the subquery to the main query
SELECT
wallPost.*,
user.currentDefault,
user.displayName,
user.userName,
wallpost_commentcount.total
FROM
wallPost
INNER JOIN user ON user.id=wallPost.sourceUserId
LEFT JOIN (SELECT wallPostId,COUNT(*) as total FROM comment GROUP BY wallPostId) as wallpost_commentcount ON (wallpost_commentcount.wallPostId=wallPost.id)
WHERE
wallPost.recipientId = ?
ORDER BY wallPost.id DESC
Please make sure you have an index on comment.wallPostId otherwise this query will take a long time.
I used the LEFT JOIN because you always want to get the wallPost even if there are no comments records yet

efficiently showing the user only items he did not already see before (mysql)

This question applies to any kind of system that contains items (e.g: news articles) and users that watch these items.
So let's say i have a users table ([id],[username]), an articles table ([id],[title],[text]) and a table that contains all the articles viewed by all the users ([user_id],[article_id]).
What i want to do is efficiently show the user only the articles he did not already read before.
I know i can just do something like
select id,title,text from articles where id not in (select article_id
from article_views where user_id = 123)
But what if the current user already read 1M articles ? the query will become something like
select id,... from articles where id not in (1,2,3,......1000000)
This, i can assume, is too slow to be practical.
Also, it sucks because the more articles a user reads - the slower response time he will have retrieving new (unread) articles..
Any other suggestions, db-wise ?
Sometimes, by doing a LEFT JOIN and only returning NULL (ie: not found) entries might be faster than sub-select. It's does a direct join A:B and only includes those where NO match is found
select
a.id,
a.title,
a.text
from
articles a
LEFT JOIN article_views av
on av.User_ID = 123
AND a.id = av.article_id
where
av.article_id IS NULL
I would ensure an index on ( UserID, Article_ID ) (which I believe would be your primary key to that table anyway).
Instead of adding them directly in to the statement, you could run something like:
select articles.id, ... from articles, article_views where article_views.user_id = [useridhere] and articles.id != article_views.id
It alleviates the issue with having huge queries, but you're still comparing a million articles if you have a million articles.