MYSQL IN alternative - mysql

I am coding a social networking type website, users have friends and I am trying to create a news feed with actions that their friends have done
I have three tables
Users
id | username
---------------------
1 | john
2 | nicole
3 | bob
Friends
id | uid | who
----------------------------
1 | 1 | 2
2 | 2 | 1
3 | 2 | 3
4 | 3 | 2
5 | 3 | 1
6 | 1 | 3
Actions
id | owner | to_id | message
-------------------------------------------
1 | 3 | 2 | 'hello'
2 | 3 | 1 | 'yoooo'
Since 'john' is friends with 'bob, 'john' should be able to see 'bob' action to 'nicole'
EDIT:: I also want to get the actions from the other direction, if 'nicole' sends an action to 'bob'
My current solution is:
have a string that contains all the friends of a user: $friends = '1,2,3,4....etc'
query:
SELECT
`Actions`.*
FROM
`Actions`
WHERE
(`Actions`.`to_id` IN (${friends}) OR `Actions`.`from_id` IN (${friends}))
AND
( `Actions`.`to_id` != (${logged_id} AND `Actions`.`from_id` != (${logged_id})
ORDER BY
`Actions`.`time` ASC
LIMIT
15
The above query works, but my problem is when users start have hundreds of friends this query will be horrendously slow, what can I do as an alternative to prevent this?

If you want to try a different and better solution, please try using any popular graph database. It is well suited for your current requirement.

What about using a JOIN to achieve this?
SELECT actions.*
FROM actions a
JOIN friends f ON f.who = a.owner
WHERE f.uid = (current user's ID)
LIMIT 15
(Returns 15 actions owned by friends of the current user.)

Related

SQL: Get top users that follow same users

I have the table followers that looks like this:
id |follower_id|followee_id|
1 | 1 | 2 |
2 | 1 | 3 |
2 | 1 | 4 |
3 | 2 | 3 |
4 | 2 | 4 |
5 | 3 | 2 |
6 | 4 | 6 |
Where follower is a user_id and followee is the user they follow.
How can I find the users that have the most common followees with let's say user 1?
The results need to be ordered by number of common followees.
For example for the current table the results for user 1 would be:
follower_id|common_followees|
2 | 2 |
3 | 1 |
As you can see 4 does not appear in results since it has no common followees with user 1
I hope I explained the problem right.
Thank You.
This is a self-join and aggregation:
select f.follower_id, count(*) as num_common_followees
from followers f join
followers f1
on f.followees = f1.followees and f1.follower_id = 1
group by f.follower_id;
You can add where f.follower_id <> 1. I like to leave that row in as a validation check.

JOIN ids with 0 as a parent ids, to a query of ids that have distinct parent ids

I'm trying to make a list of user related discussions.
Imagine, if you will:
posts
-------------------------------------------------------------------
ID | PID | FID | TITLE | USER
1 | 0 | 1 | Hello World | User 1
2 | 0 | 23 | Endangered Squirrels & How to Cook Them | Eddy
3 | 1 | 1 | Re: Hello World | Eddy
4 | 1 | 1 | Re: Hello World | Clark
5 | 0 | 3 | Any Vacation Suggestions? | Clark
6 | 5 | 3 | Re: Any Vacation Suggestions? | Eddy
7 | 5 | 3 | Re: Any Vacation Suggestions? | Clark
8 | 5 | 3 | Re: Any Vacation Suggestions? | Ellen
To show all of Eddy's posts I was using a query selecting posts with DISTINCT parents ids(pid) and thought it was working. I soon realized that 0 was only consider DISTINCT once and if he had created a new topic and not replied, it wasn't being listed if it wasn't his first.
So how do I get a list of ids with DISTINCT pid and pid != 0, and join it with a query of ids that have pid = 0 where the user = Eddy
This is the first question I've ever asked so forgive me if it's not perfect.
UPDATED WITH QUERY
I have since switched to using GROUP BY so I can fetch the post's id, but the issue is still the same
I was asked to provide my query. This is the one I'm using that only provides 1 row with pid as 0. I also updated my table layout above to show forum id, in the query below it only pulls topics from a list of public forums.
SELECT id, pid FROM posts WHERE fid IN (1,2,3) AND author = 'Eddy' GROUP BY pid ORDER BY IF(latest > timestamp, latest, timestamp) DESC LIMIT 1, 5;
thank you for any and all help.
I was able to do what I wanted with a simple IF in the GROUP BY, it was so simple.
SELECT id, pid
FROM posts
WHERE fid IN (1,2,3)
AND author = 'Eddy'
GROUP BY IF(pid > 0, pid, id)
ORDER BY IF(latest > timestamp, latest, timestamp) DESC
LIMIT 1, 5;

MySQL Order by before group by in view (No Sub-Querys)

I realize this question has been asked quite a few times, however i haven't managed to find a working solution for my case.
Essentially my problem arises because MySQL Doesn't allow sub-querys in views.
I found a few workarounds but they don't seem to work.
In more detail...
My first table (competitions) stores a users competitions:
id_tournament | id_competition | id_user | result
-------------------------------------------------
1 | 1 | 1 | 10
1 | 1 | 2 | 30
1 | 2 | 1 | 20
1 | 2 | 3 | 50
1 | 3 | 2 | 90
1 | 3 | 3 | 100
1 | 3 | 4 | 85
In this example there are three competitions:
(
user1 vs. user2,
user1 vs. user3,
user2 vs. user3 vs. user4
)
My problem is that i need to define a view that gives me the winners in each competition.
Expected Result:
id_tournament | id_competition | id_winner
------------------------------------------
1 | 1 | 2
1 | 2 | 3
1 | 3 | 3
This can be solved with the query:
SELECT
id_tournament,
id_competition,
id_user as id_winner
FROM (
SELECT * FROM competitions ORDER BY result DESC
) x GROUP BY id_tournament, id_competition
This query however uses a subquery (not allowed in views), so my first solution was to define a 'helper view'as :
CREATE VIEW competitions_helper AS (
SELECT * FROM competitions ORDER BY result DESC
);
CREATE VIEW competition_winners AS (
SELECT
id_tournament,
id as id_competition,
id_user as winner
FROM competitions_helper GROUP BY id_tournament, id_competition
);
However this does not seem to give the correct result.
It's result will then be:
id_tournament | id_competition | id_winner
------------------------------------------
1 | 1 | 1
1 | 2 | 1
1 | 3 | 1
What i don't understand is why it works when i use Sub-querys and why it gives a different result with the exact same statement in a view.
Any help is appreciated, thanks alot.
This is due to the GROUP BY behaviour.
In this case, the server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate, which is probably not what you want.
I would solve the problem in this way:
CREATE VIEW competitions_helper AS (
SELECT id_tournament,
id_competition,
MAX(result) as winning_result
FROM competitions
GROUP BY id_tournament,
id_competition
);
CREATE VIEW competition_winners AS (
SELECT c.id_tournament,
c.id_competition,
c.id_user
FROM competitions c
INNER JOIN competitions_helper ch
ON ch.id_tournament = c.id_tournament
AND ch.id_competition = c.id_competition
AND ch.winning_result = c.result
);

Many to many query with 3 tables

I have 3 tables: WAS, USER and connection table WAS_USER. The thing I would like to achieve is to get all the rows from table WAS and if there are many users to the same WAS I would like to have one row for each one.
for example:
WAS:
id | name
1 | 'was1'
2 | 'was2'
USER:
id | name
1 | 'user1'
2 | 'user2'
WAS_USER:
userId | wasId
1 | 1
2 | 1
So after queering I need to get this:
wasrId | userId | wasName | userName
1 | 1 | 'was1' | 'user1'
1 | 2 | 'was1' | 'user2'
1 | 2 | 'was1' | 'user2'
Ordinary join between the 3 tables will give my only rows from WAS, what I need is some kind of left join on 3 tables.
Ok, here is what worked for me:
SELECT *
FROM
(SELECT * FROM WAS W LEFT JOIN WAS_USER WU ON W.id=WU.wasID) TMP LEFT JOIN USER U ON TMP.userId=U.userId
Don't know about the efficiency though...

Best way to limit results in MySQL with user subcategories

I am trying to essentially solve for the following:
1) Find all users in the system who ONLY have programID 1.
2) Find all users in the system who have programID 1 AND any other active program.
My tables structures (in very simple terms are as follows):
users
userID | Name
================
1 | John Smith
2 | Lewis Black
3 | Mickey Mantle
4 | Babe Ruth
5 | Tommy Bahama
plans
ID | userID | plan | status
---------------------------
1 | 1 | 1 | 1
2 | 1 | 2 | 1
3 | 1 | 3 | 1
4 | 2 | 1 | 1
5 | 2 | 3 | 1
6 | 3 | 1 | 0
7 | 3 | 2 | 1
8 | 3 | 3 | 1
9 | 3 | 4 | 1
10 | 4 | 2 | 1
11 | 4 | 4 | 1
12 | 5 | 1 | 1
I know I can easily find all members with a specific plan with something like the following:
SELECT * FROM users a JOIN plans b ON (a.userID = b.userID) WHERE b.plan = 1 AND b.status = 1
but this will only tell me which users have an 'active' plan 1.
How can I tell who ONLY has plan 1 (in this case only userID 5) and how to tell who has plan 1 AND any other active plan?
Update: This is not to get a count, I will actually need the original member information, including all the plans they have so a COUNT(*) response may not be what I'm trying to achieve.
Okey..., You have mentioned that you don't want a count of the other plans that particular user hold, but according to your requirement you can have optimum output like,
this many user have plan 1 active and this is the count of the other active plan he have exept plan one.
SELECT u.userID,
u.Name,
p.Id AS Plan1ActiveId,
(SELECT COUNT(*) FROM plans sub where sub.userID = u.userID and sub.plan <> 1 and sub.status = 1) AS OtherActivePlanCount
FROM users u JOIN plans p ON (u.userID = p.userID)
where p.plan = 1 AND p.status = 1;
or if you want to drill it down towards the details of the plan you need to apply second query that bring you other plans' detail.
Well you can make use of the COUNT combined with a GROUP BY and the HAVING clause.
For example:
SELECT * FROM users a JOIN plans b ON (a.userID = b.userID) GROUP BY b.user_ID HAVING COUNT(*) = 1 AND b.plan = 1
This will group rows by user id, and only include rows where the count = 1 (only 1 line in the plans table)
Note - I ended up simplifying the overall concept in my head. I simply did a subselect of all members who I knew had the plan I was looking to target. I then did an overall select of all plans by members who either had IDs that were IN or NOT IN the subselect of known ids).
After testing, this was much less resource intensive and a bit easier to use than the suggestions but appreciate the help.