How to get correct result with JOIN in sql? - mysql

I wanted to get badge list with issued times.
This is brief info of my tables.
table Badge:
+----+-----------+
| id | name |
+----+-----------+
| 1 | professor |
| 2 | campaign |
| 3 | test |
+----+-----------+
table issue_history:
+----+-----------+--------+
| id | badge_id | ts |
+----+-----------+--------+
| 1 | 1 | 0908 |
| 2 | 1 | 0909 |
| 3 | 3 | 0909 |
+----+-----------+--------*
To get result I used LEFT JOIN.
SELECT
b.id,
b.name,
COUNT(*) AS issued_times
FROM
badge b
LEFT JOIN
issue_history h
ON
b.id = h.badge_id
GROUP BY
b.id
I expected result like below
+----+-----------+--------------+
| id | name | issued_times |
+----+-----------+--------------+
| 1 | professor | 2 |
| 2 | campaign | 0 |
| 3 | test | 1 |
+----+-----------+--------------+
But I got wrong result
+----+-----------+--------------+
| id | name | issued_times |
+----+-----------+--------------+
| 1 | professor | 2 |
| 2 | campaign | 1 |
| 3 | test | 1 |
+----+-----------+--------------+
As you can see, the issues times of campaign badge is 0.
But the result shows its value as 1.
How can I fix this issue?

The issue here is that Count(*) counts all the rows in a particular group. Now, even when you don't have any history row for a specific badge, you you would still have one row for the badge corresponding to the base table badge. That is why, you were getting the count as 1.
To count the history rows, you need to Count() the badge_id from the history table. So if there is no matching row in the history table, badge_id on right side table would be NULL and COUNT(NULL) = 0:
SELECT
b.id,
b.name,
COUNT(h.badge_id) AS issued_times
FROM
badge b
LEFT JOIN
issue_history h
ON
b.id = h.badge_id
GROUP BY
b.id,
b.name
Refer this official MySQL document for further understanding: https://dev.mysql.com/doc/refman/8.0/en/counting-rows.html
Also, your GROUP BY usage was not valid; I added b.name in the GROUP BY for it to be valid. Check this to get an understanding: https://stackoverflow.com/a/34115425/2469308

Related

How can I get a total count of items?

