MySQL how to get the max through inner join - mysql

For a swimming school, parents pay tuition fees for their kids. On a monthly basis, I need to collect the payable fees from a table that holds open balances, but only if their child is marked as 'active'. Here is the catch: an open balance for an "inactive child" is also considered payable IF at least one other child in the same family is marked active.
I have the following MySQL query:
SELECT sum(b.amount) as amount, f.name, u.email
FROM balance b
LEFT JOIN student s ON b.user_id = s.user_id
LEFT JOIN student_family sf ON s.user_id = sf.user_id
LEFT JOIN family f ON sf.fam_id = f.fam_id
LEFT JOIN user u ON u.user_id = s.user_id
WHERE b.status = 'open' AND s.active = 1
GROUP BY u.email
I currently check s.active=1 but this part needs to be adjusted in such as way that we check if there is at least one student in the same family that have active=1.
My attempt (that didn't work):
...
WHERE b.status = 'open' AND ( SELECT max(active) from student s2 LEFT JOIN student_family sf2 ON sf2.user_id = s2.user_id WHERE sf2.fam_id = sf.fam_id ) = 1
How to accomplish this query?

Using the exists clause will resolve the issue:
SELECT sum(b.amount) as amount, f.name, u.email
FROM balance b
LEFT JOIN student s ON b.user_id = s.user_id
LEFT JOIN student_family sf ON s.user_id = sf.user_id
LEFT JOIN family f ON sf.fam_id = f.fam_id
LEFT JOIN user u ON u.user_id = s.user_id
WHERE b.status = 'open' AND exists
(select 1 from student s1
JOIN student_family sf1 ON s1.user_id = sf1.user_id
JOIN family f ON sf1.fam_id = f1.fam_id
where s1.active = 1 and f.fam_id=f1.fam_id)
GROUP BY u.email

You could use a IN clause in your WHERE to see if the student_family.user_id is in a family with at least one active student:
Something like:
SELECT
sum(b.amount) as amount,
f.name,
u.email
FROM
balance b
LEFT JOIN student s ON b.user_id = s.user_id
LEFT JOIN student_family sf ON s.user_id = sf.user_id
LEFT JOIN family f ON sf.fam_id = f.fam_id
LEFT JOIN user u ON u.user_id = s.user_id
WHERE
b.status = 'open'
AND (sf.user_id IN (SELECT sf.user_id FROM student_family sf INNER JOIN student s ON sf.user_id = s.user_id WHERE s.active = 1 GROUP BY sf.user_id ))
GROUP BY
u.email

Related

Select multipl query and every query got 2 JOIN

I want combine multiple query become 1:
SELECT b.fname FROM almanak as a
JOIN users as b on (b.userid = a.staf1)
SELECT c.fname FROM almanak as a
JOIN users as c on (c.userid = a.staf2)
SELECT d.fname FROM almanak as a
JOIN users as d on (d.userid = a.staf3)
SELECT e.fname FROM almanak as a
JOIN users as e on (e.userid = a.staf4)
SELECT f.fname FROM almanak as a
JOIN users as f on (f.userid = a.staf5)
i want make every field userid will select fname field at another table
Here is one way to do it:
SELECT
b.fname fname1,
c.fname fname2,
d.fname fname3,
e.fname fname4,
f.fname fname5
FROM almanak as a
INNER JOIN users as b ON b.userid = a.staf1
INNER JOIN users as c ON c.userid = a.staf2
INNER JOIN users as d ON d.userid = a.staf3
INNER JOIN users as e ON e.userid = a.staf4
INNER JOIN users as f ON f.userid = a.staf5
If there might be missing relations, you can turn the INNER JOINs to LEFT JOINs.
Alternatively, you can do conditional aggregation. Assuming that the primary key of the almanak table is id, that would look like:
SELECT
MAX(CASE WHEN u.userid = a.staf1 THEN u.fname END) fname1,
MAX(CASE WHEN u.userid = a.staf2 THEN u.fname END) fname2,
MAX(CASE WHEN u.userid = a.staf3 THEN u.fname END) fname3,
MAX(CASE WHEN u.userid = a.staf4 THEN u.fname END) fname4,
MAX(CASE WHEN u.userid = a.staf5 THEN u.fname END) fname5
FROM almanak as a
INNER JOIN users as u
ON u.userid IN (a.staf1, a.staf2, a.staf3, a.staf4, a.staf5)
GROUP BY a.id

Complex query containing multiple joins is not executing right outer join

I'm writing a query with multiple joins where I want every user to show entries against all category Types. When I execute the query below only 1 record is returned because the employee u.employee_id = "0079-P" has only worked on 1 project but I want to get data for all the category_types with users workhours displayed as null for the categories he didn't work on.
Select u.employee_id As Employee_ID, u.user_name As UserName, COALESCE(primaryDept.ctd_name, primaryProj.ctd_name) As PrimaryDeptOrProj, region.region_name As Region, categoryType.ctd_id, categoryType.ctd_name, SUM(tsdd.workhours)
From users u
LEFT JOIN category_type_details primaryDept ON u.user_primary_department = primaryDept.ctd_id
LEFT JOIN category_type_details primaryProj ON u.user_primary_project = primaryProj.ctd_id
LEFT JOIN regions region ON u.region_id = region.region_id
LEFT JOIN timesheets ts ON u.user_id = ts.timesheet_user
INNER JOIN timesheet_mr tsmr ON ts.timesheet_caller = tsmr.tsmr_id
INNER JOIN timesheet_details tsd ON ts.timesheet_id = tsd.tsd_timesheet_id
INNER JOIN timesheet_day_details tsdd ON tsd.tsd_id = tsdd.tsd_id
RIGHT OUTER JOIN category_type_details categoryType ON tsd.tsd_category_type_id = categoryType.ctd_id
WHERE tsmr.tsmr_id = 14 and u.employee_id = "0079-P"
GROUP BY u.user_id, tsd.tsd_category_type_id;
I tried this query with variations and it returns 1 record in any case.
You could change your query to this:
SELECT u.employee_id AS Employee_ID, u.user_name AS UserName,
COALESCE(pd.ctd_name, pp.ctd_name) AS PrimaryDeptOrProj,
r.region_name AS Region, ct.ctd_id, ct.ctd_name, SUM(tsdd.workhours)
FROM users u
LEFT JOIN category_type_details pd ON u.user_primary_department = pd.ctd_id
LEFT JOIN category_type_details pp ON u.user_primary_project = pp.ctd_id
LEFT JOIN regions r ON u.region_id = r.region_id
LEFT JOIN timesheets ts ON u.user_id = ts.timesheet_user
INNER JOIN timesheet_mr tsmr ON ts.timesheet_caller = tsmr.tsmr_id AND tsmr.tsmr_id = 14
INNER JOIN timesheet_details tsd ON ts.timesheet_id = tsd.tsd_timesheet_id
INNER JOIN timesheet_day_details tsdd ON tsd.tsd_id = tsdd.tsd_id
RIGHT OUTER JOIN category_type_details ct ON tsd.tsd_category_type_id = ct.ctd_id AND u.employee_id = "0079-P"
GROUP BY ct.ctd_id, u.user_id, u.employee_id, u.user_name,
COALESCE(pd.ctd_name, pp.ctd_name), r.region_name, ct.ctd_name
ORDER BY ct.ctd_id, u.user_id, u.employee_id, u.user_name,
COALESCE(pd.ctd_name, pp.ctd_name), r.region_name, ct.ctd_name;
You only got 1 row because the condition in WHERE clause filter all the NULL user_id rows for other category.
For MySQL, you could omit other columns in GROUP BY clause:
SELECT u.employee_id AS Employee_ID, u.user_name AS UserName,
COALESCE(pd.ctd_name, pp.ctd_name) AS PrimaryDeptOrProj,
r.region_name AS Region, ct.ctd_id, ct.ctd_name, SUM(tsdd.workhours)
FROM users u
LEFT JOIN category_type_details pd ON u.user_primary_department = pd.ctd_id
LEFT JOIN category_type_details pp ON u.user_primary_project = pp.ctd_id
LEFT JOIN regions r ON u.region_id = r.region_id
LEFT JOIN timesheets ts ON u.user_id = ts.timesheet_user
INNER JOIN timesheet_mr tsmr ON ts.timesheet_caller = tsmr.tsmr_id AND tsmr.tsmr_id = 14
INNER JOIN timesheet_details tsd ON ts.timesheet_id = tsd.tsd_timesheet_id
INNER JOIN timesheet_day_details tsdd ON tsd.tsd_id = tsdd.tsd_id
RIGHT OUTER JOIN category_type_details ct ON tsd.tsd_category_type_id = ct.ctd_id AND u.employee_id = "0079-P"
GROUP BY ct.ctd_id, u.user_id
ORDER BY ct.ctd_id, u.user_id;

Get only those rows where column a has as many duplicate entries, as many there is distinctive values in column b

My current query:
select users.id as user_id, opportunities.id as op_id, opportunities.title, certificates.id as cert_id from opportunities
join opportunity_certificates on opportunities.id=opportunity_certificates.opportunity_id
join certificates on opportunity_certificates.certificate_id=certificates.id
join user_certificates on certificates.id=user_certificates.certificate_id
join users on user_certificates.user_id=users.id
where opportunity_certificates.is_required = 1 and
opportunities.id = 1
This produces the table on the picture below.
cert_id column can have values from 1 to 7, depends on the opportunities.id. In the table below, I want the query to return only the rows which have the same user_id but different cert_id, 1 and 2.
If the table had 3 different cert_id, I would want it to return only the rows which have same user_id but different cert_id, 1,2 and 3.
when the cert_id has only one value, query should return all the records with that one value in cert_id. Basically, it should show all users who have all required certificates.
The query has to be in the current format. I experimented with
group by users.id
having count(*) >
but I don't know how to make that comparison dynamic, relative to the count of distinctive values in the cert_id column.
Compare counts with a having condition.
select u.id as user_id --, o.id as op_id, o.title
from opportunities o
join opportunity_certificates oc on o.id=oc.opportunity_id
join certificates c on oc.certificate_id=c.id
join user_certificates uc on c.id=uc.certificate_id
join users u on uc.user_id=u.id
where oc.is_required = 1 and o.id = 1
group by u.id --,o.id,o.title
having count(distinct c.id)=(select count(distinct id) from certificates)
Useful?
with data as (
select users.id as user_id, o.title, c.id as cert_id
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
)
select user_id, min(title) as title, max(cert_id) as num_certs
from data
group by user_id
having count(cert_id) = (select max(cert_id) from data);
I'm assuming that cert_id values start and 1 and run sequentially. You could also use count(distinct ...) in the having clause but it guess it's debatable which ones expresses you intent more clearly.
If your version of MySQL doesn't support CTEs then you should be able to just drop that whole subquery into the having clause as well.
select u.id as user_id, min(o.title) as title, max(c.cert_id) as num_certs
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
group by u.id
having count(c.cert_id) = (
select max(c.cert_id)
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
);
Here's another one that might work if you have window functions available. (It might work with Laravel better?):
select *
from (
select users.id as user_id, o.title,
count(distinct c.id) over (partition by u.id) as user_certs,
max(c.id) over () as total_certs
from opportunities o
inner join opportunity_certificates oc on oc.opportunity_id = o.id
inner join certificates c on c.id = oc.certificate_id
inner join user_certificates uc on uc.certificate_id = c.id
inner join users u on u.id = uc.user_id
where oc.is_required = 1 and o.id = 1
) t
where user_certs = total_certs;

Getting result as percentage when using foreign keys

I currently have a DB with 4 table:
Customer
Sale (CustID & RoomID fk)
Room (ManID fk)
Manager
I am trying to get the cities in which the customers reside in as a percentage. However, I only want to show the the customers who made a purchase using a said manager. Currently this comes through as an amount either under or over 100% total.
When this query is isolated to the customer table, the result adds up to 100% correctly, however it does need to be taken from sales using the manager username as a parameter. This is what I currently have:
SELECT
(COUNT(c.city) / (SELECT Count(CustID) FROM Customer) * 100) AS percent, c.city AS City
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'manager123'
GROUP BY c.City;
EDIT:
I wish to add that the CustID may occur more than once within the sales table, additionally, it may be null. Not sure if is required to use distinct when counting.
Having isolated my issue to this, I felt it necessary to mention as I am still unable to exclude these from the result and an incomplete figure (totaling <100% still showing)
Here you go.
SELECT NoCity,COUNT(*)/percent *100 as percentOfCustomerBasedonManager,City
FROM (
SELECT m.UserName,COUNT(c.city) AS NoCity, COUNT(c.CustID) AS percent,c.city AS City FROM
sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
GROUP BY c.City ) cityTable WHERE cityTable.UserName = 'manager123'
This may help:
SELECT city, ((city_count)/(customer_count)*100) as Percent
(
SELECT city, COUNT(*) as city_count, customer_count
FROM
(
SELECT distinct c.city
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'manager123'
GROUP BY c.city
) as t1
LEFT JOIN
(
SELECT city as city2, COUNT(*) as customer_count
FROM
(
SELECT c.city, distinct c.CustID
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'manager123'
GROUP BY c.city
) as t2
) as t3 on t1.city=t3.city2
) as master
GROUP BY city
Ok, here is the solution I came to, with a huge thanks to Lim Neo who pointed me in the right direction.
SELECT
round(COUNT(c.city) /
(
SELECT Count(b.CustID)
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'Manager123'
) * 100)
AS percent, c.city AS City
FROM sale s
INNER JOIN customer c ON s.CustID = c.CustID
INNER JOIN room r ON s.RoomID = r.RoomID
INNER JOIN manager m ON r.ManID = m.ManID
WHERE m.UserName = 'Manager123'
GROUP BY c.City;

mysql query - count words in common between users

I've a very nice query that selects friends of the current user. user_id = 2 in the example. His friend is user_id = 4.
I want the same query to fetch the number of words user_id 2 has with selected friends. In this case they have word = love, and this is also word they both have, so I want in_common row to say = 1.
Is it possible without changing too much current query?
Should I start from scratch?
SQL FIDDLE
Assuming that both users would have an entry for 'love' in the words_en table then something like this maybe:-
SELECT b.name_surname,
b.avatar,
b.friend_words,
(b.friend_msg_id) AS friend_msg_id,
words_common.words_in_common,
COUNT(m.id) AS unread_msg
FROM
(
SELECT a.name_surname as name_surname,
a.avatar as avatar,
GROUP_CONCAT(DISTINCT w.word ORDER BY w.word ASC) AS friend_words,
(a.friend_id) AS friend_msg_id
FROM
(
SELECT f1.asked_user_id AS friend_id,
f1.created,
u.name_surname,
u.avatar
FROM friends AS f1
INNER JOIN friends AS f2
ON f1.asked_user_id = f2.asker_user_id
AND f1.asker_user_id = f2.asked_user_id
INNER JOIN users AS u ON f1.asked_user_id = u.id
WHERE f1.status = 1 AND f2.status = 1
AND f1.asker_user_id = 2
) a
LEFT JOIN connections c ON c.user_id = a.friend_id
AND c.invisible <> 1 AND c.deleted <> 1
LEFT JOIN words_en w ON c.word_id = w.id
GROUP BY 1
) b
LEFT JOIN messages m ON m.to_user_id = 2
AND m.from_user_id = b.friend_msg_id
AND m.seen = 0
LEFT OUTER JOIN
(
SELECT b.user_id AS friend_id, GROUP_CONCAT(a.word) AS words_in_common
FROM words_en a
INNER JOIN words_en b
ON a.word = b.word
WHERE a.user_id = 2
GROUP BY b.user_id
) words_common
ON b.friend_msg_id = words_common.friend_id
GROUP BY b.name_surname, b.avatar, b.friend_words, b.friend_msg_id
ORDER BY unread_msg DESC
EDIT - modification to use connections table to find common words:-
SELECT b.name_surname,
b.avatar,
b.friend_words,
(b.friend_msg_id) AS friend_msg_id,
words_common.words_in_common,
COUNT(m.id) AS unread_msg
FROM
(
SELECT a.name_surname as name_surname,
a.avatar as avatar,
GROUP_CONCAT(DISTINCT w.word ORDER BY w.word ASC) AS friend_words,
(a.friend_id) AS friend_msg_id
FROM
(
SELECT f1.asked_user_id AS friend_id,
f1.created,
u.name_surname,
u.avatar
FROM friends AS f1
INNER JOIN friends AS f2
ON f1.asked_user_id = f2.asker_user_id
AND f1.asker_user_id = f2.asked_user_id
INNER JOIN users AS u ON f1.asked_user_id = u.id
WHERE f1.status = 1 AND f2.status = 1
AND f1.asker_user_id = 2
) a
LEFT JOIN connections c ON c.user_id = a.friend_id
AND c.invisible <> 1 AND c.deleted <> 1
LEFT JOIN words_en w ON c.word_id = w.id
GROUP BY 1
) b
LEFT JOIN messages m ON m.to_user_id = 2
AND m.from_user_id = b.friend_msg_id
AND m.seen = 0
LEFT OUTER JOIN
(
SELECT b.user_id AS friend_id, GROUP_CONCAT(c.word) AS words_in_common
FROM connections a
INNER JOIN connections b
ON a.word_id = b.word_id
INNER JOIN words_en c
ON b.word_id = c.id
WHERE a.user_id = 2
GROUP BY b.user_id
) words_common
ON b.friend_msg_id = words_common.friend_id
GROUP BY b.name_surname, b.avatar, b.friend_words, b.friend_msg_id
ORDER BY unread_msg DESC