How to use MYSQL LIMIT per person - mysql

So I have a scenario where there are 1-8 people that i need to query up to 3 things they "liked" per person. I have the query set up as
SELECT liked FROM likeTable WHERE uid IN (uid1,uid2,uid3,uid4) LIMIT 12
but obviously this can potentially stop when i have 12 "likes" for uid1, leaving the rest at 0. I read a possible solution as using UNION ALL for example...
(SELECT liked FROM likeTable WHERE uid = uid1 LIMIT 3)
UNION ALL
(SELECT liked FROM likeTable WHERE uid = uid2 LIMIT 3)
UNION ALL
(SELECT liked FROM likeTable WHERE uid = uid3 LIMIT 3)
UNION ALL
(SELECT liked FROM likeTable WHERE uid = uid4 LIMIT 3)
And i would be able to achieve this by making the sql query string in php with a forloop, but is this an efficient way of querying my data?
note: I don't really care about the order in which the "liked" is retrieved, although it would be nice if i could add an ORDER BY likeID DESC, which is my autoincrementing column
Thanks!

For small number (not hundreds, I would say) of categories (users), just use the union solution.
And would I be able to achieve this by making the sql query string in php with a forloop, but is this an efficient way of querying my data?
Definitelly yes! Unless you query hundreds of users in one go. For 1-8 people, it is just perfect solution!

Related

How to improve slow MySQL subquery

I know there's many questions/answers for slow queries, but I'm struggling to relate an existing answer to my example.
I have the following simple query which counts article views in a subquery:
SELECT
articles.id,
articles.views,
articles.title,
articles.slug,
articles.created_at,
(SELECT count(*) FROM tracking WHERE element_id = articles.id AND tracking_type = 'article_view') AS tracking_views
FROM articles
WHERE articles.company_id = 123
ORDER BY articles.created_at DESC
This particular company has ~250 articles, and the query takes over 12 seconds.
Is there a better/more efficient way I could be doing this?
Try joining to a group by. Its pretty hard to say without knowing how many articles / views and companies there are though.
What you want is for SQL to be able to to the aggregation of tracking in one go, rather than individually for every row in the result, which is implied by the position of your tracking_view sub select.
If your lucky (I didnt check) the join to the counts sub select will be smart enough to skip any articles that are not for the right company. If not you can include the join back to company in the counts sub select.
eg
select a.*, counts.count
from articles a
join (
select count(*) as count, element_id
from tracking
where tracking_type = 'article_view'
group by tracking.element_id
) as counts on counts.element_id = a.id
where a.company_id = 123
ORDER BY articles.created_at DESC

Get top 5 most popular values in mysql with where clause

I'm working on a project and I have a problem. I have a table namedfriendswith three columnid,from_emailandto_email(it's a social networking site and "from_email" is the person that follows the "to_email"). I want a query to return the top 5 friends I follow according to the number of their followers. I know that the query for top 5 is:
SELECT
to_mail,
COUNT(*) AS friendsnumber
FROM
friends
GROUP BY
to_email
ORDER BY
friendsnumber DESC
LIMIT 5
Any ideas?
I would also like to return friends with the same number of followers ordered by their name. Is it possible?
You should use COUNT(from_email) instead of COUNT(*); because you want to calculate the number of followers, which is represented by from_email.
Thus, your select clause would be something like:
SELECT to_email, COUNT(from_email) as magnitude
as for getting the most popular people that you follow, you could use IN clause:
WHERE to_email IN (SELECT to_email FROM friends WHERE from_email='MY_EMAIL');
and about name, you shall join this query with the other table which contains the name value.
Since you've got the essentials now, I hope you can try to compose the full query on your own =)
Join again to the table for the 2nd tier count:
SELECT f1.to_email
FROM friends f1
JOIN friends f2 on f2.to_mail = f1.to_email
WHERE f1.from_email = 'myemail'
GROUP BY 1
ORDER BY count(*) DESC
LIMIT 5
If an index is defined on to_email, this will perform very well.

MySQL Join Query - joining tables into themselves many times

