Count 2 different things from 2 different table on one query MYSQL - mysql

Im trying to count 2 different things from 2 different table with one query.
My problem is that one of my joins affects on the other join count.
I want each of the joins to count without any connection to the count from the other join.
Here is the query:
SELECT score.score, u.user_name,
COUNT(mrank.user_id) as rank, COUNT(cr.id) as completedChallenges
FROM user u
LEFT OUTER JOIN challenges_score_user_rel score
ON score.user_id = u.id AND score.challenge_group_id = 0
LEFT OUTER JOIN challenges_score_user_rel mrank
ON mrank.score >= score.score AND mrank.challenge_group_id = 0 AND (SELECT forGym.gym
FROM user forGym WHERE forGym.id = mrank.user_id) = 22
LEFT OUTER JOIN challenges_requests cr
ON u.id = cr.receiver AND cr.status = 3
WHERE u.gym = 22 AND score.score IS NOT NULL GROUP BY u.id ORDER BY score.score DESC LIMIT 20
+------------------+------+---------------------+
| score| user_name | rank | completedChallenges |
+------------------+------+---------------------+
| 999 | A | 3 | 3 |
+------------------+----------------------------+
| 155 | B | 2 | 0 |
+------------------+----------------------------+
| 130 | C | 3 | 0 |
+------------------+----------------------------+
| 24 | D | 4 | 0 |
+------------------+----------------------------+
As you can see from the results I get is that user A is in the first place but got rank 3.
The rank should be number in the ordered by score.
The count for the rank is from this join:
LEFT OUTER JOIN challenges_score_user_rel mrank
ON mrank.score >= score.score AND mrank.challenge_group_id = 0 AND (SELECT forGym.gym
FROM user forGym WHERE forGym.id = mrank.user_id) = 22
If I remove this join:
LEFT OUTER JOIN challenges_requests cr
ON u.id = cr.receiver AND cr.status = 3
The count is fine and I get the correct rank for the all the users.
Why does 2 joins affect each other can I make them count on they own?

The easiest way to solve this is to use count(distinct):
SELECT score.score, u.user_name,
COUNT(distinct mrank.user_id) as rank,
COUNT(distinct cr.id) as completedChallenges
The problem is that you are getting a cartesian product for each user. If the numbers are large, then this is not the best performing solution. In that case, you want to pre-aggregate the data in the from clause or use a correlated subquery in the SELECT clause.

Related

MySQL JOIN + WHERE + GROUP BY

