A question about mysql joins - php

I'm working on a website, and I'm implementing a friend system. People can become friends and that will unlock some stuff which is not important. It is coded in PHP with MySQL as database.
Lets say this is my account table
id | name | picture
0 | Jorik | Cat.jpg
1 | Joost | Fish.jpg
2 | Henk | Ferret.png
This is the friend table(Is this a good way to do this?)
id | user id | friend id | invite date | accepted
0 | 0 | 1 | 123 | 0
1 | 2 | 0 | 456 | 1
2 | 1 | 2 | 123 | 1
The way I check if they are friends is as following (Could be an error in it, but you get the idea).
$fid = friend id, $uid = user id.
WHERE ( (`userid` = '{$uid}' && `friendid` = '{fid}' ) || (`friendid` = '{$uid}' && `userid` = '{$fid}' ))
First of all, is this an efficient way of doing this or is there a better way?
If I wanted to get the list of friends a user has I can run this query
WHERE (`userid` = '{$uid}' || `friendid` = '{$uid}')
now if I would run this query for the user 0, it would return
id | user id | friend id | invite date | accepted
0 | 0 | 1 | 123 | 0
1 | 2 | 0 | 456 | 1
I have worked with MySQL joins before but I can't figure out how to return something like the following result
id | friend id | invite date | accepted | Name | Picture
0 | 1 | 123 | 0 | Joost | Fish.jpg
1 | 2 | 456 | 1 | Jorik | Ferret.png
It would have to check if it should join the friend id or the user id with id from the account table. (The invited person is the friend id, the one who invited him will get the user id.)
I hope I gave enough information.
Can anyone help me with this?
Thanks in advance,
Jorik

My $0.02:
1) Normalize your data: get rid of the either 'userid' or 'id'. You only need one unique column for accounts.
2) Make a table for 'friends' like so:
( approximately )
create table Friends( UserId int unsigned, FriendUserId int unsigned, index( UserId, FriendUserId ));
Then add a row for each 'friend' where 'FriendUserId' is the account id (see #1) for each person.
3) Make a table for 'invites' like so:
(appx)
create table Invites ( UserId int unsigned, FriendId int unsigned, InviteDate datetime default timestamp, Accepted tinyint(1) default 0, primary key( UserId, FriendId ));
Add a row for each invite and update if/when its accepted.
Now to get a persons friends:
select * from Accounts a join Friends f on a.UserId = f.UserId where a.UserId = ;
to get Invites:
select * from Accounts a join Invites i on a.UserId = i.UserId
And so on...
.. but more than anything.. avoid duplicating data ..

If I understand your question correctly, something like this may work:
SELECT a.user_id as orig_user_id, f.*, ab.*
FROM account a
JOIN friends f
ON f.user_id=a.id
JOIN account ab
ON f.friend_id=ab.user_id
WHERE a.id={$uid}
I haven't tested this sql, but you get the picture. Basically, select the row for the user whose friends you are trying to locate, then join their friends rows, and then join the user account rows for their friends. You may need to alias some column names to make things clear in your code. You will have duplicates because you are double joining the account table.

Related

MySQL ORDER BY different table column but

I've been struggling a lot with this and it's really complicated so please focus because it's even hard to explain.
I have a table friends with columns:
id, friend1, friend2, since
And I want to display all user's friends ordered by the activity of that user from different table (table name: accinfo, column: lastact) where value of lastact is a php time.
The problem is that I don't know which column is the friend... it could either be friend1 or friend2 but that depends...
How could I find out which column is the friend's name and not the name of user? I obviously need to check it in the SQL itself to get the friends sorted out by the latest activity. Thank you.
Tables:
friends
id | friend1 | friend2 | since
1 | bob | joe | null
2 | kate | jane | null
3 | bob | robby | null
accinfo
id | username | lastact
1 | bob | 1483323711
2 | joe | 1483323701
3 | kate | 1483323642
4 | jane | 1483311256
5 | robby | 1483321234
One method is a conditional join:
select f.*, ai.lasact
from friends f join
accinfo ai
on (ai.userid = f.friend1 and friend2 = $userid) or
(ai.userid = f.friend2 and friend1 = $userid)
where $userid in (f.friend1, f.friend2)
order by lastacct desc;
Try this,
SELECT acf.username FROM friends fds, accinfo acf WHERE (acf.username=fds.friend1 OR acf.username=fds.frien2) ORDER BY acf.lastact DESC

