Linking two rows from same table MYSQL - php

As an example, let's say I want users to be friends on the website.
Say, user_id 1 and user_id 2 become friend, I add a row to my table friends:
table friends (user_id1 INT, user_id2 INT), the problem with this table is that I have to query each column to get my results.
Rows may be:
user_id1 user_id2
1 2
2 3
So to get friends of user with id 2, I need to query both columns (which make some not so practical queries), is there a better way to do this?
ie:
$u_id = 2;
SELECT * FROM friends WHERE (user_id1 = $u_id OR user_id2 = $u_id)

I would go for the less space efficient method of adding two rows one linking person A to person B and the second linking person B to person A. Then a single query on a single column will retrieve all friends.
This set up can also be used where "friends" have different privileges or levels of friendship. So person A can give B more access to their stuff than B gives to A.

You could try a different approach, storing all friends of a person inside a field friends in the table and storing everything as a json object.
This way you'd have all one person's friends in a single query and you could get tehir profile with a simple foreach query.
I've been using this for several many-to-many logics and it works flawlessly without the aid of a link table.

I think I'd prefer a UNION, something like:
SELECT user, friend FROM (
SELECT user_id1 AS user, user_id2 AS friend FROM friends
UNION
SELECT user_id2 as user, user_id1 AS friend FROM friends
) u
WHERE u.user = $u_id
(with appropriate caveats about using prepared statements instead of bare variables of course)

Related

JOIN query too slow on real database, on small one it runs fine

