Double join if row in third table matches where clause - mysql

I have a game server, and I want to get a list of the most-ignored player accounts.
I have a user table
Table1 - Users:
Name | ID | otherstuff
Troll | 1 | .
CoolGuy | 2 | .
I have an ignore table
Table2 - Ignores
id_UserWhoIsIgnoring | id_UserWhoIsIgnored
2 | 1
3 | 1
Now this is all great, and I can do something like:
select
u.name,
ig.id_UserWhoIsIgnored,
count(ig.id_UserWhoIsIgnored) as ignoreCount
from ignores ig
inner join users u
on ig.id_UserWhoIsIgnored = u.id
group by id_UserWhoIsIgnored
order by ignoreCount desc
limit 25;
But the problem with this is that I get accounts of users who haven't connected in a really long time. I'd like to limit my query to users connected in the past 30 days. I have a third table, sessions
Table3 - Sessions
id_user | start_time | otherstuff
1 | 2014-06-25 00:00:00 | .
(id)OldTroll | 2010-01-01 00:00:00 | .
How can I combine my first query giving the list but restrict it only cases where start_time > date_sub(now(), interval 45 days) gives me a result for id. In this case I don't want a row showing OldTroll even if they're the most ignored because their most recent connection is years old.

If start_time is in the users table, then just use a where:
select u.name, ig.id_UserWhoIsIgnored, count(ig.id_UserWhoIsIgnored) as ignoreCount
from ignores ig inner join
users u
on ig.id_UserWhoIsIgnored = u.id
where start_time > date_sub(now(), interval 45 days)
group by id_UserWhoIsIgnored
order by ignoreCount desc
limit 25;
If start_time is in the ignores table, then just use having:
select u.name, ig.id_UserWhoIsIgnored, count(ig.id_UserWhoIsIgnored) as ignoreCount
from ignores ig inner join
users u
on ig.id_UserWhoIsIgnored = u.id
group by id_UserWhoIsIgnored
having max(start_time) > date_sub(now(), interval 45 days)
order by ignoreCount desc
limit 25;
EDIT:
Then I presume you want:
select u.name, ig.id_UserWhoIsIgnored, count(ig.id_UserWhoIsIgnored) as ignoreCount
from ignores ig inner join
users u
on ig.id_UserWhoIsIgnored = u.id inner join
(select id_user, max(start_time) as start_time
from sessions
group by id_user
) s
on u.id_user = s.id_user and
s.start_time >= date_sub(now(), interval 45 days)
group by id_UserWhoIsIgnored
order by ignoreCount desc
limit 25;

Related

Get top 5 records for user, average records and rank by averages

I have a table for user records that hold scores (and need to get only users that are members). I need to get the top 5 scores for each user during this year, average the scores and return the top 10 users.
NOTE: Also the user must have a minimum of 5 entries.
SCORE TABLE:
user_id | score | date_submitted
1 99 2017-11-07 22:00:00
2 55 2017-10-33 11:33:35
1 12 2017-09-33 11:33:35
USER TABLE
id | is_member
1 1
2 1
3 0
Here is what I have so far:
SELECT s.user_id,
(SELECT AVG(s.score) FROM score s2 WHERE s2.user_id = s.user_id ORDER BY score DESC LIMIT 5) gr
FROM score s, users u
WHERE u.id = s.user_id
AND u.is_member = 1
AND YEAR(s.date_submitted) = YEAR(CURDATE())
GROUP BY s.user_id
HAVING COUNT(*) >= 5
ORDER BY gr DESC LIMIT 10
This returns:
1242 - Subquery returns more than 1 row
I understand that its the limit in the subquery, I am trying to figure out how to get the top 5 records for that user.
You don't need the subquery. I would also suggest using the join syntax:
SELECT s.user_id, AVG(s.score) gr
FROM score s
INNER JOIN users u
ON u.id = s.user_id
AND u.is_member = 1
WHERE YEAR(s.date_submitted) = YEAR(CURDATE())
GROUP BY s.user_id
HAVING COUNT(*) >= 5
ORDER BY gr DESC
LIMIT 10
Following query will calculate avg of current year's top(highest) 5 scores of each user who is member and display top 10 users:
SELECT s.user_id, AVG(s.score) gr
FROM (select * from score s
where YEAR(date_submitted) = YEAR(CURDATE())
and
(select count(*)
from score
where user_id = s.user_id
and score>=s.score
)<=5
) s
INNER JOIN users u
ON u.id = s.user_id
AND u.is_member = 1
GROUP BY s.user_id
HAVING COUNT(*) >= 5
ORDER BY gr DESC
LIMIT 10;
Hope it helps.