Selecting from more than two tables using join selects duplicates

I have 3 table on my mysql db (user, follower, post), I wrote an sql, that get all posts from the people a user followed.
sql
SELECT post.* FROM post JOIN
follower ON post.owner_user_id = follower.user_id AND
follower.follower_id = "3"
result
id | owner_user_id | content
2 | 1 | why are all my senior developers jerks?
3 | 1 | PHP7 in your face node.js
user table
id | username | password
1 | user1 | 12345678
2 | user2 | 12345678
3 | user3 | 12345678
follower table
user_id | follower_id
3 | 1
3 | 2
1 | 3
post table
id | owner_user_id | content
1 | 2 | reading a 1k+ page on mysql, do i need to?
2 | 1 | why are all my senior developers jerks?
3 | 1 | PHP7!, in your face node.js
3 | 3 | I posted
so now am trying to select post of people the user is following and the posts of the user
I tried this sql
sql
SELECT post.* FROM post JOIN
follower ON post.owner_user_id = follower.user_id AND
follower.follower_id = "3" JOIN
user ON post.owner_user_id = user.id= "3"
result
null
Please is what am trying to achieve with the sql possible,
if(possible) {"what_am_i_doing_wrong"}();
edits
user_id 3 has a post, still running the above sql returns null, was hoping if the user had no post, only post of the people the user is following is returned
select post.* from
(select user_id from follower where follower_id = 3 union select 3) blah
join post on blah.user_id = post.owner_user_id;
Try this:
SELECT Distinct post.* FROM post JOIN
follower ON post.owner_user_id = follower.user_id JOIN
user ON follower.user_id = user.id WHERE user.id = 3
You shuldn't use conditions in JOIN statement, use WHERE instead.

How to find intersect rows when condition depend on some columns in one table

Table subscribe
subscriber | subscribeto (columns)
1 | 5
1 | 6
1 | 7
1 | 8
1 | 9
1 | 10
2 | 5
2 | 6
2 | 7
There are two users that have id 1 and 2. They subscribe to various user and I inserted these data to table subscribe. Column subscriber indicates who is subscriber and column subscribeto indicates who they've subscribe to. From the above table can conclude that; user id=1 subscribed to 6 users
user id=2 subscribed to 3 users
I want to find mutual of subscription (like Facebook is mutual friends)
user 1 subscribe to user 5,6,7,8,9,10
user 2 subscribe to user 5,6,7
So, mutual subscription of user 1 and 2 are: 5,6,7
And I'm trying to create SQL statement..
I give you user table for my SQL statement and I think we can use only subscribe table but I can't figure out.
Table user
userid (columns)
1
2
3
...
...
SQL
"select * from user where (select count( 1 ) from subscribe where subscriber = '1' and subscribeto = user.userid) and (select count( 1 ) from subscribe where subscriber = '2' and subscribeto = user.userid);"
This SQL can work correctly, but it very slow for thousands of columns. Please provide better SQL for me, Thanks.
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(subscriber INT NOT NULL
,subscribeto INT NOT NULL
,PRIMARY KEY(subscriber,subscribeto)
);
INSERT INTO my_table VALUES
(1,5),
(1,6),
(1,7),
(1,8),
(1,9),
(1,10),
(2,5),
(2,6),
(2,7);
SELECT x.subscribeto mutual_friends
FROM my_table x
JOIN my_table y
ON y.subscribeto = x.subscribeto
WHERE x.subscriber = 1
AND y.subscriber = 2;
+----------------+
| mutual_friends |
+----------------+
| 5 |
| 6 |
| 7 |
+----------------+

JOIN 2 Tables with different columns

