Select specific titles based on friends' posts - php

I have some tables.
titles
id| title
1 | Cars
2 | Computers
3 | Phones
4 | Tvs
entry
id | title_id | user_id | entry | time
1 | 1 | 12 | entry-01 | 1
2 | 2 | 11 | entry-02 | 2
3 | 3 | 12 | entry-03 | 3
4 | 2 | 11 | entry-04 | 4
5 | 3 | 11 | entry-05 | 5
6 | 4 | 12 | entry-06 | 6
7 | 4 | 13 | entry-07 | 7
8 | 4 | 11 | entry-08 | 8
9 | 1 | 10 | entry-09 | 9
10 | 2 | 12 | entry-10 | 10
users
id | username
10 | user-1
11 | user-2
12 | user-3
13 | user-4
friends
id | user_id | friend_id
1 | 10 | 12
2 | 11 | 12
3 | 12 | 10
4 | 10 | 11
I need to filter titles based on friends' entries and sort the results by (entry.time) desc. And I also need to show friends name and count(entry) at the list.
Expected result filtered by user_id=10 is:
result
1 | Computers | user-3, user-2(2)
2 | Tvs | user-2, user-3
3 | Phones | user-2, user-3
4 | Cars | user-3
any ideas?

This problem is complex, but if you get into the habit of breaking problems down into smaller pieces you will catch on pretty quickly. Why not start by getting all friends of user id 10? We can do so like this:
SELECT CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL;
Notice the use of a case statement, because user_id 10 could be in either of the two columns. I use the GROUP BY in case the user/friend pair appears multiple times (like 10 and 12 for your example) and a check for not null to remove the rows that didn't match the case.
Now that you have those, you can join it with the entries and titles tables to get the information you're going to need. Just add in some aggregation to get the number of entries each user has for a title:
SELECT t.title, u.userName, COUNT(*) AS numEntries
FROM titles t
LEFT JOIN entry e ON e.title_id = t.id
JOIN users u ON u.id = e.user_id
JOIN(
SELECT
CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL) f ON f.userFriends = u.id
GROUP BY t.title, u.userName;
Matching your format is going to be very tricky. Typically, you can use GROUP_CONCAT() to get a comma separated list, but you will get something like user3, user2, user2 for your first list. To fix this, I recommend writing a CONCAT() in your select statement to modify the above query to get the number of entries to the side of each user. In addition, use another CASE statement so that this only happens when the COUNT(*) is greater than 1:
SELECT t.title,
CASE WHEN COUNT(*) > 1 THEN
CONCAT(u.userName, ' (', COUNT(*), ')')
ELSE
u.userName
END AS numEntries
FROM titles t
LEFT JOIN entry e ON e.title_id = t.id
JOIN users u ON u.id = e.user_id
JOIN(
SELECT
CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL) f ON f.userFriends = u.id
GROUP BY t.title, u.userName;
And now, I would preform a GROUP_CONCAT() on that query:
SELECT tmp.title, GROUP_CONCAT(tmp.userEntries) AS friendEntries
FROM(
SELECT t.title,
CASE WHEN COUNT(*) > 1 THEN
CONCAT(u.userName, ' (', COUNT(*), ')')
ELSE
u.userName
END AS userEntries
FROM titles t
LEFT JOIN entry e ON e.title_id = t.id
JOIN users u ON u.id = e.user_id
JOIN(
SELECT
CASE WHEN user_id = 10 THEN friend_id
WHEN friend_id = 10 THEN user_id END AS userFriends
FROM friends
GROUP BY userFriends
HAVING userFriends IS NOT NULL) f ON f.userFriends = u.id
GROUP BY t.title, u.userName) tmp
GROUP BY tmp.title;
I apologize for the lengthy response (though I wanted to be clear and cover it all). If you've made it to this point, you'll be happy to know that it works in SQL Fiddle.

Related

MySQL SUM 0 for no result on JOIN

