Select max value in subquery - mysql

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.

Related

How to add columns on a view extracted from the same table in MYSQL

i have these tables:
attendance_tbl
id | date |
-----------------------
1 | 2020-03-15 |
2 | 2020-03-16 |
student_tbl
id | attendance_id | name | remark |
---------------------------------------------
1 | 1 | oliver | P |
2 | 1 | john | P |
3 | 2 | oliver | P |
4 | 2 | john | L |
attendance_id is foreign from attendance_tbl
I want to achieve something like this:
name | 2020-03-15 | 2020-03-16 |
--------------------------------------
oliver | P | P |
john | P | L |
i am getting all the results between two dates (starting and beginning) so the columns may add up if there are more dates between the range given by the user.
but i'am having a hard time pulling this through.
This is one of the way to solve the above problem.
select names,
Max(case when attendence_date='2020-03-15' then remark else null end) as '2015-03-15',
max(case when attendence_date='2020-03-16' then remark else null end )as '2015-03-16'
from attendence a
join student_attendence b
on a.id=b.attendence_id
group by names;

How to get correct result with JOIN in sql?

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

Is it possible to use two COUNT and two JOIN in a SQL query from 3 tables?

So what I'm trying to do here is get a report on how many emails (with a MailChimp like app) were sent by different users, but I want two different metrics in one query. I want to know how many individual emails were sent by each user. Meaning if they sent 3 emails to 100 contacts each, that would display 300. But I also want to know how many unique emails were sent, meaning that would display 3.
I'd like to get something that looks like:
-------------------------------------------------------------
| Full Name | Username | Total Sent | Unique Mails |
|-------------|-----------------|------------|--------------|
| John Doe | jdoe#mail.com | 12000 | 4 |
| James Smith | jsmith#mail.com | 6000 | 12 |
| Jane Jones | jjones#mail.com | 4000 | 2 |
| ... | ... | ... | ... |
-------------------------------------------------------------
So I could know that John sends a few emails to a lot of contacts while James sends more emails to fewer contacts.
Here's what my query looks like. I've changed the table and column names, but this is otherwise an exact representation of what it is.
SELECT
CONCAT(Usernames.FirstName, ' ', Usernames.LastName) AS 'Full Name',
Usernames.Username,
COUNT(Sent_Mail_Contacts.IDContact) AS `Total Sent`,
COUNT(Mass_Mail.IDMass_Mail) AS `Individual E-Mails`
FROM Usernames
LEFT JOIN Sent_Mail_Contacts ON Usernames.Username = Sent_Mail_Contacts.Username
LEFT JOIN Mass_Mail ON Usernames.Username = Mass_Mail.Username
GROUP BY Usernames.Username
ORDER BY `Total Sent`
I have a table with Usernames, a table with individual contacts reached by which emails and a table with unique emails.
So does my query make sense or not? Is this even possible? Because right now when I run it, it gives me something like this:
-------------------------------------------------------------
| Full Name | Username | Total Sent | Unique Mails |
|-------------|-----------------|------------|--------------|
| John Doe | jdoe#mail.com | 12000 | 12000 |
| James Smith | jsmith#mail.com | 6000 | 6000 |
| Jane Jones | jjones#mail.com | 4000 | 4000 |
| ... | ... | ... | ... |
-------------------------------------------------------------
I just gives me the same number in both columns and takes 7 minutes to process.
Here is an example of what the 3 tables would look like separately if that can help:
Usernames
------------------------------------------------
| Username | FirstName | LastName | ... |
|-----------------|-----------|----------|-----|
| jdoe#mail.com | John | Doe | ... |
| jsmith#mail.com | James | Smith | ... |
| jjones#mail.com | Jane | Jones | ... |
| ... | ... | ... | ... |
------------------------------------------------
Mass_Mail
----------------------------------------------------
| ID_Mass_Mail | Username | Date | ... |
|--------------|----------------|------------|-----|
| 1 | jdoe#mail.com | 2019-01-16 | ... |
| 2 | jdoe#mail.com | 2019-01-29 | ... |
| 3 | jjones#mail.com| 2019-02-14 | ... |
| ... | ... | ... | ... |
----------------------------------------------------
Sent_Mail_Contacts
---------------------------------------------------------------------
| ID_Mass_Mail | Username | Contact_ID | Contact_Email | ... |
|--------------|----------------|------------|----------------|------
| 1 | jdoe#mail.com | 1 | bob#mail.com | ... |
| 1 | jdoe#mail.com | 2 | jim#mail.com | ... |
| 1 | jdoe#mail.com | 3 | cindy#mail.com | ... |
| ... | ... | ... | ... | ... |
| 2 | jdoe#mail.com | 4 | mike#mail.com | ... |
| 2 | jdoe#mail.com | 2 | jim#mail.com | ... |
| 2 | jdoe#mail.com | 3 | cindy#mail.com | ... |
| ... | ... | ... | ... | ... |
---------------------------------------------------------------------
Use COUNT(DISTINCT ...) :
SELECT
CONCAT(Usernames.FirstName, ' ', Usernames.LastName) AS 'Full Name',
Usernames.Username,
COUNT(Sent_Mail_Contacts.IDContact) AS `Total Sent`,
COUNT(DISTINCT Mass_Mail.IDMass_Mail) AS `Individual E-Mails`
FROM Usernames
LEFT JOIN Sent_Mail_Contacts ON Usernames.Username = Sent_Mail_Contacts.Username
LEFT JOIN Mass_Mail ON Usernames.Username = Mass_Mail.Username
GROUP BY Usernames.Username
ORDER BY `Total Sent`
NB : this will not make the query any faster though. To start with, you should at least make sure that you are using primary/foreign keys relations in the JOINs : Usernames(Username), Sent_Mail_Contacts(Username), Mass_Mail(Username)
Assuming the values in IDMass_Mail indicate a unique email, then you just need to edit the last COUNT to use the DISTINCT keyword.
COUNT(DISTINCT Mass_Mail.IDMass_Mail) AS `Individual E-Mails`
That will return the number of unique values in the grouping by Username.
You should also get a performance boost if you're able to add indexes to the Username columns in the Sent_Mail_Contacts and Mass_Mail tables.
I managed to do it using a query that (besides from changing the actual table and column names due to privacy concerns) looked exactly like this.
SELECT
Accounts.Account_Name AS `account`,
Usernames.Username AS `username`,
COUNT(Mass_Mail_Reached_Contacts.ID_Contact) AS `total_emails`,
COUNT(Mass_Mail_Reached_Contacts.ID_Mass_Mail) /
(
SELECT COUNT(*)
FROM
Mass_Mail_Reached_Contacts
WHERE
Mass_Mail_Reached_Contacts.DATE >= '2019-02-01'
AND
Mass_Mail_Reached_Contacts.DATE <= '2019-02-28'
)
* 100 AS `%`,
COUNT(DISTINCT Mass_Mail.ID_Mass_Mail) AS `unique_emails`,
COUNT(Mass_Mail_Reached_Contacts.ID_Mass_Mail) /
COUNT(DISTINCT mass_mail.ID_Mass_Mail)
AS `avg_contacts_per_email`
FROM
Usernames
LEFT JOIN Mass_Mail_Reached_Contacts ON Mass_Mail_Reached_Contacts.Username = Usernames.Username
LEFT JOIN Account ON Account.ID_Account = Usernames.ID_Account
LEFT JOIN Mass_Mail ON Mass_Mail.ID_Mass_Mail = Mass_Mail_Reached_Contacts.ID_mass_mail
WHERE
Mass_Mail_Reached_Contacts.DATE >= '2019-02-01'
AND
Mass_Mail_Reached_Contacts.DATE <= '2019-02-28'
GROUP BY
Usernames.Username
HAVING COUNT(DISTINCT Mass_Mail.IDMass_Mail) > 0
ORDER BY
`total_emails` DESC
I'm now able to get a table that looks like this
Emails Stats
--------------------------------------------------------------------------------------
| account | username | total_emails | % | unique_emails | avg_contact_email |
|----------|--------------|--------------|-------|------------------------------------
| Bob inc. | bob#mail.com | 28,550 | 14.52 | 12 | 2379.17 |
| ... | ... | ... | ... | ... | ... |
--------------------------------------------------------------------------------------
To start with: Why do Mass_Mail and Sent_Mail_Contacts both contain a Username? This looks redundant. Or is Sent_Mail_Contacts.ID_Mass_Mail nullable?
For this query at least, I suppose we can ignore the Username in Sent_Mail_Contacts completely. What really links the two tables is the ID_Mass_Mail, and you have forgotten this join criteria in your query.
select
ws_concat(' ', u.firstname, u.lastname) as full_name,
u.username,
count(smc.idmass_mail) as total_sent,
count(mm.idmass_mail) as individual_e_mails
from usernames u
left join mass_mail mm on mm.username = u.username
left join sent_mail_contacts smc on smc.id_mass_mail = u.id_mass_mail
group by u.username
order by total_sent;