Display only the Id's of users who have not added comments for 3 Days

I have two tables with multiple rows. The first is called comments and has the following structure:
user_id last_commented
........................
9239289 2017-11-06
4239245 2017-11-05
4239245 2017-11-03
6239223 2017-11-02
1123139 2017-11-04
The second one is called users and has the following structure:
user_id user_name user_status
.................................
9239289 First Name 0
4239245 First Name2 2
6239223 First Name3 1
1123139 First Name4 2
I need a query that displays the users who have not added comments for the last 3 days and have a user_status equals to 2.
This is my query so far:
SELECT user_name FROM
(
SELECT MAX(last_commented) lastdate,user_id
FROM `comments`
GROUP BY user_id
) A
LEFT JOIN users
USING (user_id)
WHERE lastdate < ( DATE(NOW()) - INTERVAL 3 DAY )
AND user_status = 2
For example, you can use NOT EXISTS
select u.*
from users u
where not exists (
select 1
from comments c
where c.user_id = u.user_id and last_commented > DATE(NOW()) - INTERVAL 3 DAY
) and user_status = 2
EDIT: if you want the number of days since the last user login in the result then you can use the following approach:
select u.*, datediff(DATE(now()), comment.last_commented)
from users u
join (
select user_id, max(last_commented) last_commented
from comments
group by user_id
) comment on comment.user_id = u.user_id
where last_commented <= DATE(NOW()) - INTERVAL 3 DAY
This approach uses subquery to find the last comment date and then it uses it for filtering and also for computation.
you can use this query.
SELECT MAX(last_commented) lastdate,user_id
FROM `comments`
where lastdate<DATE_SUB(NOW(), INTERVAL 3 day) and
user_status = 2
GROUP BY user_id

MySQL INNER JOIN from second table (TOP10)

