I'm trying to create a ranking table based on how many likes/upvotes a user had on all his items in total. User in the upvotes table links to id of the user that made the like, but I think you don't need this.
Hopefully by giving these tables everything will get clear.
I think the trick here is to get all the upvotes by each item and merge them together towards a user this item was from to get a total likes for each user and then rank all the users based on this total. Of course doing this will probably be a slow query so I need a very performant way to handle this.
The hard thing is here mainly that the upvotes table doesn't include the user id.
3 tables:
CREATE TABLE `items` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`user_id` int(255) NOT NULL,
`img` varchar(500) NOT NULL,
`message` varchar(200) NOT NULL,
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`active` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=19 DEFAULT CHARSET=latin1;
CREATE TABLE `upvotes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user` int(255) NOT NULL,
`item_id` int(255) NOT NULL,
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`password` binary(60) NOT NULL,
`first_name` varchar(255) NOT NULL,
`last_name` varchar(255) NOT NULL,
`active` int(1) NOT NULL DEFAULT '1',
`created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
I need a performant query giving me the ranking of each user ranked on how many likes they got on all their items?
I managed to write this:
SELECT #rank := #rank + 1 AS rank, m.*
FROM (SELECT
users.first_name as first_name,
users.last_name as last_name,
count(upvotes.item_id) as total
FROM upvotes
INNER JOIN users
ON users.id = (SELECT items.user_id FROM items WHERE items.id = upvotes.item_id LIMIT 1)
GROUP BY users.id
ORDER BY total DESC
) m, (SELECT #rank := 0) r
But I reckon this will be super slow when the database grows...
You can do a simple join query in order to get the total likes for each item of user and order your results with the resulting count in descending order
SELECT u.*,i.*,COUNT(DISTINCT up.user) `total_user_likes_item`
FROM users u
JOIN items i ON(i.user_id = u.id)
JOIN upvotes up ON(up.item_id = i.id)
GROUP BY u.id,i.id
ORDER BY u.id,i.id,total_user_likes_item DESC
Edit from comments For user total likes you remove i.id from group by as below query
SELECT u.*,COUNT(DISTINCT up.user) `total_user_likes_item`
FROM users u
JOIN items i ON(i.user_id = u.id)
JOIN upvotes up ON(up.item_id = i.id)
GROUP BY u.id
ORDER BY total_user_likes_item DESC
I'll try answer your question:
In table users you can add row sum_upvotes. Every time when someone get one like (vote) you will increment this column by:
UPDATE users
SET sum_upvotes = sum_upvotes + 1
;
Of course, you will insert a column in table upvotes.
Finally, you query to select users and order them by upvotes will look like this
SELECT first_name, last_name
FROM users
ORDER BY sum_upvotes
;
Hope this helps.
Related
I built a website for the fantasy movie league that I run (similar to fantasy football but for motion pictures) using PHP and MySQL. Each movie's profile page has a place where you can leave comments and I have that part working properly. Now, I want to add the ability to "like" individual comments but I'm having some trouble. I think I have the "likes" table set up properly and I'm able to add a like to each individual comment, but displaying the likes inline with each comment is another story.
Here is the code for the "comments" table:
CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`movie_id` int(11) NOT NULL,
`player_id` tinyint(2) NOT NULL,
`date_time` datetime NOT NULL,
`comment` text CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Here is the code for the "likes" table:
CREATE TABLE `likes` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`player_id` int(2) NOT NULL,
`comment_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `player_comment` (`player_id`,`comment_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
Here is the code for the "players" table:
CREATE TABLE `players` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(10) NOT NULL,
`username` char(10) NOT NULL,
`password` char(32) NOT NULL,
`email` varchar(100) NOT NULL DEFAULT '',
`active` tinyint(1) NOT NULL,
`admin` tinyint(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Now, here is the SQL statement that I use to grab all the comments for any particular movie:
SELECT comments.id as comment_id, comments.player_id, comments.date_time, comments.comment,
players.first_name
FROM comments
LEFT JOIN players on players.id = comments.player_id
WHERE movie_id = $movie_id
ORDER BY date_time ASC
My problem is, I'm not sure how to grab all of the likes for each comment in the same statement. I want to be able to display each player's first name that has liked that particular comment. So I want it to look like this:
Line 1: $comment
Line 2: $commenter_first_name
Line 3: $liker_first_name1, $liker_first_name2, $liker_first_name3 likes this
I'm able to get all of the likers for an individual comment like this:
SELECT first_name
FROM likes
LEFT JOIN players on players.id = likes.player_id
WHERE comment_id = 264
ORDER BY first_name ASC
But I don't know how to incorporate this without using the actual comment_id.
Hope this makes sense, I welcome any possible help.
How about something like:
SELECT comments.id as comment_id, comments.player_id, comments.date_time, comments.comment, likers.names
FROM comments
LEFT JOIN
(SELECT comment_id, GROUP_CONCAT(first_name) AS names FROM likes
INNER JOIN players ON likes.player_id = players.id GROUP BY comment_id) AS likers
ON comments.id = likers.comment_id
WHERE movie_id = $movie_id
ORDER BY date_time ASC
The "likers" table should have something like:
| (comment_id) | Bob,Jim,Kathy |
For better list formatting, you can specify a separator:
GROUP_CONCAT(first_name SEPARATOR ', ')
Cant you just add the linkes to the comments table?
CREATE TABLE `comments` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`movie_id` int(11) NOT NULL,
`player_id` tinyint(2) NOT NULL,
`date_time` datetime NOT NULL,
`comment` text CHARACTER SET utf8 NOT NULL,
`likes` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
And then when somebody likes the comment, you just also add +1 to the likes in comment DB, not only in likes DB. And when somebody removes his like, you juset change the number to -1
Like this:
"UPDATE comments SET likes=likes+1 WHERE id='$comment_id'";
The problem I see is, that you want to show the likers...
So maybe something like this:
"SELECT comments.*, likes.* FROM comments INNER JOIN likes comments.id=likes.comment_id WHERE movie_id='$movie_id'";
Your select:
"SELECT comments.id as comment_id, comments.player_id, comments.date_time, comments.comment, players.first_name, likes.player_id
FROM comments
LEFT JOIN players ON players.id=comments.player_id
INNER JOIN likes ON comments.id=likes.comment_id
WHERE movie_id='$movie_id'
ORDER BY date_time ASC";
The other thing is, that it will only return you the ID's of the peoples that liked the comment. So I would suggest you to save also users name to the likes table.
I'm making a social plugin for my website, and I have a friends table that holds all accepted friend requests, and I need to display all posts from the users friend AND the users posts in order of the date, so I've tried this sql query:
SELECT DISTINCT `social_posts`.*, `social_friends`.*, `users`.*
FROM `social_posts`
JOIN `social_friends`
ON `fUID` = '1' AND `friend` = `pUID` OR `pUID` = '1'
JOIN `users`
ON `friend` = `uid`
ORDER BY `date` DESC
Structure
CREATE TABLE `social_friends` (
`fID` int(11) NOT NULL AUTO_INCREMENT,
`fUID` int(11) NOT NULL,
`friend` int(11) NOT NULL,
PRIMARY KEY (`fID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `social_posts` (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`pUID` int(11) NOT NULL,
`body` text NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
fUID is the users (viewing) user ID, and friend is the users friend, and pUID is the user ID of the user who made the post.
But this shows two of each post, even with SELECT DISTINCT, and I'm out of ideas on how to figure this out.
1 - Can you give as more info (the fields) of the tables?
What is fUID and pUID
2 - Try and change
ON `fUID` = '1' AND `friend` = `pUID` OR `pUID` = '1'
to ON friend = pUID and put fUID = 1 OR pUID = 1 in the WHERE clause
I have 2 separate tables, both of which I need to query simultaneously to get the correct information to display. The tables are members and posts. Through an html form, a user enters criteria for the members table, and then I need to use the primary index of all those specific members to find all the posts submitted by those members and then do a sort on the posts table results. The results will be a mixture of rows from the two tables. Both tables have a primary index of the name 'id'. So far what I've come up with is:
$sql_get_posts = mysqli_query($link, "(SELECT id, username FROM members WHERE active='y' AND gender='M' AND city='Yuma' AND state='Arizona') UNION (SELECT * FROM posts WHERE member_id='id' AND active='y' ORDER BY list_weight DESC)") or die(mysqli_error($link));
The error I'm getting is "The used SELECT statements have a different number of columns".
I need to then cycle through the returned results from both tables to populate the content seen by the user:
<?php
while ($row = mysqli_fetch_array($sql_get_posts)) {
$post_id = $row['id']; //This should be the post primary index named 'id', not the member primary index also name 'id'
$member_id = $row['member_id']; //This is the member_id row in the post table referencing this particular member who wrote this post
$member_username = $row['username']; //This is a row stored in the member table
$title = $row['title']; //This is a row stored in post table
******//and on and on getting rows from only the post table
}
Edit My SQL tables:
CREATE TABLE IF NOT EXISTS `members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`age` varchar(3) NOT NULL,
`gender` varchar(1) NOT NULL,
`city` varchar(20) NOT NULL,
`state` varchar(50) NOT NULL,
`active` enum('y','n') NOT NULL DEFAULT 'y',
`created_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
CREATE TABLE IF NOT EXISTS `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`member_id` int(11) NOT NULL,
`title` text NOT NULL,
`comments` enum('y','n') NOT NULL DEFAULT 'y',
`post_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`list_weight` double NOT NULL,
`active` enum('y','n') NOT NULL DEFAULT 'y',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=47 ;
Use join instead of union, union assumes the tables you're combining are similar, whereas join merges the columns of two tables.
Something like:
SELECT members.id, members.username, posts.*
FROM members
INNER JOIN posts
ON members.id = posts.member_id
WHERE members.active='y' AND members.gender='M' AND members.city='Yuma' AND members.state='Arizona'
ORDER BY posts.list_weight DESC
SELECT statement within the UNION must have the same number of columns. The columns must also have similar data types. Also, the columns in each SELECT statement must be in the same order.
But you have selected only 2 columns in first query and "*" for second select query
use joins
SELECT m.id, m.username,p.* FROM members m JOIN posts p on m.active='y' AND m.gender='M' AND m.city='Yuma' AND m.state='Arizona' and p.member_id='id' AND p.active='y' ORDER BY p.list_weight DESC
or you can use
SELECT m.id, m.username,p.* FROM members m,posts p where m.active='y' AND m.gender='M' AND m.city='Yuma' AND m.state='Arizona' and p.member_id='id' AND p.active='y' ORDER BY p.list_weight DESC
I have a register and login site where people can click save progress on a form and it inserts a new row with their session id in column 2 as (user_id).
I also have an admin login where I can see their entries but it shows a list all their saves.
I wonder if you could help me figure out a query to list all the latest save progresses of each unique person? like (id,user_id,name,score): (3,3,bob, score 5) (6,4,sam, score 30) without showing all saves of a user's past saves like:
(1,3,bob, score 5) (2,4,sam, score 30) (3,3,bob, score 5) (4,4,sam, score 30)
I need the latest save of each user.
Like the latest id of a distinct list of user_id. Hope this makes sense. Thanks!
select user_id,name,score from your_table where (user_id,id) in (select user_id,max(id) from your_table group by user_id)
Considering the below formats for your tables
CREATE TABLE IF NOT EXISTS `user` (`user_id` int(11) NOT NULL auto_increment,
`user_name` varchar(200) collate latin1_general_ci NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci AUTO_INCREMENT=1;
CREATE TABLE IF NOT EXISTS `user_score` (
`id` int(11) NOT NULL auto_increment,
`user_id` int(11) NOT NULL,
`user_score` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci AUTO_INCREMENT=1;
Please try executing the below sql select query for retrieving latest score for each user
SELECT u . * ,s.user_score,s.id FROM user u JOIN user_score s ON u.user_id = s.user_id WHERE id IN (SELECT MAX( id ) FROM user_score GROUP BY user_id)
I'm trying to do a mysql query to display a list of recommendations. The list of recommendations sorted by proximity of friends. I was able to sort the list based on the recommendations of friends, but I am having trouble sorting a list of recommendations based on friends of friends.
The following is the design of the tables:
/*Table structure for table `recommendation` */
CREATE TABLE `recommendation` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`kategori` varchar(255) NOT NULL,
`nama` varchar(255) NOT NULL,
`rating` set('1','2','3','4','5') NOT NULL,
`writer_id` varchar(255) NOT NULL,
`search_here` longtext NOT NULL,
`user_type` set('0','1') NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `kategori` (`kategori`),
KEY `nama` (`nama`),
FULLTEXT KEY `search_here` (`search_here`)
)
ENGINE=MyISAM AUTO_INCREMENT=100174
DEFAULT CHARSET=latin1 COMMENT='recommendation table';
/*Table structure for table `user_relationship` */
DROP TABLE IF EXISTS `user_relationship`;
CREATE TABLE `user_relationship` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`me` varchar(255) NOT NULL,
`friend` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `me` (`me`)
)
ENGINE=MyISAM AUTO_INCREMENT=100982
DEFAULT CHARSET=latin1 COMMENT='user relationship table';
I apply the following query:
SELECT
a.id AS ids,
a.writer_id,
a.kategori,
a.rating,
b.me,
a.user_type
FROM
recommendation a
LEFT OUTER JOIN
user_relationship b
ON
a.writer_id = b.friend
AND
b.me='$USER_ID'
WHERE
MATCH(search_here) AGAINST('$SEARCH' IN BOOLEAN MODE)
ORDER BY
b.me DESC,
a.writer_id
The list of recommendations should shown in the following order: recommendations from friends, recommendations from friends of friends, and recommendations from the public.
anyone can help me?
thanks
EDIT:
I have tried create clone of user_relationship table named "user_friend_friend".
the "user_friend_friend" table design like this:
CREATE TABLE `user_friend_friend` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`friend` varchar(255) NOT NULL,
`friend2` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
KEY `friend` (`friend`)
)
ENGINE=MyISAM AUTO_INCREMENT=100982
DEFAULT CHARSET=latin1 COMMENT='user friend friend table';
I do following query:
SELECT
a.id AS ids,
a.writer_id,
a.kategori,
a.nama,
a.rating,
b.me, b.friend,
c.friend2 AS ff, a.user_type
FROM recommendation a
LEFT OUTER JOIN webref.user_relationship b
ON a.oleh = b.friend AND b.me='1010147270'
LEFT OUTER JOIN webref.user_friend_friend c
ON a.oleh = c.friend AND b.friend IS NULL
WHERE MATCH(search_here) AGAINST('$SEARCH' IN BOOLEAN MODE)
GROUP BY a.id
ORDER BY
b.me DESC,
user_type DESC,
rating DESC,
c.friend DESC,
a.writer_id
is there any more efficient solution?
A few performance tips:
- You should have an index on columns you are joining. For example it doesn't look like writer_id has an index.
- Using VARCHAR(256) as the type for a column you are joining is slow even if you use an index. It would be better to store the ids as integers. You could have a separate table that you stores the int -> varchar mapping that you query later.
But to do the friend of friend query, something like this should work:
SELECT
a.id AS ids,
a.writer_id,
a.kategori,
a.rating,
b.me,
a.user_type
FROM
recommendation a
LEFT OUTER JOIN
user_relationship b
ON
a.writer_id = b.friend
AND
b.me='$USER_ID'
LEFT OUTER JOIN
user_relationship c
ON
a.writer_id = c.friend
AND
c.me = b.friend