I need help with this mysql query that executes too long or does not execute at all.
(What I am trying to do is a part of more complex problem, where I want to create PHP cron script that will execute few heavy queries and calculate data from the results returned and then use those data to store it in database for further more convenient use. Most likely I will make question here about that process.)
First lets try to solve one of the problems with these heavy queries.
Here is the thing:
I have table: users_bonitet. This table has fields: id, user_id, bonitet, tstamp.
First important note: when I say user, please understand that users are actually companies, not people. So user.id is id of some company, but for some other reasons table that I am using here is called "users".
Three key fields in users_bonitet table are: user_id ( referencing user.id), bonitet ( represents the strength of user, it can have 3 values, 1 - 2 - 3, where 3 is the best ), and tstamp ( stores the time of bonitet insert. Every time when bonitet value changes for some user, new row is inserted with tstamp of that insert and of course new bonitet value.). So basically some user can have bonitet of 1 indicating that he is in bad situation, but after some time it can change to 3 indicating that he is doing great, and time of that change is stored in tstamp.
Now, I will just list other tables that we need to use in query, and then I will explain why. Tables are: user, club, club_offer and club_territories.
Some users ( companies ) are members of a club. Member of the club can have some club offers ( he is representing his products to the people and other club members ) and he is operating on some territory.
What I need to do is to get bonitet value for every club offer ( made by some user who is member of a club ) but only for specific territory with id of 1100000; Since bonitet values are changing over time for each user, that means that I need to get the latest one only. So if some user have bonitet of 1 at 21.01.2012, but later at 26.05.2012 it has changed to 2, I need to get only 2, since that is the current value.
I made an SQL Fiddle with example db schema and query that I am using right now. On this small database, query is working what I want and it is fast, but on real database it is very slow, and sometimes do not execute at all.
See it here: http://sqlfiddle.com/#!9/b0d98/2
My question is: am I using wrong query to get all this data ? I am getting right result but maybe my query is bad and that is why it executes so slow ? How can I speed it up ? I have tried by putting indexes using phpmyadmin, but it didn't help very much.
Here is my query:
SELECT users_bonitet.user_id, users_bonitet.bonitet, users_bonitet.tstamp,
club_offer.id AS offerId, club_offer.rank
FROM users_bonitet
INNER JOIN (
SELECT max( tstamp ) AS lastDate, user_id
FROM users_bonitet
GROUP BY user_id
)lastDate ON users_bonitet.tstamp = lastDate.lastDate
AND users_bonitet.user_id = lastDate.user_id
JOIN users ON users_bonitet.user_id = users.id
JOIN club ON users.id = club.user_id
JOIN club_offer ON club.id = club_offer.club_id
JOIN club_territories ON club.id = club_territories.club_id
WHERE club_territories.territory_id = 1100000
So I am selecting bonitet values for all club offers made by users that are members of a club and operate on territory with an id of 1100000. Important thing is that I am selecting club_offer.id AS offerId, because I need to use that offerId in my application code so I can do some calculations based on bonitet values returned for each offer, and insert data that was calculated to the field "club_offer.rank" for each row with the id of offerId.
Your query looks fine. I suspect your query performance may be improved if you add a compound index to help the subquery that finds the latest entry from users_botinet for each user.
The subquery is:
SELECT max( tstamp ) AS lastDate, user_id
FROM users_bonitet
GROUP BY user_id
If you add (user_id, tstamp) as an index to this table, that subquery can be satisfied with a very efficient loose index scan.
ALTER TABLE users_bonitet ADD KEY maxfinder (user_id, tstamp);
Notice that if this users_botinet table had an autoincrementing id number in it, your subquery could be refactored to use that instead of tstamp. That would eliminate the possibility of duplicates and be even more efficient, because there's a unique id for joining. Like so.
FROM users_botinet
INNER JOIN (
SELECT MAX(id) AS id
FROM users_botinet
GROUP BY user_id
) ubmax ON users_botinet.id = ubmax.id
In this case your compound index would be (user_id, id.
Pro tip: Don't add lots of indexes unless you know you need them. It's a good idea to read up on how indexes can help you. For example. http://use-the-index-luke.com/

Simple MYSQL data query?

Just a really simple question. In my SQL database I have a column named "friend_count" in table "users" to record all friends the logged in user has. This works with UserID numbers and I am trying to figure out how to record them all. Here's my example:
USER #29 becomes friends with USER #422
In the user database under USER #29's info in the column "friend_count" there will then be the number "422", if he then becomes friends with USER #500, it will show "422, 500" and so on... If he deletes one, that particular number is removed from the box. The script will then search these numbers through the user database to show a list of the people you are friends with.
How can I do this? Any ideas? Thanks!
Any time you have a delimited list of values in a column it's almost always a sign that the data model is incorrect. (Actually, I can't think of an exception to this, but I'll stick with "almost always" just to be safe.)
In this case you have two types of entities:
User
Friendship
A friendship, though not a physical object, is a conceptual entity in and of itself. It connects two users and can add more information related to the friendship itself but not necessarily to the two users.
So you might have tables like this:
User
--------
ID
Name
etc.
Friendship
--------
ID
OriginatingUser
AcceptingUser
BecameFriendsOn
etc.
So OriginatingUser might be the user who sent the friend request, and AcceptingUser might be the user who accepted it. BecameFriendsOn is the date it was accepted. You'd probably want to have statuses and other dates to keep track of pending requests, denied requests, etc. But all of this information is related to the friendship, not necessarily to the users.
The concept you're looking to understand here is called a Foreign Key. The OriginatingUser and AcceptingUser columns are the same data type as the ID column on the User table. You would create the Friendship table such that those columns are foreign keys to the User table, this enforces the integrity of the data so that you can't create a Friendship record without two valid and existing User records.
Then to get the list of friends, you'd join the tables in a query. Perhaps something like this:
SELECT
User.Name
FROM
Friendship
INNER JOIN User ON Friendship.AcceptingUser = User.ID
WHERE
Friendship.OriginatingUser = ?
When supplied with the ID of the originating user, this would get all of the names of users to whom that user sent a friend request. You can further build on the query to also get users who sent this same user a friend request, and so on. By making use of the key foreign key relationships between tables which represent different types of entities in the system, you can construct very robust queries to view that data in lots of different ways.
You are describing a relationship between two entities so you can create a table to store the details of that relationship.
Suppose that you have your user table with a userid column and other columns. You can then create a friends table with two columns that are both foreign keys to the user table.
friends = (user, friend)
Thus, for each friend that user #29 gets you need to add a row into the friends table. For example:
USER
ID NAME ...
29 Sam
30 Henry
32 Jane
Friends
user friend
29 30
29 32
Sam is friends with both Jane and Henry but Jane and Henry are not friends.
I would create a table "friendship" with two cols "user" and "friend", then you start adding pairs of user/friend ids
users table:
id, username, whatever else
friends table:
relationship_id, user_id, user_friend_id
example query to get a list of IDs that belong to the users friends:
SELECT f.user_friend_id FROM users u
LEFT JOIN friends f ON f.user_id = u.id
WHERE u.id = {$user_id}
A very simple approach, assuming that if UserA becomes friends with UserB , then UserB also becomes friends with UserA.
Usually, Comma Separated Lists are not recommended, as they will become a pain when the list is very large. A simpler approach will be, make a table friends with columns user_id and friend_id Where user_id and friend_id are the respective UserIDs .
Now when you want to add a friend to someone's list, use the following :
INSERT INTO users (user_id,friend_id) VALUES(UserA,UserB),(UserB,UserA)
Now when you execute the above query, you will have 2 new rows in your friends table :
user_id friend_id
UserA UserB
UserB UserA
When you want to get the list of a user's friends, use :
SELECT friend_id FROM friends WHERE user_id=(Your user's ID)
This will return a row one by one, and hence will give you all the IDs of friends of a particular user.
Now when you want to delete a friend, use :
DELETE FROM friends WHERE (user_id,friend_id) IN ((user_id,friend_id),(friend_id,user_id))
This removes both the rows from the table, which means that the relationship between UserA and UserB is deleted, and neither of them is friend of each other.

Showing users only their information

Okay so im new to databases, and have created a site with a users table, and i also hace a list table, where suers can insert list items, however when they log in everyones list is appearing, how can i link the user table to the lists table, is it creating the same field in each one and using a foreign key? Sorry I am very new to this. Appreciate any help
I think you can just use user_id on both tables to fix this. Let me give an example:
Table A (user_id, username, password)
Table B (list_item_id, user_id , any_other_attribute)
When you design your tables like this a simple sql call will do what you need like:
SELECT 'list_item_id','any_other_attribute' FROM Table B Where user_id=$user_id
Where $user_id is the user_id of the one's who loginned your system.
Also by your question, i suggest you to read about these : 'sessions' , 'sql queries' , 'generating sql query results' on your choice of programming language.
It calls MANY MANY relationnship. There mus be 1 table with fields user_id and field_id that will join this 2 tables

MySQL search different tables and only fetch specfic IDs

I am trying to create an algorithm to sort out the most relevant data for a specific user_id.
I imagine my end result to be an array with KEY as the found USER_ID and the VALUE to be the number of times the specific USER_ID has been found in the different rows.
So, I need to look through different rows in different tables and look for where CURRENT_USER_ID (lets say id: 30) exists, and then find the RECIEVER_ID, that is the user which was communicated to. This is pretty hard to explain, but lets take an example:
I have a table called: edu_posts, which contains wallposts and comments to theese. The are different values in this table, but the ones we should focus on is: post_author and post_reciever. We then have to look for all the rows where post_author equals 30 (the test example; just needs to be the current users id) and then print out the post_reciever IDS. This would be easy enough with a single query, but lets say we have to find data in 5 or 10 different tables, that 10 different queries, which is a lot.
We also have a table called edu_folowers. There we have to look for where follow_author equals 30 (the test example; just needs to be the current users id), and then print out the follow_user ID. Again, to find out who the current user have interest in.
I image the final mysql_fetch to look something like this:
user_id => 25
times_found => 5
user_id => 11
times_found => 3
user_id => 95
times_found => 1
etc.
Can this be done using a single query, maybe using JOIN? And even maybe count the results IN the query, so I don't have to do this manually in the PHP code.
Or should I create a mysql_query for every table I wish to get data from, and then manage the data afterwards using PHP? This sounds like the easiest way to me, but also the most inefficient relating to script optimization.
I have tried out with the following test-query:
SELECT
u.user_id AS user_id,
f.follow_user AS user_id_follow,
p.post_reciever AS user_id_posts
FROM
`edu_posts` u
LEFT JOIN `edu_followers` f ON f.follow_author = '30'
LEFT JOIN `edu_posts` p ON p.post_author = '30' && p.post_reciever != '30'
WHERE
u.user_id = '30'
GROUP BY
f.follow_id, p.post_id
But the problem is that it outputs unexcepted results, and also I will have different values to look for, fx: user_id (not really needed, as we already know that it is 30), user_id_follow, user_id_posts, and so on.
I hope you understand my question, and please let me know, if you need additional information.
Many thanks in advance!
You could create a union view of all of the tables that you want to search, depending on your exact requirements you might only need to query that once.
e.g.
create view allPostTypesUnion as
select user_id, post_receiver
from edu_posts
union
select user_id, post_receiver
from different_edu_posts
union
select user_id, post_receiver
from another_different_edu_posts
then:
select post_receiver, count(*)
from allPostTypes
group by post_receiver

How to select all posts from database?

In database in table user I have three columns:
id
name
friends
In column friends are names of people who are friends with person whose name is stored in column name. Column friends looks like this:
friendname1,friendname2,friendname3,friendname4
Each of those friends have it's own row where name is equal to their name.
I also have another table called post where I have four columns:
id
name_posted
post
visible
What I would like now, is to select all posts from table post where name_posted is equal to name of the logged in user or any of his friends which are stored in column friends in table user.
Name of the logged in user is stored in variable $user.
For selecting only posts from logged in user I can use this:
$all_posts = mysqli_query($connect_db, "SELECT * FROM post WHERE name_posted='$user' AND visible='yes'");
but I don't know how to include to select also posts from his friends. Something like Facebook have, when you log in and you see your posts plus your friends posts. I don't know how they created that. Sorry for long post, I just wanted to give you detailed description.
For selecting data based on information across multiple tables I suggest reading up on MySQL Joins.
Maybe with two querys, first select friends something like this:
SELECT * FROM user WHERE name='$user'
You then have all his friends in string like this if I understood correctly :
friend1,friend2,friend3...
Explode $row['friends'] -> explode(',',$row['friends']); to get all friends names in array and then you can do another select in foreach loop to get all posts from friends and display it the way you like or you can even better do IN in query
select * from posts where name_posted IN ($row['friends'])
this is the other way, which would be longer
foreach($friendarray as $k=>$friend){
...
mysqli_query($connect_db,
"SELECT * from post where name_posted='$friend' AND visible='yes'");
...
}
and also the query you already have to get own posts. Don't forget to escape all values and stuff...
You could also join two tables but I cant write that query from my mind , would have to sit down and try it with real data but that would be ultimate solution for you.
Don't know if I hit it right but shout if you need help
You can do it in a single query with something like:
SELECT p.*
FROM user u
join post p
on (u.name = p.name or concat(',',u.friends,',') like concat('%,',p.name,',%')
AND p.visible='yes'
WHERE u.name='$user'
- but the performance is likely to be much poorer than if you had a properly normalised design for the relationship between users and their friends.
You should probably reconsider the design of your DB. From what you've described, you should have three tables: userinfo, friendship, posts.
That way, you can then do a quick union between all three tables to get what you're looking for in one query.
Let me talk about how I will solve that case if its required from me.
I will use the following tables
users - user_id, name, email and whatever I need
relations - relation_id, user_id, fiend_id -- this table will relate one user to other
posts - post_id, user_id, content, visible
Now basically we have everything needed.
For selecting all data from the currently logged user and all of his friend I will use the following query:
SELECT * FROM posts
WHERE Visible = 1
AND (
user_id IN (SELECT friend_id FROM relations WHERE user_id = 1)
OR
user_id = 1)
What I do here, I use nested queries to accomplish that.
The interesting part here is the nested query - which basically return "array" with the ids of my friends. MySQL IN function check the user_id against that "array" After that in the main parentheses I add OR user_id = 1 which is the my user_id.
In that way I should have the content which I want to use to my feed.
However this code I away from fast and optimized but it's good example how to do that.

Categories