Here DB Structure:
turns DB Table
+-----------+-------------+------------+------------+----------------+
| turnNumber| userId | locationId | status | itemsPurchased |
+-----------+-------------+------------+------------+----------------+
| 32 | 1 | 1 | 1 | 20 |
| 33 | 2 | 1 | 0 | 0 |
+-----------+-------------+------------+------------+----------------+
locations DB Table
+-----------+---------+---------+
| id | Address | ZIPCode |
+-----------+---------+---------+
| 1 | ... | 12345 |
| 2 | ... | 67890 |
+-----------+---------+---------+
Im trying to get every location data (Address, ZIPCode...) + the amount of turns pending (with status 0) per location + the sum of items purchased per location (for all turns even if their state is 1)
Here my Query:
SELECT
l.*,
COUNT(t.id) AS turns,
SUM(IF(t.itemsPurchased > 0, t.itemsPurchased, 0)) AS items
FROM turns t RIGHT OUTER JOIN locations l
ON t.locationId = l.id
WHERE t.status = 0 AND
l.ZIPCode = XXXX
GROUP BY l.id
The thing is when i put the t.status condition it doesnt get the location data when theres no turn with status 0 in turns table, also even if it would, i guess the count for items purchased would take in count only turns with status 0 and not all turns.
Im wondering if theres a way to get all data within the same query, please Help!
Edit:
The expected output is as following:
+-----------+-------------+------------+------------+----------------+
| id | Address | ZIPCode | turns | itemsPurchased |
+-----------+-------------+------------+------------+----------------+
| 1 | ... | 12345 | 1 | 20 |
+-----------+-------------+------------+------------+----------------+
The condition "t.status = 0" in the WHERE clause negates the "outerness" of the join; the same result we'd get with an INNER JOIN.
With the outer join, any rows in locations that don't have a matching row in turns will be returned with NULL values for the all of the t. columns. The unmatched rows from locations are going to get excluded by the condition in the WHERE clause.
Consider relocating that condition from the WHERE clause to ON clause of the outer join.
Or consider relocating that condition into an aggregate expression.
As an example:
SELECT l.id
, l.zipcode
, SUM(IF(t.status = 0, 1, 0)) AS turns
, SUM(IF(t.status = 0 AND t.itemspurchased > 0, t.itemspurchased, 0)) AS items
FROM locations l
LEFT
JOIN turns t
ON t.locationid = l.id
AND t.status = 0
WHERE l.zipcode = XXXX
GROUP
BY l.id
, l.zipcode
Using a LEFT JOIN and putting the criteria on different places.
To avoid that by using a criteria in the WHERE clause for the LEFT JOIN'd table, that it would give it the effect on as it were an INNER JOIN.
SELECT
loc.ZIPCode,
COUNT(DISTINCT CASE turns.status WHEN 0 THEN turns.id END) AS turns,
SUM(CASE
WHEN turns.status = 1 AND turns.itemsPurchased > 0
THEN turns.itemsPurchased
ELSE 0
END) AS items
FROM locations loc
LEFT JOIN turns ON turns.locationId = loc.id
WHERE loc.ZIPCode = 12345
GROUP BY loc.id, loc.ZIPCode

if field X in any row has value Y then ignore else group results by GROUP BY in mysql without subquery

I have two tables:
users
id | admin_id | name
-----------------------
1 | 10 | a
2 | 10 | b
3 | 15 | c
4 | 10 | d
5 | 10 | e
6 | 10 | f
status
id | user_id | status
-----------------------
1 | 2 | error
2 | 2 | success
3 | 2 | error
4 | 4 | success
5 | 6 | error
now I am trying to get all users of admin_id 10 who don't have any entry in table status and the users which don't have any entry with status success in status table by using group by clause.
My expected results are:
Result:
id | admin_id | name | status
------------------------------------
1 | 10 | a | null
5 | 10 | e | null
6 | 10 | f | error
So as per the expected results the query should ignore the user_id 2,4 because these two have success status in status table. Right now I am trying to make the query like this:
SELECT
users.*,
status.status
FROM
users
LEFT JOIN
status ON users.id = status.user_id
WHERE
users.admin_id = 10 AND status.status != "success"
GROUP BY
users.id
It can be done by sub-query but my requirement is to do it with single query and without using sub-query.
Is there a way to do this?
Thanks!
You should move the success condition away from the where clause into the join ... on clause:
SELECT
users.*,
status.status
FROM
users
LEFT JOIN
status ON users.id = status.user_id AND status.status != "success"
WHERE
users.admin_id = 10
GROUP BY
users.id
A side note unrelated to your question: it is non-standard to SQL to specify columns in your select clause which are neither grouped by, nor functionally dependent on a grouped by expression, nor aggregated.
So better is to aggregate the status in some way (choose what best suits your needs: min, max, ...):
SELECT
users.*,
min(status.status) as status
FROM
users
LEFT JOIN
status ON users.id = status.user_id AND status.status != "success"
WHERE
users.admin_id = 10
GROUP BY
users.id
Try the next query. We first group by the users columns you are using on the SELECT clause, then we use HAVING clause for discard those groups whose count of status="success" is greater than zero.
SELECT
users.id,
users.admin_id,
users.name,
MAX(status.status)
FROM
users
LEFT JOIN
status ON status.user_id = users.id
WHERE
users.admin_id = 10
GROUP BY
users.id, users.admin_id, users.name
HAVING
SUM(CASE WHEN status.status = "success" THEN 1 ELSE 0 END) = 0

