subscription algorithm that scales - php

Web application PHP,Mysql. Users can write articles. How to build algorithm that allows users to subscribe (follow) to other users articles and then see list of last articles added by subscribed users? Algorithm must scale, so that one user can subscribe to 10 000 users and 10 000 users can subscribe to one user and all parts works quick - when new article added and when users looks for last articles from subscribed users.

create table `user`(
`id` INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT
);
create table `subscribes_to` (
`subscriber_user_id` INT(10) NOT NULL,
`subscribed_to_user_id` INT(10) NOT NULL, # Receiver of the subscription
PRIMARY KEY(`subscribe_user_id`, `subribed_to_user_id`),
KEY `subscriber(`subscribe_user_id`),
KEY `subscribed(`subscribed_to_user_id`);
);
# Users subscribed to role 100
SELECT distinct u.* FROM user u
JOIN subscribes_to st ON st.subscriber_user_id = u.id
WHERE
st.subscribded_to_user_id = 100;
# User 100's subsriptions
SELECT distinct u.* FROM user u
JOIN subscribes_to st ON st.subscribded_to_user_id = u.id
WHERE
st.subscriber_user_id = 100;
Additional Schema to show relationship with articles:
article (
id int(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255),
body TEXT,
date_created DATETIME,
date_updated DATETIME,
author_user_id int(10)
);
# Create new article
INSERT INTO `article` VALUES (NULL, "Hello", "This is the body", NOW(), NOW(), 1);
# Find the last 10 articles posted that user 15 suscribes to
# the author of
SELECT a.* FROM article a
JOIN user ON u.id = a.author_user_id
JOIN subscribes_to st ON st.subscribed_to_user_id = u.id
WHERE st.subscriber_user_id = 15 ORDER BY a.date_created DESC LIMIT 10;

Related

Display posts from followers and logged-in user