$stmt = $conn->prepare('SELECT a.*, c.*, SUM(a.money+b.RESULT) AS ARESULT
FROM users a
INNER JOIN bankaccounts c
ON a.id = c.owner
INNER JOIN
(
SELECT owner, SUM(amount) AS RESULT
FROM bankaccounts
GROUP BY owner
) b ON a.id = b.owner
ORDER BY ARESULT DESC LIMIT 10');
What's problem, it show wrong only one record? I want list max 10 records - like TOP 10 richest who has [money+(all his bankaccounts amount)]
Lets say.. I have 2 tables.
Table: users
ID | username | money
1 | richman | 500
2 | richman2 | 600
Table: bankaccounts
ID | owner | amount
65 | 1 | 50
68 | 1 | 50
29 | 2 | 400
So it would list:
richman2 1000$
richman 600$
Try using a subqueries...
$stmt = $conn->prepare('SELECT a.*,
IFNULL((SELECT SUM(amount) FROM bankaccounts b WHERE b.owner=a.id),0) AS BANK_MONEY,
(IFNULL(a.money,0) + IFNULL((SELECT SUM(amount) FROM bankaccounts c WHERE c.owner=a.id),0)) AS ARESULT
FROM users a
ORDER BY ARESULT DESC LIMIT 0, 10');
EDIT: Added a field for bank account totals
EDIT2: Added IFNULL to SQL statement in case user is not in BankAccounts table
Try this:
SELECT a.*, (a.money + b.RESULT) AS ARESULT
FROM users a
INNER JOIN (SELECT owner, SUM(amount) AS RESULT
FROM bankaccounts
GROUP BY owner
) b ON a.id = b.owner
ORDER BY ARESULT DESC
LIMIT 10

How to concatenate values into string in MySQL

I have this SQL table:
user_skills
==========================
user_id | skill_id | value
1 | 4 | 1
1 | 5 | 1
1 | 6 | 1
1 | 7 | 1
1 | 8 | 1
2 | 4 | 1
2 | 6 | 1
4 | 4 | 1
4 | 5 | 1
5 | 8 | 1
Than, I have SQL query which returns me info about user.
$mysqli->query("SELECT u.*,
COUNT(a.user_id) AS jobs,
r.*
FROM users u
LEFT JOIN articles a
ON u.id = a.user_id
LEFT JOIN rating r
ON r.user_id = u.id
LEFT JOIN regions r1
ON r1.id = u.city
WHERE u.active = 1 AND
u.server_id IN (" . $server_id . ")
GROUP BY (CASE WHEN a.user_id IS NULL THEN u.id ELSE a.user_id END)
ORDER BY u.points DESC,
jobs_done DESC,
u.id DESC
LIMIT " . (($page - 1) * $limit) . ", " . ($page * $limit));
Now, I need to select to each user their skills, or better, skill_ids. Ideally as a string separated by commas because we spoke about only 5 skill_id (4-8).
I tried:
$mysqli->query("SELECT u.*,
COUNT(a.user_id) AS jobs,
r.*,
CONCAT_WS(',', us.skill_id) AS skill_ids
FROM users u
LEFT JOIN articles a
ON u.id = a.user_id
LEFT JOIN rating r
ON r.user_id = u.id
LEFT JOIN regions r1
ON r1.id = u.city
LEFT JOIN user_skills us
ON us.user_id = u.id AND skill_id IN (4, 5, 6, 7, 8)
WHERE u.active = 1 AND
u.server_id IN (" . $server_id . ")
GROUP BY (CASE WHEN a.user_id IS NULL THEN u.id ELSE a.user_id END)
ORDER BY u.points DESC,
jobs_done DESC,
u.id DESC
LIMIT " . (($page - 1) * $limit) . ", " . ($page * $limit));
But it returns me only the first skill_id from database, without concatenating.
When I tried to use CONCAT_WS(',', us.skill_id) AS skill_ids, result was comma and the first one skill_id (, 4).
Data in database are okay, SUM(us.skill_id) returns 30 for user with id=4.
Any idea?
You have two major problems...
A) The function you want is GROUP_CONCAT(), which produces a CSV of values for the group. This query give you the skills list for each user:
select user_id, group_concat(skill_id) as skill_ids
from user_skills
group by user_id
B) A more major problem is your incorrect use of GROUP BY: For it to work as expected, you must list every column that is not an aggregate function, either using positional numbers or expressions.
Assuming users table has 6 columns and rating has 4:
SELECT
u.*,
COUNT(a.user_id) AS jobs,
r.*,
GROUP_CONCAT(us.skill_id) AS skill_ids
FROM ...
...
GROUP BY 1,2,3,4,5,6, 8,9,10,11,12 -- note 7 is missing
or you can list column expressions in the GROUP BY:
GROUP BY u.id, u.name, ..., r.id, r.foo, ...
If you omit even one non- aggregate column, it won't work as expected. This peculiar behaviour is mysql only - every other database throws a syntax exception if you left one out.

Mysql refrencing derived tables from nested query

I posted something similar to this yesterday, but now I'd like something a little different from my query-
I'm trying to query a database to retrieve the number of one-time users who have visited a website over time. The data looks something like this:
Day | UserID
1 | A
1 | B
2 | B
3 | A
4 | B
4 | C
5 | D
I'd like the query result to look this this
Time Span | COUNT(DISTINCT UserID)
Day 1 to Day 1 | 2
Day 1 to Day 2 | 1
Day 1 to Day 3 | 0
Day 1 to Day 4 | 1
Day 1 to Day 5 | 2
The result is 2,1,0,1,2 because, at the end of those days, there are X number of users who have visited a single time. e.g. for day 5, at the end of day 5, users c and d have visited only once each.
I think I'm looking for a query similar to this:
select d.day, (select count(distinct userid) from visits where day<=d.day)
from (select distinct day from visits) d
The difference between the query above and what I'm looking for is that I'd like this new query to consider only one-time users for each time span, and not repeat users.
Thanks
This subquery should work for the clarified requirements.
select d.day, count(distinct case when b.userid is null then a.userid end)
from (select day from visits group by day) d
inner join
(
select a.day, a.userid, count(*) c
from visits a
join visits b on a.userid=b.userid and b.day <= a.day
group by a.day, a.userid
having count(*) = 1
) a on a.day <= d.day
left join
(
select a.day, a.userid, count(*) c
from visits a
join visits b on a.userid=b.userid and b.day <= a.day
group by a.day, a.userid
having count(*) > 1
) b on a.userid = b.userid and b.day <= d.day
group by d.day
Original
You must have taken the idea from SQL Server - it is the only RDBMS (IIRC) that will allow you to reference a twice removed (nesting) query. Please indicate what you want and we can rewrite the query.
For the exact query shown, you don't need 2 levels of subquery
SELECT
C.col_c1 AS Data,
(
SELECT count(col_b1)
FROM tbl
WHERE col_b2 <= C.col_c1
) A
FROM (
SELECT col_c1 # subquery to get distinct c1
FROM tbl
GROUP BY col_c1) C;