mysql help to make a report

i have two table here is table_user and table_feedback like below
table_user
| id | name |
|----|------|
| 1 | john |
| 2 | tony |
| 3 | mona |
table_feedback
| id | rate | user_id | date |
|----|------|---------|----------|
| 1 | 1 | 3 |2015-11-2 |
| 2 | 1 | 2 |2015-11-2 |
| 3 | 1 | 3 |2015-11-1 |
I wanted to show report by date from table_feedback including name and id from table_user and all user will be show if table_feedback didn't contain the user id then this will be return blank data. I have idea about inner join and here is my query. problem is that the query return 2 row only but i need 3 row including table_user id 1 with blank column rate.
Here is my query below.
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
table_feedback.date
FROM table_feedback
INNER JOIN table_user
ON table_user.id = table_feedback.user_id
WHERE table_feedback = '2015-11-2'
expected_result_table
| user_id | name | rate | date |
|-------- |------|------|----------|
| 1 |jony | |2015-11-2 |
| 2 |tony | 1 |2015-11-2 |
| 3 |mona | 1 |2015-11-2 |
The solution to this is an outer join. Anytime you find yourself thinking along the lines of "I need to see all rows from this table, regardless of a match in another table..." you should look to an outer join.
We can use an outer join to select all users, and link them to the feedbackTable in our JOIN clause. This will return null values for any columns in the table that don't match up. Try this:
SELECT u.id, u.name, t.rate, t.dateCol
FROM userTable u
LEFT JOIN feedbackTable t ON t.user_id = u.id AND t.dateCol = '2015-11-02';
Here is an SQL Fiddle example. As a side note, it is good practice not to name date columns date since that is a keyword in MySQL.
Edit based on your expected results:
To make sure the date column appears in each row, you can hardcode it into your select. If you choose to use a variable, you won't have to update the date twice each time, you can just update the declaration:
SET #reportDate = '2015-11-02';
SELECT u.id, u.name, t.rate, #reportDate
FROM userTable u
LEFT JOIN feedbackTable t ON t.user_id = u.id AND t.dateCol = #reportDate;
Here is an updated SQL Fiddle.
My guess is you need to use LEFT JOIN:
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
table_feedback.date
FROM table_user
LEFT JOIN table_feedback
ON table_user.id = table_feedback.user_id
AND table_feedback.date = '2015-11-2'
In order to include user without feedback you need to use LEFT OUTER JOIN
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
'2015-11-2'
FROM table_feedback
LEFT OUTER JOIN table_user
ON table_user.id = table_feedback.user_id
WHERE table_feedback = '2015-11-2'
You need to do three modifications to your query:
Use LEFT JOIN instead of INNER JOIN (to get all the users)
Change the table order (first the table you want to get all rows
from)
As the user 1 (john) does not have any data in the second table, you
cannot limit the rows in WHERE-clause. Do the limitation in JOIN
instead, so it applies only to the rows that are matching the JOIN.
So:
SELECT
table_user.id,
table_user.name,
table_feedback.rate,
table_feedback.date
FROM table_user
LEFT JOIN table_feedback ON table_user.id = table_feedback.user_id and table_feedback.date = '2015-11-02'

Strange order of results when adding joins