I Have 2 Tables, One For New Pictures and One For New Users, i want to create like a wall that mixes the latest actions so it'll show new users & pictures ordered by date.
What i want is a single query and how to know inside the loop that the current entry is a photo or user.
TABLE: users
Columns: id,username,fullname,country,date
TABLE: photos
Columns: id,picurl,author,date
Desired Output:
Daniel from California Has just registred 5mins ago
New Picture By David ( click to view ) 15mins ago
And so on...
I'm begging you to not just give me the query syntax, i'm not pro and can't figure out how to deal with that inside the loop ( i only know how to fetch regular sql queries )
Thanks
You could use an union:
SELECT concat(username, " from ", country, " has just registered") txt, date FROM users
UNION
SELECT concat("New picture By ", username, " (click to view)") txt, date FROM photos INNER JOIN users ON author=users.id
ORDER BY date DESC
LIMIT 10
This assumes that author column in photos corresponds to the users table id. If author actually is a string containing the user name (which is a bad design), you'll have to do this instead:
SELECT concat(username, " from ", country, " has just registered") txt, date FROM users
UNION
SELECT concat("New picture By ", author, " (click to view)") txt, date FROM photos
ORDER BY date DESC
LIMIT 10
Make sure you have an index on date in both tables, or this will be very inefficient.
I've put together this little example for you to look at - you might find it helpful.
Full script can be found here : http://pastie.org/1279954
So it starts with 3 simple tables countries, users and user_photos.
Tables
Note: i've only included the minimum number of columns for this demo to work !
drop table if exists countries;
create table countries
(
country_id tinyint unsigned not null auto_increment primary key,
iso_code varchar(3) unique not null,
name varchar(255) unique not null
)
engine=innodb;
drop table if exists users;
create table users
(
user_id int unsigned not null auto_increment primary key,
country_id tinyint unsigned not null,
username varbinary(32) unique not null
-- all other detail omitted
)
engine=innodb;
drop table if exists user_photos;
create table user_photos
(
photo_id int unsigned not null auto_increment primary key,
user_id int unsigned not null,
-- all other detail omitted
key (user_id)
)
engine=innodb;
The important thing to note is that the primary keys of users and photos are unsigned integers and auto_increment (1,2,3..n) so I can find the latest 10 users and 10 photos by ordering by their primary keys (PK) descending and add a limit clause to restrict the number of rows returned.
-- change limit to increase rows returned
select * from users order by user_id desc limit 2;
select * from user_photos order by photo_id desc limit 2;
Test Data
insert into countries (iso_code, name) values ('GB','Great Britain'),('US','United States'),('DE','Germany');
insert into users (username, country_id) values ('f00',1),('bar',2),('stack',1),('overflow',3);
insert into user_photos (user_id) values (1),(1),(2),(3),(1),(4),(2),(1),(4),(2),(1);
So now we need a convenient way (single call) of selecting the latest 10 users and photos. The two tables are completely different so a union isnt going to be the best approach so what we'll do instead is write a stored procedure that returns two resultsets and handle generating the wall (merge resultsets) in our php script.
Stored procedure
Just a wrapper around some SQL code - think of it like SQL's version of a function call
drop procedure if exists list_latest_users_and_photos;
delimiter #
create procedure list_latest_users_and_photos()
begin
-- last 10 users
select
'U' as type_id, -- integer might be better
u.user_id,
u.country_id,
u.username,
-- other user columns...
c.name as country_name
from
users u
inner join countries c on u.country_id = c.country_id
order by
u.user_id desc limit 10;
-- last 10 photos
select
'P' as type_id,
up.photo_id,
up.user_id,
-- other photo columns...
u.username
-- other user columns...
from
user_photos up
inner join users u on up.user_id = u.user_id
order by
up.photo_id desc limit 10;
end #
delimiter ;
Testing
To test our stored procedure all we need to do is call it and look at the results.
mysql> call list_latest_users_and_photos();
+---------+---------+------------+----------+---------------+
| type_id | user_id | country_id | username | country_name |
+---------+---------+------------+----------+---------------+
| U | 4 | 3 | overflow | Germany |
| U | 3 | 1 | stack | Great Britain |
| U | 2 | 2 | bar | United States |
| U | 1 | 1 | f00 | Great Britain |
+---------+---------+------------+----------+---------------+
4 rows in set (0.00 sec)
+---------+----------+---------+----------+
| type_id | photo_id | user_id | username |
+---------+----------+---------+----------+
| P | 11 | 1 | f00 |
| P | 10 | 2 | bar |
| P | 9 | 4 | overflow |
| P | 8 | 1 | f00 |
| P | 7 | 2 | bar |
| P | 6 | 4 | overflow |
| P | 5 | 1 | f00 |
| P | 4 | 3 | stack |
| P | 3 | 2 | bar |
| P | 2 | 1 | f00 |
+---------+----------+---------+----------+
10 rows in set (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
Now we know that works we can call it from php and generate the wall.
PHP Script
<?php
$conn = new Mysqli("localhost", "foo_dbo", "pass", "foo_db");
$result = $conn->query("call list_latest_users_and_photos()");
$users = array();
while($row = $result->fetch_assoc()) $users[] = $row;
$conn->next_result();
$result = $conn->use_result();
$photos = array();
while($row = $result->fetch_assoc()) $photos[] = $row;
$result->close();
$conn->close();
$wall = array_merge($users, $photos);
echo "<pre>", print_r($wall), "</pre>";
?>
Hope you find some of this helpful :)

