Duplicate COUNT when doing JOINS and CONDITIONS along with GROUP BY - mysql

I have the following two tables
games
+--------+-------------+----------------+----------------+
| gameID | challengeID | player1_userID | player2_userID |
+--------+-------------+----------------+----------------+
| 1 | 1 | 1 | 2 |
| 2 | 1 | 1 | 3 |
| 3 | 2 | 1 | 4 |
+--------+-------------+----------------+----------------+
challenges
+-------------+---------+
| challengeID | content |
+-------------+---------+
| 1 | one |
| 2 | two |
| 3 | three |
+-------------+---------+
And I want the output to be as below. Basically, a user (say, userID = 1) wants all challenges' data along with the count of the games played against each challenge and also information on whether that challenge has been played by the requesting user.
output (for userID = 1)
+-------------+---------+---------+---------------------+
| challengeID | content | n_games | played_by_requestor |
+-------------+---------+---------+---------------------+
| 1 | one | 2 | TRUE |
| 2 | two | 1 | TRUE |
| 3 | three | 0 | FALSE |
+-------------+---------+---------+---------------------+
This appeared to be deceptively simple, but I've been trying it for the past 4 hours (it is 1:35am now) and the best I could get to was the code below.
SET #myID = 1;
SELECT
COUNT(g1.challengeID) as n_games,
CASE
WHEN g.gameID IS NULL THEN FALSE ELSE TRUE
END AS played_by_requestor,
c.*
FROM challenges c
LEFT JOIN games g
ON c.challengeID = g.challengeID AND
(player1_userID = #myID or player2_userID = #myID)
LEFT JOIN games g1
ON c.challengeID = g1.challengeID
GROUP BY c.challengeID;
But it gives the wrong result. For requestor userID = 1, this code gives n_games = 4 for challengeID = 1 and also n_games = 1 for challengeID = 2.
SQL Fiddle here
Thanks.

Finally, got it to work !
SET #myID = 3;
SELECT
COUNT(g.challengeID) AS n_games,
CASE
WHEN uC.p_challengeID IS NULL THEN FALSE ELSE TRUE
END AS played_by_requestor,
c.*
FROM challenges c
LEFT JOIN games g
ON (c.challengeID = g.challengeID)
LEFT JOIN
(SELECT g1.challengeID AS p_challengeID
FROM games g1
WHERE (player1_userID = #myID OR player2_userID = #myID)
GROUP BY g1.challengeID) uC
ON c.challengeID = uC.p_challengeID
GROUP BY c.challengeID;
If there is a more elegant solution (e.g. without using two joins on games table), I'll gladly accept it as the answer.

Related

Get all person rows when left joined values missing [duplicate]

I have two tables and need to get all rows from the first one and then check which values from the second table match the first table.
My goal is to get all so called 'achievements' and then check which one the user has reached.
achievements
+---------------+-------------+
| achievementID | description |
+---------------+-------------+
| 1 | goal1 |
| 2 | goal2 |
| 3 | goal3 |
+---------------+-------------+
achievement_user
+---------------------+---------------+--------+
| achievementRecordID | achievementID | userID |
+---------------------+---------------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 3 |
| 3 | 4 | 2 |
| 4 | 3 | 1 |
+---------------------+---------------+--------+
My desired result for a query where I check the results for userID = 1 would be something like
+---------------+---------------+--------------+
| achievementID | description | solvedByUser |
+---------------+---------------+--------------+
| 1 | goal1 | true |
| 2 | goal2 | false |
| 3 | goal3 | true |
+---------------+---------------+--------------+
The new column solvedByUser could be basically any datatype (boolean, int, ...).
I just need a list of all available achievements and then see which one the user has reached.
You can left join the achievments table with achievement_user:
select a.*, (au.userID is not null) solvedByUser
from achievements a
left join achievement_user au
on au.achievementID = a.achievementID
and au.userID = 1
solvedByUser is a 0/1 flag that indicates whether the given achievement was reached by the given user.
I think you need a left join -
SELECT A.achievementID, A.description, CASE WHEN AU.userID IS NOT NULL THEN 'true' ELSE 'false' solvedByUser
FROM achievements A
LEFT JOIN achievement_user AU ON A.achievementID = AU.achievementID
AND userID = 1; -- YOUR INPUT ID
You need a left join:
select a.*,
case when u.achievementID is null then 'false' else 'true' end solvedByUser
from achievements a left join achievement_user u
on u.achievementID = a.achievementID and u.userid = 1

joining multiple tables and returning a zero if not row in one

I'm trying to setup a new permissions database in MySQL and I'm breaking my brain over something that I'm sure is very simple. I'm certain something to this tune has been answered here before but after hours of searching I have found nothing that works.
I have 4 tables that are relevant
Permission (contains every possible permission)
|permission_name | description |
--------------------------------
|users.list | etc. etc. |
|users.update | etc. etc. |
|users.delete | etc. etc. |
User
| id | fname | group_id |
------------------------------
| 1 | John | 1 |
| 2 | Nancy | 1 |
| 3 | Paul | 2 |
Group
| group_id | group_name |
-------------------------
| 1 | Webmasters |
| 2 | Corporate |
| 3 | HR |
Group_permission (contains permissions relevant to each group)
| group_id | permission_name | permission_type (1=Y|0=not set|-1=N)
----------------------------------------------
| 1 | users.list | 1 |
| 1 | users.update | 1 |
| 2 | users.list | 1 |
OK so lots of relations going on, but I'm trying to get ALL the group permissions for a specific user EVEN if the group permission doesn't exist yet.
I imagined this being some sort of left join using a permission table as a base, but whenever I include the WHERE user.id = 2 it limits my result set down and won't include nulls on the right side.
SELECT a.permission_name, IFNULL(b.permission_type, 0)
FROM permission a
LEFT JOIN group_permission b on b.permission_name = a.permission_name
LEFT JOIN user c on c.group_id = b.group_id
WHERE c.id = 2
the result I want to see for Nancy is
|permission_name | permission_type |
------------------------------------
|users.list | 1 |
|users.update | 0 |
|users.delete | 0 |
I won't know what group the user is in on the PHP side, so I have to query by using the users ID only.
All I'm getting is
|permission_name | permission_type |
------------------------------------
|users.list | 1 |
Any help appreciated. TIA
It ended up being just a subquery that did the trick.
SELECT a.permission_name, IFNULL(b.permission_type, 0)
FROM permission a
NATURAL LEFT JOIN
(
SELECT a.group_id, a.permission_name, a.permission_type FROM group_permission a
NATURAL LEFT JOIN users b
WHERE b.id = 2
) as b
Not 100% sure that this will work, but try joining the group_permissions table to the permissions table.
SELECT a.permission_name, IFNULL(b.permission_type, 0)
FROM group_permission a
LEFT JOIN permission b on a.permission_name = b.permission_name
LEFT JOIN user c on c.group_id = b.group_id
WHERE c.id = 2

Querying MySQL tables for a item a user hasnt 'scored' yet

Tables
__________________ ________________________________
|______name________| |____________scores______________|
|___id___|__name___| |_id_|_user-id_|_name-id_|_score_|
| 1 | bob | | 1 | 3 | 1 | 5 |
| 2 | susan | | 2 | 1 | 3 | 4 |
| 3 | geoff | | 3 | 3 | 2 | 3 |
| 4 | larry | | 4 | 2 | 4 | 5 |
| 5 | peter | | 5 | 1 | 1 | 0 |
-------------------- ----------------------------------
Im looking to write a query that returns a RANDOM name from the 'name' table, that the user hasnt scored so far.
So given user '1' for example, it could return 'susan, larry or peter' as user '1' hasnt given them a score yet.
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id
WHERE votes.user_id = 1
AND (votes.score IS NULL);
So far I have this, but it doesnt seem to be working as I would like
(atm it doesnt return a random, but all, but this is wrong)
Any help would be appreciated.
If you are filtering on some field of outer joined table type of join is automatically changed to inner. In your case it's condition
votes.user_id = 1
So you need to move that condition from WHERE to ON
SELECT *
FROM names
LEFT JOIN
votes
ON names.id = votes.name_id and votes.user_id = 1
WHERE (votes.score IS NULL);
Consider moving the condition from WHERE to JOIN ON clause since you are performing an OUTER JOIN else the effect would be same as INNER JOIN
LEFT JOIN votes
ON names.id = votes.name_id
AND votes.user_id = 1
WHERE votes.score IS NULL
ORDER BY RAND();
You could apply :
SELECT name FROM name join scores on name.id=scores.user_id WHERE scores.score=0
You can perform this as a sub-query
SELECT *
FROM names
WHERE id NOT IN (SELECT name_id FROM votes WHERE user_id=1)
ORDER BY RAND()
LIMIT 1

MySQL LEFT Join with multiple possibilities and Inner Join

I don't know if a similar question have been asked, but I looked fow more than hour on mysql in stackoverflow
My problem is, i have multiple tables and I need to join them with both left join and inner join in mysql
Entity table :
id (key) | entityName
1 | john
2 | harris
3 | henry
4 | mark
5 | dom
Activity table
id (key) | entityID | status
1 | 1 | 1
2 | 2 | 0
3 | 4 | 1
Geodata table
id (key) | entityID | moment (timestamps when the entry was done)
1 | 1 | 1429542320 (smaller)
2 | 1 | 1429542331 (bigger)
3 | 2 | 1429542320 (smaller)
4 | 2 | 1429542331 (biger)
5 | 4 | 1429542331 (bigger)
Info table
id (key) | entityID | infos | date
1 | 1 | xxx | today
2 | 1 | xxx | yesterday
3 | 2 | xxx | today
4 | 2 | xxx | yesterday
5 | 3 | xxx | yesterday
6 | 5 | xxx | today
7 | 5 | xxx | yesterday
8 | 5 | xxx | tomorrow
So basically, I need every Entities that has an info for today
Moreover, if their status is true (or 1) (from activity table), show me their date in geodata table.
So this is what i've got :
SELECT e.id,
e.entityName,
i.infos,
a.status,
MAX(g.moment) -- but the max only if status =1
FROM entities AS e
LEFT JOIN activity AS a ON a.entityID = e.id
LEFT JOIN geodata AS g ON g.entityID = e.id
INNER JOIN infos AS i ON e.id = i.entityID
WHERE i.date = 'today'
GROUP BY e.id
I want every entities that has an info about today, but some of them have activity too, so i want to show it (if it doesn't just let the left join put NULL) If the status is 0, I don't need the moment, but if its true, I only need the bigger one (its numbers, so Max() should do it but it breaks)
The expected results is :
id (key) | entityName | infos | status | MAX(moment) | ..other columns
1 | john | xxx | 1 | 1429542331 (the bigger one)
2 | harris | xxx | 0 | NULL
5 | dom | xxx | NULL | NULL
If someone can help me, I'll be very thankful :)
PS.: Sorry for my english, it isn't my first language
You could change the
MAX(g.moment)
to
IF(a.status<>1, NULL, MAX(g.moment))
or alternately change LEFT JOIN geodata AS g ON g.entityID = e.id to LEFT JOIN geodata AS g ON a.entityID = e.id AND a.status = 1
Which one is faster will probably depend on your actual data; the second may be faster as less records are joined, but the more complicated join condition it uses might slow down the joining.

MySQL Join and Default

I'm still very new to SQL queries and can't quite figure this one out.
I have two tables, one table I'm running a general SELECT ... WHERE, super easy SQL statement.
Ex:
SELECT * from maindata where somedata4 LIKE "%data4.%"
This gives me back a list of all 6 entries below, however I want an additional column to show me if the current userdata.userId has a matching row and to include the amount column of that. If it doesn't have that row to default to a value of 0.
Table: maindata
id | somedata | somedata2 | somedata3 | somedata4
_________________________________________________
1 | data1.1 | data2.1 | data3.1 | data4.1
2 | data1.2 | data2.2 | data3.2 | data4.2
3 | data1.3 | data2.3 | data3.3 | data4.3
4 | data1.4 | data2.4 | data3.4 | data4.4
5 | data1.5 | data2.5 | data3.5 | data4.5
6 | data1.6 | data2.6 | data3.6 | data4.6
Table: userdata
id | itemId | amount | userId
_____________________________
1 | 6 | 4 | 1
2 | 4 | 4 | 26
3 | 4 | 2 | 1
It should search table maindata for WHERE somedata4 LIKE "%data4.%" and on each of those entries look in table userdata for userdata.amount with maindata.id = userdata.itemId WHERE maindata.userId = 1
Here's what I currently have for SQL
SELECT m.*, IFNULL(u.amount,0)
from maindata m
LEFT OUTER JOIN userdata u ON m.id = u.itemId
WHERE m.somedata4 LIKE "%data4.%"
What I'm missing is the filtering of only amounts from userdata.userId = 1, I want the entire list to show as it is in that query.
Expected Results:
id | somedata | somedata2 | somedata3 | somedata4 | amount
__________________________________________________________
1 | data1.1 | data2.1 | data3.1 | data4.1 | 0
2 | data1.2 | data2.2 | data3.2 | data4.2 | 0
3 | data1.3 | data2.3 | data3.3 | data4.3 | 0
4 | data1.4 | data2.4 | data3.4 | data4.4 | 4
5 | data1.5 | data2.5 | data3.5 | data4.5 | 0
6 | data1.6 | data2.6 | data3.6 | data4.6 | 2
SELECT m.*, IFNULL(u.amount,0) from maindata m LEFT OUTER JOIN
userdata u ON m.id = u.itemId WHERE m.userId = 1
Here is the SQL Query I was looking for (I think):
SELECT m.*, IFNULL(u.amount,0) AS "Amount"
FROM maindata m
LEFT OUTER JOIN userdata u ON m.id = u.itemId AND userid = 1
WHERE m.somedata4 LIKE "%data4.%"
It's giving me the desired results listed above, I just don't know if it's the most efficient way of handling this request.
SQLFiddle Here
I tried this solution but i am not sure if this is what you are looking for. Let me know.
-------EDIT--------
Now I'm sure about what you are looking for. I had just a couple of minutes so I didn't really optimized the code, but this seems to work except for the order of the fields, that can't be really modified since we're operating in two different sets of results.
By the way, this is the query
SELECT m.*, '0' as amount
from maindata m
left outer JOIN userdata u ON m.id = u.itemId
where (u.userid is null) and m.somedata4 LIKE '%data4.%'
UNION
SELECT m.*, u.amount
from maindata m
inner JOIN userdata u ON m.id = u.itemId
where u.userid = 1 and m.somedata4 LIKE '%data4.%'
and this is the updated fiddle
Hope this helps.