I want to display posts from the users being followed and the logged in user using a single PHP mysql query.
I have three tables:
- Users (id, name, password)
- Posts (id, body, date, user_id)
- Followers (id, user_id, follower_id)
followers.follower_id = person who does the following
followers.user_id = person who is being followed
$_SESSION['id'] = id of the logged in user
DB::query('
SELECT users.name, posts.body
FROM users, posts, followers
WHERE posts.user_id = followers.user_id
AND users.id = posts.user_id
AND followers.follower_id = :userid',
array(':userid'=>$_SESSION['id'])
);
But the query only shows posts from the users being followed, not from the logged in user. How do I fix this?
I have already created a relation between the posts table and the users table.
CREATE TABLE `posts` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`body` varchar(160) NOT NULL DEFAULT '',
`user_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
You need to include 2 criteria in your WHERE clause: Either the creator of the post matches your :userid variable, or the follower matches your :userid.
Please use current join syntax: This makes your query a lot easier to read.
Your final query should be something like this (I'm excluding the PHP part):
SELECT users.name, posts.body
FROM posts
INNER JOIN users ON
users.id = Posts.user_id
-- use left join to handle when a
-- user has no followers
LEFT JOIN Followers ON
Followers.user_id = users.user_id
WHERE
users.id = :userid
OR followers.follower_id = :userid

Select last distinct pair

Ok we have inbox table where we keep messages that users send to each other. Here is the table:
CREATE TABLE IF NOT EXISTS `inbox` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fromid` int(10) unsigned NOT NULL DEFAULT '0',
`toid` int(10) DEFAULT NULL,
`message` text CHARACTER SET utf8 NOT NULL,
`time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `toid` (`toid`),
KEY `fromid` (`fromid`),
KEY `fromid_2` (`fromid`,`toid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
fromid and toid are id's of the users. We have their id's, times when the message is sent. What we need is a query that would return all messages that are not replied by 'our users' (admins).
Table accounts keeps track of users. To simplify:
CREATE TABLE IF NOT EXISTS `accounts` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`our` int(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
So basically, we need a query that gives us the users WHOSE messages WERE NOT ANSWERED by admins (our users), their count and the date of the last message they sent to ADMIN, ordered from last to oldest.
So far we only have some basic queries, we didn't come up with anything reasonable that I could post.
Thanks in advance.
EDIT: From what I see we first need to find last interaction from two DISTINCT users in inbox table... then check & filter only those that were sent TO our users
How about this?
SELECT i.* FROM inbox as i
WHERE (i.toid, i.fromid) NOT IN
(SELECT i2.fromid, i2.toid FROM inbox as i2 WHERE i2.`time` >= i1.`time` AND i2.id = 1);
Another way using join:
SELECT DISTINCT i1.*
FROM inbox as i1 LEFT JOIN inbox as i2
ON i1.toid = 1 AND
i1.fromid = i2.toid AND
i1.toid = i2.fromid AND
i1.`time` <= i2.`time`
WHERE i2.id IS NULL;
Two possible solutions presented below: LEFT JOIN solution should perform better.
LEFT JOIN solution
SELECT
i.fromid, COUNT(*) AS unread, MAX(i.time) AS lastmsg
FROM inbox AS i
INNER JOIN accounts AS a
ON i.toid = a.id
LEFT JOIN inbox AS i2
ON i.fromid = i2.toid AND i.toid = i2.fromid AND i.time <= i2.time
WHERE a.our = 1 AND i2.id IS NULL
GROUP BY i.fromid
ORDER BY lastmsg DESC;
NOT IN solution
SELECT
i.fromid, COUNT(*) AS unread, MAX(i.time) AS lastmsg
FROM inbox AS i
INNER JOIN accounts AS a ON i.toid = a.id
WHERE a.our = 1 AND
(i.toid, i.fromid)
NOT IN (SELECT i2.fromid, i2.toid FROM inbox AS i2 WHERE i2.time >= i.time)
GROUP BY i.fromid
ORDER BY lastmsg DESC;

Set random values from an array into database

I'm working on a PHP project with MYSQL database. I have a table of groups of students. Each group has an examiner. What i want to do is that i want to set two examiners for each group randomly. How to do it?
MySQL Code:
create table groups (
groupID int(10) not null,
nbStudents int not null,
avgGPA DOUBLE NOT NULL,
projectName varchar(50) not null,
advisorID int,
examiner1ID int,
examiner2ID int,
adminID int not null,
primary key (groupID)
);
create table faculty (
name varchar(30) not null,
facultyID int(10) not null,
email varchar(30) not null,
mobile int(15) not null,
primary key (facultyID)
);
examiner1ID and examiner2ID are foreign keys from the table faculty.
Here is a very convoluted way to do it. It uses 2 subqueries to pick faculty members, and insert .. on duplicate key to update the examiners IDs.
insert into groups
(groupID, examiner1ID, examiner2ID)
select groupID,
#x:=(select facultyID from faculty order by rand() limit 1),
(select facultyID from faculty where facultyID <> #x order by rand() limit 1)
from groups
on duplicate key update examiner1ID=values(examiner1ID), examiner2ID=values(examiner2ID);
#x is a user-defined-variable. In this case, it is used to store the first random faculty member. <> #x makes sure we don't pick the same faculty member in both slots.
Since groupID is a unique key, when we try to insert a row with an existing unique key, it will update the existing row instead of inserting it. That's what on duplicate key update clause is used for.
set different examiners for each group:
insert into groups
(groupID, examier1ID, examier2ID)
select a.groupID, max(if(b.id%2, b.facultyID, 0)), max(if(b.id%2, 0, b.facultyID))
from (
select #row:=#row+1 id, groupID
from groups a
join (select #row:=0) b) a
join (
select #row:=#row+1 id, facultyID
from (
select facultyID
from faculty a
order by rand()) a
join (select #row:=0) b) b on a.id = ceil(b.id/2)
group by a.groupID
on duplicate key update examiner1ID=values(examiner1ID), examiner2ID=values(examiner2ID);

PHP: Idea for friend-system?

I need help on making a friend-system.
I am thinking how to make so both users can see that they are friends together.
Should i just make a user1 field and user2 field, and then when displaying him/her´s friends, it should select where user1 ='$id' OR user2 = '$id' ?
Or should i make two rows each time people are being friends?
Smart way and example would be appreciated. Thank you.
I am storing in mysql database.
My thoughts is exactly in how should i list that who is friend with who. Lets say i use method 1) with user1 and user2 column, then i should have WHERE user1 or user2 is $id (users id) but can this work properly?
I just tried this and it shows the userid for user2 in user1´s friendslist,
but in user2´s friendslist it just shows his own userid and not the user1s..
On a big projects with sharding you should always replicate data for each user.
For not so big project it's okay to keep one table with "initiator" and "accepter" fields for user_id. Don't forget to add indexes and "status" field for friendship
I'm assuming you're storing them in a database. A simple way would be to have a new table, lets call it "friendships" with two columns, user_1, user_2. If two users are friends, they should have a row in this table. This is an extremely simple way of implementing this, but it should work.
SELECT users.name, fr.name
FROM users, friends, users AS fr
WHERE users.user_id = ?
AND users.user_id = friends.user_id1
AND friends.user_id2 = fr.user_id
UNION
SELECT users.name, fr.name
FROM users, friends, users AS fr
WHERE users.user_id = ?
AND users.user_id = friends.user_id2
AND friends.user_id1 = fr.user_id
This will allow you check against an intermediary table for your many-to-many relationship and not have to duplicate (inverse) rows.
CREATE TABLE `users` (
`user_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`user_id`),
);
CREATE TABLE `friends` (
`user_id1` bigint(20) unsigned NOT NULL,
`user_id2` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`user_id1`,`user_id2`)
);
Some good answers here, If I had to do this I would do it one way, this way you can determine if the friendship has been confirmed and also ensure the friendship is mutual, so for example
Lets assume status has two values
0 = Unconfirmed
10 = Confirmed
Using simple tables with status for acceptance levels
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`user_id`),
);
CREATE TABLE `friend` (
`user_id` bigint(20) unsigned NOT NULL,
`friend_id` bigint(20) unsigned NOT NULL,
`status` int(11) unsigned NOT NULL,
PRIMARY KEY (`user_id`,`friend_id`)
);
INSERT INTO `users` (`id`, `name`) VALUES
(1, 'Jack'),
(2, 'John');
Jack would like to be friends with John, so you would create a relationship between the two only one way:
INSERT INTO `friend` (`user_id`, `friend_id`, `status`) VALUES (1, 2, 0);
Now you can query the database to find Jacks friends or Jacks requests and Johns friends or Johns requests using simple queries
To find Jacks unconfirmed friends you would use something like
SELECT users.* FROM users JOIN friend ON users.id = friend.user_id WHERE friend.friend_id = 1 AND friend.status = 0
To find Jacks confirmed friends you would use something like
SELECT users.* FROM users JOIN friend ON users.id = friend.user_id WHERE friend.friend_id = 1 AND friend.status = 10
To find Jacks any friend requests you would use something like
SELECT users.* FROM users JOIN friend ON users.id = friend.user_id WHERE friend.friend_id = 1
When someone makes a confirmation of friendship you would perform 2 queries, one for updating the record and one as a reverse confirmation
UPDATE friend SET status = 10 WHERE user_id = 1 AND friend_id = 2;
INSERT INTO `friend` (`user_id`, `friend_id`, `status`) VALUES (2, 1, 10);
On a different note, I would also use a Graph database for complex relationship queries whilst maintaining a firm copy in the MySQL database
Graph teaser for friends of friends to build relationships
MATCH (Person { id: 1 })-[:knows*2..2]->(friend_of_friend) WHERE NOT (Person)-[:knows]->(friend_of_friend) AND NOT friend_of_friend.id = 1 RETURN friend_of_friend.id, friend_of_friend.name LIMIT 10

How can I join in data from a key : value meta table in mysql?

Say I have three tables in my database:
CREATE TABLE `users` (
`user_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR(16) NOT NULL
);
CREATE TABLE `users_meta` (
`meta_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` INT NOT NULL ,
`key` VARCHAR(200) NOT NULL ,
`value` TEXT NOT NULL
);
CREATE TABLE `posts` (
`post_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` INT NOT NULL ,
`content` TEXT NOT NULL
);
The table users_meta is just a key-value store of information about users, such that we can add any piece of information we want.
Say I added a key => value pair to the users_meta table for each user where the key was "age", and the value was a number representing their age.
Given this set of circumstances, what's the best way to select the first 10 posts ordered by user age?
I like putting the condition of the join in the join itself to be clear that I want a limited join:
SELECT p.post_id, p.content
FROM users u
INNER JOIN users_meta um
ON (u.user_id = um.user_id) AND um.key = 'age'
INNER JOIN posts p
ON (p.user_id = u.user_id)
ORDER BY um.value
limit 10
If you order by user age only, you will select 10 posts of the same user (the youngest one).
I would suggest to denormalize and store age in users table directly.
Agree with #KOHb, but if that's exactly what you want, here is the query:
SELECT TOP 10 p.id, p.content
FROM users u JOIN users_meta um ON (u.user_id = um.user_id)
JOIN posts p ON (p.user_id = u.user_id)
WHERE um.key = 'age'
ORDER BY um.value

Categories