Left joins, i need an explanation about a code

i am watching a tutorial. There is a code which i don't understand what is supposed to do.
$sql = 'SELECT p.*,
a.screen_name AS author_name,
c.name AS category_name
FROM
posts p
LEFT JOIN
admin_users a ON p.author_id = a.id
LEFT JOIN
categories c ON p.category_id = c.id
WHERE
p.id = ?';
I read about the left joins but i didn't understand them. Can somebody please explain me the code i shared.
Thanks in advance!
Imagine you have two tables. One that stores the information about the programmers on your website, and the other table that keeps track of their online purchases.
PROGRAMMERS Table
+--------------------------------------------+
| ID | NAME | AGE | ADDRESS | SALARY |
+----+----------+-----+-----------+----------+
| 1 | Desire | 32 | 123 fake s| 3000.00 |
| 2 | Jamin | 25 | 234 fake s| 2500.00 |
| 3 | Jon | 23 | 567 fake s| 2000.00 |
| 4 | Bob | 30 | 789 fake s| 1500.00 |
| 5 | OtherGuy | 31 | 890 fake s| 1000.00 |
| 6 | DudeMan | 32 | 901 fake s| 500.00 |
+--------------------------------------------+
PURCHASES Table
+---------------------------------------------+
| ORDER_ID | PROG_ID | DATE | PRICE |
+-------------+---------+---------------------|
| 1 | 1 | 1-1-2017 | 100 |
| 2 | 2 | 1-2-2017 | 200 |
| 3 | 6 | 1-3-2017 | 300 |
+---------------------------------------------|
You decide you need to make a new table to consolidate this information to a table that contains
certain columns you want.
For example, you figure it would be nice for shipping purposes to have a table
that has the ID, the NAME, the PRICE, and the DATE columns.
Currently, the tables we have don't display all of that in a single table.
If we were to LEFT JOIN these tables, we would end up filling the desired columns
with NULL values where there is no information to join.
SELECT ID, NAME, PRICE, DATE
FROM PROGRAMMERS
LEFT JOIN PURCHASES
ON PROGRAMMERS.ID = PURCHASES.PROG_ID;
Notice that I'm selecting the columns I want from the starting table, then joining the right table
even though there might be missing information.
RESULTING TABLE
+-------------------------------------+
| ID | NAME | PRICE | DATE |
+----+----------+-----------------+---+
| 1 | Desire | 100 | 1-1-2017 |
| 2 | Jamin | 200 | 1-2-2017 |
| 3 | Jon | NULL | NULL |
| 4 | Bob | NULL | NULL |
| 5 | OtherGuy | NULL | NULL |
| 6 | DudeMan | 300 | 1-3-2017 |
+-------------------------------------+
For a visual representation of the difference between SQL JOINs check out
https://www.codeproject.com/Articles/33052/Visual-Representation-of-SQL-Joins .