I have two tables, 'authorization' and 'transaction'.
authorization table
Auth_ID | User_ID | Auth_Hours
-------------------------------
5 | 1 | 60
6 | 2 | 40
7 | 3 | 50
transaction table
Auth_ID | User_ID | Used_Hours
-------------------------------
5 | 1 | 5
6 | 2 | 2
5 | 1 | 20
6 | 2 | 17
5 | 1 | 11
6 | 2 | 9
I want my query to sum Used_Hours and group by Auth_ID and User_ID from the transaction table. Here's the catch - I also want the query result to show users who have not used any of their Auth_Hours with a 0. See below:
QUERY
SELECT a.Auth_ID, a.USER_ID, a.Auth_Hours, SUM(t.Used_Hours)
FROM AUTHORIZATION a
JOIN TRANSACTION t
on a.Auth_ID = t.Auth_ID
and a.User_ID = t.User_ID
GROUP BY a.Auth_ID, a.USER_ID, a.Auth_Hours
Actual Result
Auth_ID | User_ID | Auth_Hours | Total Hours Used
-------------------------------------------------
5 | 1 | 60 | 36
6 | 2 | 40 | 28
Wanted Result
Auth_ID | User_ID | Auth_Hours | Total Hours Used
-------------------------------------------------
5 | 1 | 60 | 36
6 | 2 | 40 | 28
7 | 3 | 50 | 0
I would imagine the query to be relatively simple.
The JOIN statement is a shortcut for INNER JOIN which returns records that have matching values in both tables. If you want to return all records from one table and the matching records from the other table, then you should use an outer join (LEFT [OUTER] JOIN or RIGHT [OUTER] JOIN). Then you can use the IFNULL() or COALESCE()functions to convert NULLs to zeros:
SELECT a.Auth_ID, a.USER_ID, a.Auth_Hours, IFNULL(SUM(t.Used_Hours), 0) AS 'Total Hours Used'
FROM authorization a
LEFT JOIN transaction t ON a.Auth_ID = t.Auth_ID AND a.User_ID = t.User_ID
GROUP BY a.Auth_ID, a.USER_ID, a.Auth_Hours
Notice that I used single quotes to assign a string with spaces as a name to the total field (as you used in your examples). This will work in all databases. In MySQL you can also use back ticks, but that only works in MySQL.
Here is a good illustration about the different types of joining tables.
this query work in any mysql Engine or Version. use this query:
SELECT a.Auth_ID, a.USER_ID, a.Auth_Hours, CASE COUNT(t.Used_Hours) WHEN 0 THEN 0 ELSE SUM(t.Used_Hours) END AS 'Total Hours Used'
FROM authorization a
LEFT JOIN TRANSACTION t ON a.Auth_ID = t.Auth_ID AND a.User_ID = t.User_ID
GROUP BY a.Auth_ID, a.USER_ID, a.Auth_Hours

Iget data from 1 table and check the data does not exist in second table

I have 3 tables created forum groups and group_members I want to get groups only those which are not in group members along with user id currently it is getting group which group id and user id is not present in the group members table if data is not in the table only if 1 group member exist it pulls up the record . In simple words I want to show show groups which user have not joined here is my table schema for both 3 tables
Groups
+----+----------+
| id | name |
+----+----------+
| 1 | group 1 |
| 2 | group 2 |
| 3 | group 3 |
| 4 | group 4 |
+----+----------+
forums
+------------------+-------------+
| id | title | group_id |
+------------------+-------------+
| 1 | test 1 | 2 |
| 2 | test 2 | 3 |
| 3 | test 3 | 2 |
| 4 | test 4 | 3 |
| 5 | test 5 | 2 |
| 6 | test 6 | 4 |
+------------------+-------------+
Group_members
+-----------------+-------------+
| id | user_id | group_id |
+-----------------+-------------+
| 1 | 107 | 2 |
| 2 | 106 | 3 |
+-----------------+-------------+
Here is my sql I have written
<?php
$sql_grp_chk = $this->db->query("SELECT * FROM groups WHERE NOT EXISTS (SELECT * FROM group_members WHERE groups.id == group_members.group_id)");
foreach($sql_grp_chk->result() as $data_ct):
$sql_gr_coun = $this->db->query("SELECT groups.*, (SELECT count(group_id) FROM forums WHERE groups.id = forums.group_id) as forumcount FROM groups WHERE groups.id != '".$data_ct->id."' ORDER BY forumcount DESC LIMIT 5");
foreach($sql_gr_coun->result() as $data_count):
$sql_follow = $this->db->get_where('group_members', array('group_id' => $data_count->id));
var_dump($data_count);
?>
<?php endforeach; ?>
<?php endforeach; ?>
Not sure why forums is there, but to select all groups that are not linked to a user you can do left join:
select g.* from groups g
left join group_members m on m.group_id = g.id and m.user_id = :userId
where m.id is null;
EDIT:
Select top 5 groups, by number of forums linked:
select g.*, count(nullif(f.id, 1)) as cnt from groups g
inner join forums f on f.group_id = g.id
group by g.id
order by cnt desc
limit 5;
Both queries together - top 5 groups, by number of forums linked, which user has not joined yet:
select g.*, count(nullif(f.id, 1)) as cnt from groups g
left join group_members m on m.group_id = g.id and m.user_id = :userId
left join forums f on f.group_id = g.id
where m.id is null
group by g.id
order by cnt desc
limit 5;

