I have a table of movie ratings that contains millions of rows containing userid's, movieid's and ratings.
| userId | movieId | rating |
------------------------------
| 1 | 213 | 5 |
| 1 | 245 | 4 |
| 2 | 213 | 4 |
| 2 | 245 | 4 |
| 3 | 657 | 5 |
| 3 | 245 | 5 |
I'm trying to figure out a way of grouping together userId's that contain matching sets of movieId's. Ideally I want the query to only find matches if they have at least 5 movieId's in common and if the rating is above 4, but I've simplified it for this example.
In the instance above, userId 1 and 2 would be the only users that match as they both contain the same movieIds. I need a statement that would essentially replicate this. Thanks in advance for any help.
You can perform a self-join on matching movies, filter out records with uninteresting ratings, group by user-pairs and then filter the resulting groups for only those that have at least the requisite number of matching records:
SELECT a.userId, b.userId
FROM myTable a JOIN myTable b USING (movieId)
WHERE a.userId < b.userId
AND a.rating > 4
AND b.rating > 4
GROUP BY a.userId, b.userId
HAVING COUNT(*) >= 5
select movieId, rating
from tablename
group by movieId
having count(userId) > 1 and rating > 4;
this gives me movieId 245 and rating 5, which should be correct according to your provided example data, have more than 1 userId and a rating greater than 4.
Related
I have a table like so (after doing a query on it to order it by score):
+---+-------+------+
|id | level |score |
+---+-------+------+
| 4 | 1 | 30 |
| 3 | 1 | 35 |
| 1 | 1 | 40 |
| 5 | 1 | 45 |
| 7 | 1 | 50 |
| 8 | 1 | 55 |
+---+-------+------+
I will output that to php in a while loop. So each row in the while loop will be the same as in the table above.
Essentially what I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). This will be like a score board but only showing the user's score with the two above and two below.
E.g. say the user is id=5, I want to show
+---+-------+------+
|id | level |score |
+---+-------+------+
| 3 | 1 | 35 |
| 1 | 1 | 40 |
| 5 | 1 | 45 |
| 7 | 1 | 50 |
| 8 | 1 | 55 |
I am wondering does anyone know a way of doing this in php?
Basically
//select query output is in while loop
//get a certain row of the loop
//get the two rows above it and two rows below it
One method uses a lot of variables:
select t.*
from (select t.*,
lag(id, 1) over (order by score) as prev_id,
lag(id, 2) over (order by score) as prev_id2,
lead(id, 1) over (order by score) as next_id,
lead(id, 2) over (order by score) as next_id2
from t
) t
where 5 in (prev_id, prev_id2, next_id, next_id2, id)
order by score;
An alternative method is something like this:
(select t.*
from t
where t.score <= (select t2.score from t t2 where t2.id = 5)
order by score desc
limit 3
) union all
(select t.*
from t
where t.score > (select t2.score from t t2 where t2.id = 5)
order by score
limit 2
)
order by score;
This exactly syntax may not work in all databases, but the idea can easily be translated in whatever dialect of SQL. This also assumes that the scores are unique.
I am joining users table with selected_course table and course table with selected_course, my requirement is I want to get those users with course_id=2 and in same row select using concatenation of all courses which is selected by user.
user table
id name
1 user_1
2 user_2
user_education_details
id user_id education_id selected_course
1 1 1 2
2 2 1 2
3 1 2 4
4 3 1 2
5 3 2 4
Actual Requirement:
user_id required_course user_all_courses course_name
1 2 2,4 a,b
2 2 2 a
3 2 2,4 a,b
Using codeigniter query format:
$this->db->select('users.*');
$this->db->from('users');
$this->db->join('selected_course sc','c.user_id=users.id');
$this->db->join('course c','c.id=sc.selected_course');
$this->db->where('sc.selected_course',$course_id=2);
$this->db->get()->result();
A HAVING clause is what you're looking for. To filter data based on a group use this clause.
SELECT
ued.user_id,
2 AS required_course, -- passed course id
GROUP_CONCAT(ued.selected_course) AS user_all_courses,
GROUP_CONCAT(c.name) course_name
FROM user_education_details ued
INNER JOIN courses c ON ued.selected_course = c.id
GROUP BY user_id
HAVING SUM(IF(selected_course = 2, 1,0)) > 0;
Output:
| user_id | required_course | user_all_courses | course_name |
| 1 | 2 | 4,2 | b,a |
| 2 | 2 | 2 | a |
| 3 | 2 | 2,4 | a,b |
You can also order user_all_courses and course_name by using a ORDER BY clause inside GROUP_CONCAT. Here is one example.
GROUP_CONCAT(ued.selected_course ORDER BY selected_course ASC) AS user_all_courses,
Now you can quickly convert this to your equivalent CodeIgniter query.
Check this working fiddle for the same.
I have two tables;
Users
id | username | club
-----------------------
1 | James5 | 2
2 | 007 | 1
3 | xmen | 2
4 | terminator | 2
suggestedusers
id | username | club
----------------------
1 | mark | 2
2 | bon | 1
3 | hero | 2
4 | scorpio | 2
5 | lame | 5
How do I join these tables to get the total of the clubs? e.g an answer like
club | clubCount
-------------------
2 | 6
1 | 2
5 | 1
I was thinking of the following query;
SELECT User.club, COUNT(User.club) + COUNT(suggestedusers.club) AS clubCount FROM User, suggestedusers
GROUP BY User.club
ORDER BY clubCount DESC
But the above script is not working.
Your target result is not clear to me.
You can try with this one
Select club, count(club) as clubcount from(
select users.id, users.username, users.club from users
UNION
select suggestedusers.id, suggestedusers.username,
suggestedusers.club from suggested users ) group by club, clubcount Order by
clubcount desc;
Assuming what you want is to get the number of users per club for both tables, this should work for you:
SELECT DISTINCT COALESCE(u.club, su.club) AS club
, COALESCE(count(u.club), count(su.club)) AS clubcount
FROM Users u
INNER JOIN Suggestedusers su ON su.club = u.club
GROUP BY u.club,su.club
ORDER BY clubcount DESC
Please check the names of tables and columns when using in your code.
In a blog-like website, all the users can "star" a news (= bookmark it, mark it as "favourite").
I have a mysql table for stats.
table_news_stats
id_news
total_stars (int) //Total number of users who starred this news
placement (int)
The placement field is intuitive: if you order all the news by the total_stars field you get each news placement. So, the news with most stars will be number 1, and so on.
So, suppose I have 700 records in my table_news_stats, and for each one I have the id and the total_stars count, how can I update the placement field automatically for each record? Which query is faster/better?
Example of the table_news_stats content:
First record (A):
1-3654-?
Second record (B):
2-2456-?
Third record (C):
3-8654-?
If you order the record by stars count:
the sequence of records is C - A - B
So... the result will be:
First record (A):
1-3654-2
Second record (B):
2-2456-3
Third record (C):
3-8654-1
Clarification:
why would I ever need the placement field at all?
It's pretty simple... the placement field will be populated by a cronjob the first day of every month. Basically it will provide a 'snapshot' of the rank of each news in terms of popularity (as it was at the beginning of the current month). As a consequence, thanks to the placement field, I will have the following information:
"The 1st day of this month the 'top starred' news list was like this:
1- News C
2- NewsA
3- News B "
Then, with a query "SELECT * FROM table_news_stats ORDER BY total_stars DESC" I can obtain the new ranking (in real-time).
As a consequence, I will have the following information:
"At the time the page is loaded, the 'top starred' news list is like this:
1- News A
2- News C
3- News B "
Finally, by comparing the two rankings, I obtain the last piece of information:
"News A has gained a position" +1
"News C has lost a position" -1
"News B has no change in position" +0
If there is a better way of doing this, let me know.
I guess you don't need to update the table just:
SELECT *
FROM table_news_stats
ORDER BY total_stars DESC
But if you want to know the place of each one you can:
SELECT *, IF(#idx IS NULL,#idx:= 1,#idx:= #idx+1)
FROM table_news_stats
ORDER BY total_stars DESC
And if you still need to update something like:
UPDATE table_news_stats
SET placement = FIND_IN_SET(id_news,(SELECT GROUP_CONCAT(t.id_news) FROM (SELECT id_news
FROM table_news_stats
ORDER BY total_stars DESC) t ))
SQLFiddle
Consider the following
mysql> select * from test ;
+------+-------------+-----------+
| id | total_stars | placement |
+------+-------------+-----------+
| 1 | 3 | 0 |
| 2 | 6 | 0 |
| 3 | 7 | 0 |
| 4 | 2 | 0 |
| 5 | 9 | 0 |
| 6 | 2 | 0 |
| 7 | 1 | 0 |
+------+-------------+-----------+
Now using the following you can update the placement as
update test t1 join
(
select *,
#rn:= if(#prev = total_stars,#rn,#rn+1) as rank ,
#prev:= total_stars
from test,(select #rn:=0,#prev:=0)r
order by total_stars desc
)t2
on t2.id = t1.id
set t1.placement = t2.rank ;
mysql> select * from test order by placement ;
+------+-------------+-----------+
| id | total_stars | placement |
+------+-------------+-----------+
| 5 | 9 | 1 |
| 3 | 7 | 2 |
| 2 | 6 | 3 |
| 1 | 3 | 4 |
| 4 | 2 | 5 |
| 6 | 2 | 5 |
| 7 | 1 | 6 |
+------+-------------+-----------+
Note that in case of tie will have the same placement.
I'm trying to build a ranking system in a mysql database.
I've found several tutorials on ranking and items here on StackOverflow about ranking individual rows against each other.
However, my issue is that I need to group rows by a user id column, add up the values to a second column grouped by user id, then rank them against other groups of a different user id.
Here's an example of the table I'm using:
user_id km_skied date_entered
1 34 2010-08-19
3 2 2010-08-23
1 3 2010-08-13
4 23 2010-08-01
3 5 2010-08-02
The result printout would be by rank:
Skier Rank:
Rank User ID Total KM
1 1 37
2 4 23
3 3 7
Also, I was wondering how I find the rank for a specific user. Meaning, if I know what the user id is, can I give them just their rank? Like say
"Your Rank: 2 of 345"
That is the second part of this.
Anyone know how to do that?
Thanks!
Troy
Your query should look something like this. Add the ranking logic to the outer loop.
select * from
(select user_id, sum(km_skied) as km from ski group by user_id) x
order by x.km desc;
Don't know if it's an option, but you can use a temporary table for rankings as follows:
create temporary table ranks (rank int primary key auto_increment, user_id int, km int);
insert into ranks (user_id, km)
select user_id, km from (
select user_id, sum(km_skied) as km from ski group by user_id
) x order by x.km desc;
This gives you what you want:
mysql> select * from ranks;
+------+---------+------+
| rank | user_id | km |
+------+---------+------+
| 1 | 1 | 37 |
| 2 | 4 | 23 |
| 3 | 3 | 7 |
+------+---------+------+
3 rows in set (0.00 sec)
One downside to this approach is that skiers who are tied won't get the same rank.
Do the grouping in subquery and ranking of the results (using any of the methods you've found before) in outer query.
Thanks for your help guys.
I was able to come up with an answer based on the following Query:
$totalQuery = "SELECT SUM(track_length) as usertracklength, username, MAX(track_create_time) as lasttrack, count(DISTINCT track_create_time) as totaldays FROM user_tracks GROUP BY username ORDER BY usertracklength DESC";
$totalResult = mysql_query($totalQuery);
$rankResult = mysql_query($totalQuery);
$totalNumEntries = mysql_num_rows($totalResult);
Then Ouputting that to an array
// rank position array
$rankArray = array();
while ($row1 = mysql_fetch_array($rankResult)) {
$rankArray[] = $row1['username'];
}
Then finding position of that username in the array by using a foreach in php
foreach ($rankArray as $rank => $user) {
if ($user == $username) {
$yourRank = $rank+1;
}
}
It's the long way around, but I suppose it works for what I'm going for.
Was kind of hoping to get it done within the mysql query for efficiency.
Thanks!
You could try grouping to sum the Km as a first query, then follow it by a correlated subquery to find the ranks. For instance, if your values are stored in a table called "test", sum the Km values into a table called testtbl and then do the ranking.
mysql> select * from test;
+------+--------+------+
| Id | km_run | name |
+------+--------+------+
| 1 | 34 | a |
| 3 | 2 | c |
| 1 | 3 | a |
| 4 | 23 | d |
| 3 | 5 | c |
+------+--------+------+
5 rows in set (0.00 sec)
mysql> create table testtbl as
(select Id, sum(km_run) as tot
from test
group by Id);
Query OK, 3 rows affected (0.04 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from testtbl;
+------+------+
| Id | tot |
+------+------+
| 1 | 37 |
| 3 | 7 |
| 4 | 23 |
+------+------+
3 rows in set (0.00 sec)
mysql> select t1.Id,t1.tot,
((select count(distinct t2.tot) from testtbl t2 where t1.tot < t2.tot)+1) as Rk from testtbl t1
order by Rk;
+------+------+------+
| Id | tot | Rk |
+------+------+------+
| 1 | 37 | 1 |
| 4 | 23 | 2 |
| 3 | 7 | 3 |
+------+------+------+
3 rows in set (0.00 sec)