I have 4 queries I need to excecute in order to suggest items to users based on items they've already expressed an interest in:
Select 5 random items the user already likes
SELECT item_id
FROM user_items
WHERE user_id = :user_person
ORDER BY RAND()
LIMIT 5
Select 50 people who like the same items
SELECT user_id
FROM user_items
WHERE user_id != :user_person
AND item_id = :selected_item_list
LIMIT 50
SELECT all items that the original user likes
SELECT item_id
FROM user_items
WHERE user_id = :user_person
SELECT 5 items the user doesn't already like to suggest to the user
SELECT item_id
FROM user_items
WHERE user_id = :user_id_list
AND item_id != :item_id_list
LIMIT 5
What I would like to know is how would I excecute this as one query?
There are a few reasons for me wanting to do this:
at the moment, I have to excecute the 'select 50 people' query 5 times and pick the top 50 people from it
I then have to excecute the 'select 5 items' query 50 * (number of items initial user likes)
Once the query has been excecuted, I intend to store the query result in a cookie (if the user gives consent to me using cookies, otherwise they don't get the 'item suggestion' at all) with the key being a hash of the query, meaning it will only fire once a day / once a week (that's why I return 5 suggestions and select a key at random to display)
Basically, if anybody knows how to write these queries as one query, could you show me and explain what is going on in the query?
This will select all items you need:
SELECT DISTINCT ui_items.item_id
FROM user_items AS ui_own
JOIN user_items AS ui_others ON ui_own.item_id = ui_others.item_id
JOIN user_items AS ui_items ON ui_others.user_id = ui_items.user_id
WHERE ui_own.user_id = :user_person
AND ui_others.user_id <> :user_person
AND ui_items.item_id <> ui_own.item_id
(please, check if result are exact same with you version - I tested it on a very small fake data set)
Next you just cache this list and show 5 items randomly, because ORDER BY RAND() is VERY inefficient (non-deterministic query => no caching)
EDIT: Added the DISTINCT to not show duplicate rows.
You can also return a most popular suggestions in descending popularity order by removing DISTINCT and adding the following code to the end of the query:
GROUP BY ui_items.item_id
ORDER BY COUNT(*) DESC
LIMIT 20
To the end of the query which will return the 20 most popular items.

MySQL select comments for friends then other comments while chronological order

This question could asked somewhere before but I could not find it, anyways.
So I have a video record that has many comments, each comment belongs to a user. The user on the other hand has many user friends.
Now, when user X signs in and view video Y I want to display the comments of his friends first (ordered by most recent). Then list other comments ordered with most recent.
I would prefer to make this logic in one single query if possible.
Thanks,
In your ORDER BY, do something like the following. Please know that I have know idea what your schema looks like, so this query won't work. But just create a 1/0 value indicating whether or not the user is a friend, and then order by that first, and then order by the posting / comment id afterwards
SELECT
posting.id,
IF(a.friend_id = b.criteria, 1, 0) AS is_friend
...
ORDER BY is_friend DESC, posting.id DESC
How about this
Select comment of friends of X on video Y order by date
Union
Select comment on video Y where they is not friend of X order by date
Use UNION.
(
SELECT 1 AS sort,*
FROM comments
INNER JOIN friends ON comments.poster_id=friends.friend_id
WHERE friends.id='123'
)
UNION
(
SELECT 2 AS sort,*
FROM comments
WHERE poster_id NOT IN(SELECT friend_id FROM friend WHERE id='123')
)
ORDER BY sort,time DESC

SQL Row Counting

Before I go on, I don't want to use ANY query which involves selecting all rows and counting the occurrences manually. I'm doing this in PHP by the way.
Basically, I have a bans table. Each new record/row is a new ban. The field titled user_name signifies which player was banned. Is there a way to count who has the most bans in this table? I really don't want to select every row and then count it out for each player. The table is pretty big, therefore making the mentioned solution impractical and inefficient.
This is done with COUNT() aggregates, grouping by user_name.
Get bans by user from most to least:
SELECT
user_name,
COUNT(*) as numbans
FROM bans
GROUP BY user_name
/* ordered by number of bans from greatest to least */
ORDER BY numbans DESC
Get only the most banned user:
SELECT
user_name,
COUNT(*) as numbans
FROM bans
GROUP BY user_name
ORDER BY numbans DESC
/* adjust LIMIT for how many records you want returned -- 1 gives only the first record */
LIMIT 1
In your question, you're very concerned about not wanting to count manually in PHP. Note that this is just about never necessary. RDBMS systems are designed explicitly for organizing and querying data, and are very good at doing tasks like this efficiently. Read up on GROUP BY clauses and aggregate functions in MySQL and master them. You generally shouldn't need to do it in code.
Bonus: Get all users banned 3 or more times
SELECT
user_name,
COUNT(*) as numbans
FROM bans
GROUP BY user_name
/* HAVING clause limits results of an aggregate like COUNT() (which you cannot do in the WHERE clause) */
HAVING numbans >= 3
gets the top 10 most banned (leave the limit line if you want to see all in order):
select user_name, count(user_name) as num_of_bans
from bans
group by user_name
order by num_of_bans desc
limit 10
SELECT user_name , COUNT( id )
FROM table_name
GROUP BY user_name