Join two table mysql query return all user list values?

Join two table MySQL query return all user list values.
Please correct this query or provide some query.
Table1 : users
+---------+------------+-----------+
| user_id | user_name | cource_id |
+---------+------------+-----------+
| 1 | ramalingam | 1,2,3,4 |
| 2 | yuvi | 1 |
| 3 | Saravanan | 1,2,3 |
| 4 | gandhi | 1 |
+---------+------------+-----------+
Table2 : course
+-----------+-------------+
| cource_id | cource_name |
+-----------+-------------+
| 1 | php |
| 2 | wordpress |
| 3 | seo |
| 4 | magento |
+-----------+-------------+
Output
--------------------------------------
user_id | user_name | cource_id
--------------------------------------
1 | ramalingam| php,wordpress,seo,magnto
2 | yuvi | php
3 | Saravanan | php,wordpress,seo
4 | gandhi | php
This my query
SELECT u.user_id,u.user_name, GROUP_CONCAT(c.cource_name)as course_name
FROM users as u
LEFT JOIN course as c ON c.cource_id = u.user_id
Thank you for any help I can get on this...
In general DB design is bad, don't use comma separated lists at all. Hovewer you should use FIND_IN_SET() in your JOIN clause in order to achieve this:
SELECT
u.user_id,
u.user_name,
GROUP_CONCAT(c.cource_name) AS course_name
FROM
users AS u
LEFT JOIN cource AS c ON FIND_IN_SET(c.cource_id, u.cource_id)
GROUP BY
u.user_id,
u.user_name
Output is:
+---------+------------+---------------------------+
| user_id | user_name | course_name |
+---------+------------+---------------------------+
| 1 | ramalingam | php,wordpress,seo,magento |
| 2 | yuvi | php |
| 3 | Saravanan | php,wordpress,seo |
| 4 | gandhi | php |
+---------+------------+---------------------------+
4 rows in set