COUNT in LEFT JOIN returning duplicated value

I've the following tables (example):
users:
id | user | photo | joined | country
1 | Igor | abc.jpg | 2015 | Brazil
2 | John | cga.png | 2014 | USA
3 | Lucas| hes.jpg | 2016 | Japan
posts (see that there are two lines with author = Igor and ft = 2 and one line with author = Igor and ft = 3 and Igor have three posts):
id | author | content | date | ft (2 = photos and 3 = videos)
1 | Igor | hi | 2016 | 2
2 | Igor | hello | 2016 | 3
3 | John | hehehe | 2016 | 2
4 | Igor | huhuhuh | 2016 | 2
5 | Lucas | lol | 2016 | 3
friendship (when status = 2 means that they are friends):
id | friend1 | friend2 | status
1 | Igor | Lucas | 2
2 | Lucas | John | 2
3 | John | Igor | 2
And I want to do a COUNT of posts with ft = 2 and a COUNT of friends (status = 2) according to the currently logged user (Igor, in this case).
So, I do (assuming that the current user logged in is Igor):
SELECT photo, joined, country, sum(CASE WHEN ft = 2 THEN 1 ELSE 0 END) AS numPhotos, sum(CASE WHEN ft = 3 THEN 1 ELSE 0 END) AS numVideos
FROM users
LEFT JOIN posts
ON users.user = posts.author
WHERE users.user = 'Igor'
GROUP BY users.user
LIMIT 1
And when I check on a foreach, the data is correct:
numPhotos = 2 and numVideos = 1.
But, I want to select too the number of friends, so, I do:
SELECT photo, joined, country, sum(CASE WHEN ft = 2 THEN 1 ELSE 0 END) AS numPhotos, sum(CASE WHEN ft = 3 THEN 1 ELSE 0 END) AS numVideos, count(friendship.status) AS numFriends
FROM users
LEFT JOIN posts
ON users.user = posts.author
LEFT JOIN friendship
ON (users.user = friend1 OR users.user = friend2) AND friendship.status = 2
WHERE users.user = 'Igor'
GROUP BY users.user
LIMIT 1
But, the output is:
numPhotos = 4, numVideos = 2 and numFriends = 6.
In other words, he is duplicating all results but in numFriends he's taking the total of posts of Igor (3) and duplicating the value too. And if I change count(friendship.status) to sum(friendship.status) the output is:
numPhotos = 4, numVideos = 2 and numFriends = 18 (triples the numFriends).
I tried too with count(distinct friendship.status) and the result is:
numPhotos = 4, numVideos = 2 and numFriends = 1 (duplicates the values again as well as return the wrong value 1 for numFriends that should be 2 knowing he has two friends).
So, how I can do this? (I'm using MySQL)
EDIT:
I changed the count(distinct friendship.status) to count(distinct friendship.id) and it worked to select the number of friends. But the rest of values (numPhotos and numVideos) continue duplicated.
I discovered that the problem is in ON (users.user = friend1 OR users.user = friend2), because if I leave only ON (users.user = friend1) or ON (users.user = friend2) the output isn't duplicated. I tried too with ON 'Igor' IN (friend1, friend2) but the result is the same (numPhotosandnumVideos` continue duplicated).
I think the left join may be joining on a one-to-many relationship, which is causing inflated counts.
Since you are only retrieving the counts for 1 user, I suggest using a subquery to retrieve the friendship counts (for retrieving the counts for multiple users, a derived table may be faster than a subquery):
SELECT
sum(ft = 2) AS numPhotos,
sum(ft = 3) AS numVideos,
(select count(*) from friendships f
where (friend1 = users.user
or friend2 = users.user)
and status = 2) as friendship_count
FROM users
LEFT JOIN posts
ON users.user = posts.author
WHERE users.user = 'Igor'
Note that I removed the group by because users.user is already in the where clause, which means there is only 1 group.
Instead of count(distinct friendship.status), try using count(distinct friendship.id). That should give you the number of unique friends. Counting distinct statuses doesn't work because all the statuses will be 2 by definition, so there is only one distinct value.

Group by 2 columns regardless of order

The following query
select a.message, a.sender_id, a.rec_id, a.id, a.is_seen, b.total_msg, b.last_id, users.name
from tbl_message a left join users on (users.id=a.sender_id)
inner join
(select sender_id, rec_id, max(id) last_id, count(*) total_msg
from tbl_message group by sender_id,rec_id
)b on a.id=b.last_id
order by a.id desc
gives the result as below:
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
| message | sender_id | rec_id | id | is_seen | total_msq | last_id | name |
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
| latest testing l5 aug | 2 | 1 | 12 | 0 | 5 | 12 | B |
| testing | 1 | 2 | 11 | 1 | 3 | 11 | A |
| this msg of A | 1 | 3 | 9 | 0 | 1 | 9 | A |
| this is again 3rd msg of C | 3 | 1 | 6 | 1 | 3 | 6 | C |
+----------------------------+-----------+--------+----+---------+-----------+---------+------+
I want the result as:
For sender_id/rec_id = 1 or 2 id = 12 and for sender_id/rec_id = 1 or 3 id = 9
It sounds like you want to group rows by the sender_id,rec_id participants pair regardless of which order they appear in (i.e. sender_id,rec_id or rec_id,sender_id should be part of the same group).
If so, change your group by from
group by sender_id, rec_id
to
group by least(sender_id,rec_id), greatest(sender_id,rec_id)
Using greatest and least will ensure that each conversation will be grouped by the participants regardless of which order they appear in.
Looks like you need to join with all the grouped columns
Try this
SELECT a.message
,a.sender_id
,a.rec_id
,a.id
,a.is_seen
,b.total_msg
,b.last_id
,users.NAME
FROM tbl_message a
LEFT JOIN users ON (users.id = a.sender_id)
INNER JOIN (
SELECT sender_id
,rec_id
,max(id) last_id
,count(*) total_msg
FROM tbl_message
GROUP BY sender_id
,rec_id
) b ON a.sender_id=b.sender_id and a.rec_id=b.rec_id and a.id = b.last_id
ORDER BY a.id DESC
I think you need to remove one column from the GROUP BY clause of the derived table (subquery)
select a.message, a.sender_id, a.rec_id, a.id, a.is_seen, b.total_msg, b.last_id, users.name
from tbl_message a left join users on (users.id=a.sender_id)
inner join
(select sender_id, rec_id, max(id) last_id, count(*) total_msg
from tbl_message group by sender_id
)b on a.id=b.last_id
order by a.id desc
I would expect this to result in rows only for the following
SenderID ID
2 12
1 9
3 6

mySQL "Rank in Highscore"-Query

Hi there coders around the world,
I'm working on a project where users can do certain things and gain points for it. To simplify this question let's say we got 2 tables user and points.
-- table user -- table points
+---------------+ +-----------------------------+
| id | name | | id | points | user_id |
+---------------+ +-----------------------------+
| 1 Tim | | 1 5 1 |
| 2 Tom | | 2 10 1 |
| 3 Marc | | 3 5 1 |
| 4 Tina | | 4 12 2 |
| 5 Lutz | | 5 2 2 |
+---------------+ | 6 7 1 |
| 7 40 3 |
| 8 100 1 |
+-----------------------------+
Now to get the complete highscore-list I use the following query
SELECT u.*, SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
ORDER BY sum_points DESC
resulting in a fine highscore-list with all users from first to last
+------------------------------+
| id | name | sum_points |
+------------------------------+
| 1 Tim 127 |
| 3 Marc 40 |
| 2 Tom 14 |
| 4 Tina 0 |
| 5 Lutz 0 |
+------------------------------+
Alright back to the question itself. On the profile of a single user I'd like to show his ranking within the highscore-list.
Can this be done using a single query just showing that for example Tom (id=2) is ranked in place 3?
Thanks alot :-)
The idea is to ask, "how many players rank above #this_user":
select count(*) + 1 from
(
/* list of all users */
SELECT SUM( p.points ) AS sum_points
FROM user u
LEFT JOIN points p ON p.user_id = u.id
GROUP BY u.id
) x
/* just count the ones with higher sum_points */
where sum_points > (select sum(points) from points where user_id = #this_user)
Edited to make result 1-based instead of 0-based
SELECT q.*,
#r := #r + 1 AS rank
FROM (
SELECT #r := 0
) vars,
(
SELECT u.*,
SUM(p.points) AS sum_points
FROM
user u
LEFT JOIN
points p
ON p.user_id = u.id
GROUP BY
u.id
ORDER BY
sum_points DESC
) q

Categories