I'm trying to figure out a MYSQL string and my noob-ness is getting in my way. I'm trying to count the total number of teams per phase.
Tables to consider:
phases
+----+------------+
| id | phase_name |
+----+------------+
| 1 | start |
| 2 | middle |
| 3 | end |
| 4 | finish |
+----+------------+
teams
+----+-----------+----------+
| id | team_name | phase_id |
+----+-----------+----------+
| 1 | team1 | 2 |
| 2 | team2 | 3 |
| 3 | team3 | 3 |
| 4 | team4 | 4 |
| 4 | team5 | 3 |
+----+-----------+----------+
Desired result
+----------+------------+-----------+
| phase_id | phase_name | tot_teams |
+----------+------------+-----------+
| 1 | start | NULL |
| 2 | middle | 1 |
| 3 | end | 3 |
| 4 | finish | 1 |
+----------+------------+-----------+
I've tried:
SELECT
T.phase_id, P.phase_name, COUNT(*) AS tot_teams
FROM
teams T
LEFT JOIN
phases P ON P.id = T.phase_id
GROUP BY
phase_id;
but that only shows the affected phase_id's...and I'm hoping to get ALL phase_id's in a table. I also tried:
SELECT
P.phase_name, T.phase_id, COUNT(*)
FROM
teams T
RIGHT JOIN
phases P on P.`id` = T.`phase_id`
GROUP BY
P.id
but that shows invalid data. (For example, phase_id has a qty of 1 but doesn't show up in the teams table.
Can you point me in the right direction?
Thanks!
The RIGHT JOIN is correct, but you need to use COUNT(T.phase_id) instead of COUNT(*). Otherwise, you're counting the row containing NULL that's generated for the phase with no teams.
Most people prefer to use LEFT JOIN, putting the master table first.
SELECT P.phase_name, P.phase_name, COUNT(T.phase_id)
FROM phase AS P
LEFT JOIN teams AS T ON P.id = T.phase_id
GROUP BY P.id

Select max value in subquery

I have these two tables:
Student:
| name | email |
|---------------------|-------------------------|
| Arturo Vidal | arturo.vidal#usm.cl |
| Bastian Quezada | bastian#usm.cl |
| Javier Jeria | javier#usm.cl |
| Sebastian Piñera | sebastian#presidente.cl |
| Sebastian Gallardo | sebastian#usm.cl |
Class:
| classId | email | signUpDate |
|---------|-------------------------|-------------|
| 1 | sebastian#usm.cl | 2018-01-01 |
| 1 | javier#usm.cl | 2019-10-01 |
| 1 | bastian#usm.cl | 2018-07-01 |
| 2 | sebastian#usm.cl | 2018-05-04 |
| 2 | bastian#usm.cl | 2018-01-01 |
| 3 | bastian#usm.cl | 2018-12-05 |
| 3 | sebastian#usm.cl | 2018-02-01 |
| 4 | arturo.vidal#usm.cl | 2018-03-01 |
| 5 | sebastian#presidente.cl | 2018-03-01 |
I want to show the name the last student that signed up for each classId. That means, I should get a name for classId 1, one for classId 2, etc. My solution for firstly getting the mails (to know the student's name after) is this:
select classId, email, max(signUpDate)
from Class
group by classId
it prints the max date, which is ok, but it also prints the wrong mails for each date:
| ClassId | email | max(signUpDate) |
|---------|-------------------------|-----------------|
| 1 | sebastian#usm.cl | 2019-10-01 |
| 2 | sebastian#usm.cl | 2018-05-04 |
| 3 | bastian#usm.cl | 2018-12-05 |
| 4 | arturo.vidal#usm.cl | 2018-03-01 |
| 5 | sebastian#presidente.cl | 2018-03-01 |
which is completely wrong (). Therefore, when I try to join the the values for getting the names, I get incorrect values.
In other words, I don't understand why are the rows mixing up. Is there any solution for getting correct emails for the max(signUpDate) for each ClassId?
Thanks for your time
i have created the test data fiddle and made an easy and understandable query to fetch the required data, i.e:
SELECT DISTINCT classId,
std.name,
Class.email,
signUpDate
FROM CLASS
INNER JOIN Student std ON std.email = Class.email
WHERE signUpDate IN
(SELECT max(signUpDate)
FROM CLASS
GROUP BY classId)
Sql Fiddle here
This is an instance of a very common class of questions: find the whole row FOR EACH GROUP of the field that maximizes some value (in the group). In your case, you want to GROUP BY the ClassId, and FOR EACH ONE OF THESE GROUPS, you want the whole row of the field with the maximum signupDate.
SHORT ANSWER: You can use this query:
SELECT
C.ClassId,
S.name
FROM
(
SELECT A.*
FROM Class AS A
LEFT JOIN Class AS B
ON A.email = B.email AND A.signupDate < B.signupDate
WHERE B.email IS NULL
) AS C
LEFT JOIN Student AS S ON S.email=C.email
LONG ANSWER:
Here you can find a very clear explanation of what I have just said.
Assuming that we can use the e-mail at your tables as unique identifier, you can do FIRST a join (on the e-mail field) of the table "Class" with itself, to select the "maximum date" for each class id. After that, you join (on the e-mail field) with the table "Student". After that, you will have a table with all the fields of the "Class" table and all the fields of the "Student" table. You can select the fields that you need. In the following example, I will select "Class.classId" and "Student.name"
If you run this query:
SELECT A.*
FROM Class AS A
LEFT JOIN Class AS B
ON A.email = B.email AND A.signupDate < B.signupDate
WHERE B.email IS NULL
You obtain this table:
+---------+-------------------------+------------+
| ClassId | email | signupDate |
+---------+-------------------------+------------+
| 1 | javier#usm.cl | 2019-10-01 |
| 2 | sebastian#usm.cl | 2018-05-04 |
| 3 | bastian#usm.cl | 2018-12-05 |
| 4 | arturo.vidal#usm.cl | 2018-03-01 |
| 5 | sebastian#presidente.cl | 2018-03-01 |
+---------+-------------------------+------------+
Now you can join this with the table "Student", and select the fields that you want. If you run the query provided in the "short answer" part of this post, you get the following result:
+---------+--------------------+
| ClassId | name |
+---------+--------------------+
| 4 | Arturo Vidal |
| 3 | Bastian Quezada |
| 1 | Javier Jeria |
| 5 | Sebastian Piñera |
| 2 | Sebastian Gallardo |
+---------+--------------------+
Try this:
SELECT A.classId, C.name, C.email, B.signUpDate
FROM
(SELECT classId, max(signUpDate) maxSignUpDate
FROM Class
GROUP BY classId) A JOIN Class B
ON A.classId=B.classId AND A.maxSignUpDate=B.signUpDate
JOIN Student C ON C.email=B.email;
I assume the email to be the ID field of the Student table. See MySQL Join Made Easy and MySQL GROUP BY for insights.
See it run on SQL Fiddle.

Counts from 3 tables with 2 left joins and 1 composite primary key

I have 3 tables like this
SecretAgents
| id | name |
|----|------|
| 1 | A |
| 2 | B |
Victims
| id | name | agent_id |
|----|------|----------|
| 1 | Z | 1 |
| 2 | Y | 1 |
| 3 | X | 2 |
Data
| id | keys | values | victim_id | form_id |
|----|------|--------|-----------|---------|
| 1 | a1 | x | 1 | 1 |
| 2 | a2 | xx | 1 | 2 |
| 3 | a3 | xxx | 2 | 1 |
| 4 | a5 | xxx | 1 | 1 |
I have to get the count of forms(here victim_id and form_id are composite primary keys) and the count of victims for each agent.
I have tried this for any 2 tables with left joins and group by but I am not able to achieve the same together. If anyone can be generous enough to offer a pointer/solution, that would be super awesome..
EDIT 1: The query
This is definitely not the right query but anyways
SELECT count(DISTINCT v.id) as victimcount, `sa`.`username`, `sa`.`id`, count(DISTINCT d.form_id) as submissions
FROM `SecretAgents` as `sa`
LEFT JOIN `Victims` as `v` ON `v`.`agent_id`=`sa`.`id`
LEFT JOIN `Data` as `d` ON `d`.`victim_id`=`v`.`id`
GROUP BY `v`.`agent_id`
ORDER BY `sa`.`id` ASC
The victimcount is correct but the submissions count becomes wrong. Tried lots of other things too but this is the most relevant...
Thanks
I believe you can count the forms-per-agent like so:
SELECT COUNT(*) as form_count, a.id as id, a.name as agent
FROM Data d
LEFT JOIN Victims v ON v.id = d.victim_id
LEFT JOIN SecretAgents a on v.agent_id = a.id
GROUP BY a.id;
To count the victims, just leave off the Data table.

mysql request using join and group_concat

I have three tables "names", "groups", "tasks"
"names"
| nid | name |
| 1 | John |
| 2 | Jim |
| 3 | Jerry |
"groups"
| gid | nid |
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
"tasks"
| tid | gid |
| 1 | 2 |
| 2 | 1 |
I want to get a list of names, that belong to a task, like this:
| tid | names |
| 1 | Jim,Jerry |
| 2 | John,Jim |
I successfully tried:
SELECT t.tid,n.name
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
This works so far, but the names are in single outputs so I tried to use GROUP_CONCAT(), but this does not work. I just get the first task with the wrong names!?!?
SELECT t.tid,GROUP_CONCAT(n.name)
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
I have never used GROUP_CONCAT() before. It works well, when using it in a single SELECT without joins. A short explanation what I did wrong, would be nice.
When using group_concat you need to group your results.
Add group by t.id at the end of your query.

How can I join two tables, keeping rows that do not meet the JOIN condition?

ticket
+----------+--------+
| ticketID | assign |
+----------+--------+
| 1015 | NULL |
| 1020 | James |
| 1021 | Nick |
+----------+--------+
staffinfo
+---------+-------+
| staffID | staff |
+---------+-------+
| 1 | Jane |
| 2 | James |
| 3 | Nick |
| 4 | Cole |
+---------+-------+
SELECT staff,COUNT(*) as count FROM staffinfo,ticket
WHERE ticket.assign = staffinfo.staff
GROUP BY staff
result:
+-------+-------+
| staff | count |
+-------+-------+
| James | 1 |
| Nick | 1 |
+-------+-------+
Works fine, but infact i need smthing like:
+-------+-------+
| staff | count |
+-------+-------+
| James | 1 |
| Nick | 1 |
| Jane | 0 |
| Cole | 0 |
+-------+-------+
COUNT doesnt count records that arent in the table, and since i just started learning SQL, i wanna ask if theres a way to count as the above result?
you should be using LEFT JOIN
SELECT a.staff, COUNT(b.assign) as count
FROM staffinfo a
LEFT JOIN ticket b
ON b.assign = a.staff
GROUP BY a.staff
SQLFiddle Demo
To fully gain knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins
Use LEFT JOIN
The LEFT JOIN keyword returns all rows from the left table (table_name1), even if there are no matches in the right table (table_name2).
SELECT staffinfo.staff, count(ticket.assign)
FROM staffinfo
LEFT JOIN ticket
ON ticket.assign =staffinfo.staff
GROUP BY staffinfo.staff
The LEFT JOIN keyword returns all the rows from the left table (staffinfo), even if there are no matches in the right table (ticket).