How to determine order for new item?

I have a members table in MySQL
CREATE TABLE `members` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(65) collate utf8_unicode_ci NOT NULL,
`order` tinyint(3) unsigned NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
And I would like to let users order the members how they like.
I'm storing the order in order column.
I'm wondering how to insert new user to be added to the bottom of the list.
This is what I have today:
$db->query('insert into members VALUES (0, "new member", 0)');
$lastId = $db->lastInsertId();
$maxOrder = $db->fetchAll('select MAX(`order`) max_order FROM members');
$db->query('update members
SET
`order` = ?
WHERE
id = ?',
array(
$maxOrder[0]['max_order'] + 1,
$lastId
));
But that's not really precise while when there are several users adding new members at the same time, it might happen the MAX(order) will return the same values.
How do you handle such cases?
You can do the SELECT as part of the INSERT, such as:
INSERT INTO members SELECT 0, "new member", max(`order`)+1 FROM members;
Keep in mind that you are going to want to have an index on the order column to make the SELECT part optimized.
In addition, you might want to reconsider the tinyint for order, unless you only expect to only have 255 orders ever.
Also order is a reserved word and you will always need to write it as `order`, so you might consider renaming that column as well.
Since you already automatically increment the id for each new member, you can order by id.
I am not sure I understand. If each user wants a different order how will you store individual user preferences in one single field in the "members" table?
Usually you just let users to order based on the natural order of the fields. What is the purpose of the order field?
Usually I make all my select statements order by "order, name"; Then I always insert the same value for Order (either 0 or 9999999 depending on if I want them first or last). Then the user can reorder however they like.
InnoDB supports transactions. Before the insert do a 'begin' statement and when your finished do a commit. See this article for an explanation of transactions in mySql.
What you could do is create a table with keys (member_id,position) that maps to another member_id. Then you can store the ordering in that table separate from the member list itself. (Each member retains their own list ordering, which is what I assume you want...?)
Supposing that you have a member table like this:
+-----------+--------------+
| member_id | name |
+-----------+--------------+
| 1 | John Smith |
| 2 | John Doe |
| 3 | John Johnson |
| 4 | Sue Someone |
+-----------+--------------+
Then, you could have an ordering table like this:
+---------------+----------+-----------------+
| member_id_key | position | member_id_value |
+---------------+----------+-----------------+
| 1 | 1 | 4 |
| 1 | 2 | 1 |
| 1 | 3 | 3 |
| 1 | 4 | 2 |
| 2 | 2 | 1 |
| 2 | 3 | 2 |
+---------------+----------+-----------------+
You can select the member list given the stored order by using an inner join. For example:
SELECT name
FROM members inner join orderings
ON members.member_id = orderings.member_id_value
WHERE orderings.member_id_key = <ID for member you want to lookup>
ORDER BY position;
As an example, the result of running this query for John Smith's list (ie, WHERE member_id_key = 1) would be:
+--------------+
| name |
+--------------+
| Sue Someone |
| John Smith |
| John Johnson |
| John Doe |
+--------------+
You can calculate position for adding to the bottom of the list by adding one to the max position value for a given id.

Categories