I'm trying to build a commenting system on my website but having issues with ordering the comments correctly. This is a screenshot of what I had before it went wrong:
And this is the query before it went wrong:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
ORDER BY
com.parent_id DESC;
I now want to include each comment's votes from my blog_comment_votes table, using a LEFT OUTER JOIN, and came up with this query, which works, but screws with the order of results:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username,
IFNULL(c.cnt,0) votes
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
LEFT OUTER JOIN (
SELECT comment_id, COUNT(vote_id) as cnt
FROM blog_comment_votes
GROUP BY comment_id) c
ON com.comment_id = c.comment_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
ORDER BY
com.parent_id DESC;
I now get this order, which is bizarre:
I tried adding a GROUP BY clause on com.comment_id but that failed too. I can't understand how adding a simple join can alter the order of results! Can anybody help back on the correct path?
EXAMPLE TABLE DATA AND EXPECTED RESULTS
These are my relevant tables with example data:
[users]
user_id | username
--------|-----------------
1 | PaparazzoKid
[blog_comments]
comment_id | parent_id | is_reply | article_id | user_id | comment
-----------|-----------|----------|------------|---------|---------------------------
1 | 1 | | 1 | 1 | First comment
2 | 2 | 1 | 1 | 20 | Reply to first comment
3 | 3 | | 1 | 391 | Second comment
[blog_comment_votes]
vote_id | comment_id | article_id | user_id
--------|------------|------------|--------------
1 | 2 | 1 | 233
2 | 2 | 1 | 122
So the order should be
First comment
Reply to first comment +2
Second Comment
It's difficult to say without looking at your query results, but my guess is that it's because you are only ordering by parent id and not saying how to order when two records have the same parent id. Try changing your query to look like this:
SELECT
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username,
COUNT(c.votes) votes
FROM
blog_comments AS com
LEFT JOIN
users AS usr ON com.user_id = usr.user_id
LEFT JOIN
blog_comment_votes c ON com.comment_id = c.comment_id
WHERE
com.article_id = :article_id AND com.moderated = 1 AND com.status = 1
GROUP BY
com.comment_id,
com.parent_id,
com.is_reply,
com.user_id,
com.comment,
com.posted,
usr.username
ORDER BY
com.parent_id DESC, com.comment_id;

How would I accomplish this using SQL?

I have 2 tables in my database that look like so:
clients
+-------------+
| id | sms |
|------+------|
| 1 | 0 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
+------+------+
clients_lists_relationships
+----------------------+
| listid | clientid |
|----------+-----------|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 3 | 1 |
+----------+-----------+
Now what I'm trying to do is get a list of clients who are in a bunch of lists. I do that like so:
SELECT c.id,
l.*
FROM clients AS c,
clients_lists_relationships AS l
WHERE c.id = l.clientid
AND c.sms = '1'
AND ( l.listid = '1'
OR l.listid = '2' );
This does give me a list of the clients that I need. But because a client can be in more than one list I get the same client more than once. How would I limit this to only one row for each client no matter how many lists they are in?
If you just need any client that is in a list, you can just query the relationship table:
SELECT DSITINCT(clientid) FROM clients_lists_relationships
You can also use that distinct on your combined query, but be aware that the "listid" you'll get is just one.
Use GROUP BY:
SELECT c.id,
l.listid
FROM clients c
INNER JOIN clients_lists_relationships l
ON c.id = l.clientid
WHERE c.sms = 1
AND l.listid IN (1,2)
GROUP BY c.id
Note that by doing this you lose information on which lists the client was a member of. This means that you should probably not select anything from client_lists_relationships as this information is either redundant (clientid) or incomplete (listid).
First of all take a look at MySQL:: JOIN It's much better than the WHERE statements you use now.
I think you are looking for GROUP BY.
In total, the query look like:
SELECT
c.id,
l.*
FROM
clients AS c
INNER JOIN
clients_lists_relationships AS l
ON
l.clientid = c.id
AND
c.sms = '1'
AND
( l.listid = '1'
OR l.listid = '2' );
GROUP BY
c.id
To return just the clients participating in more than 1 list you may want to consider using the HAVING clause:
SELECT c.id
FROM Clients c
INNER JOIN Client_Lists_Relationships l
ON l.clientid = c.id
WHERE c.sms = 1
HAVING COUNT(L.listid) > 1
GROUP BY c.id