Grouping, counting and excluding based on column value - mysql

Although I've not a complete newbie in SQL or MySQL I notice that there's still quite a bit to learn. I cannot get my head around this one, after much trying, reading and searching. If you can give any pointers, I'd be grateful.
I have simplified the actual data and tables to the following.
Two tables are relevant: Staff, and Work. They contain data on staff in various projects.
Staff:
ID Name Unit
1 Smith Chicago
2 Clarke London
3 Hess Chicago
Work:
StaffID ProjectID
1 10
2 10
3 10
1 20
2 30
3 40
1 50
3 50
Goal:
To get grouped all those projects where there are staff from Chicago, with the count of all staff in that project.
Expected result:
Project Staff count
10 3
20 1
40 1
50 2
So the project 30 is not listed because its member(s) are not from Chicago.
My query below is obviously wrong. It counts only those project members who are from Chicago, not the whole project staff.
SELECT
work.projectID as Project, COUNT(*) as "Staff count"
FROM
staff
JOIN
work ON staff.ID=work.staffID
WHERE
unit="Chicago"
GROUP BY
work.projectID;

I'd put the test for Chicago in a subselect.
Alternatively you can use a self-join, but I find the sub-select easier to understand.
SELECT
w.projectid as project
,COUNT(*) as `staff count`
FROM work w
INNER JOIN staff s ON (w.staffID = s.id)
WHERE w.projectID IN (
SELECT w2.projectID FROM work w2
INNER JOIN staff s2 ON (w2.staffID = s2.id AND s2.unit = 'Chicago'))
GROUP BY w.projectid

Remove the where clause and add a having clause which checks that at least one member of staff is from Chicago.
SELECT
work.projectID as Project, COUNT(*) as "Staff count"
FROM
staff
JOIN
work ON staff.ID=work.staffID
GROUP BY
work.projectID
HAVING
count(case unit when 'Chicago' then 1 end) > 0;

Finally: the result. Thanks again both #Johan and #a'r for your help, and #Johan for getting me on the right track (in my case).
I changed the sub-select to a derived table, and inner-joined this with the Work table on projectID.
SELECT
w.projectID AS project
,COUNT(*) AS `staff count`
FROM work w
INNER JOIN
(SELECT DISTINCT w2.projectID
FROM work w2
INNER JOIN staff s ON (w2.staffID = s.id AND s.unit = 'Chicago')) c
ON (w.projectID = c.projectID)
GROUP BY w.projectID

Related

Eliminate certain duplicated rows after group by

With this db:
Chef(cid,cname,age),
Recipe(rid,rname),
Cooked(orderid,cid,rid,price)
Customers(cuid,orderid,time,daytime,age)
[cid means chef id, and so on]
Given orders from customers, I need to find for each chef, the difference between his age and the average of people who ordered his/her meals.
I wrote the following query:
select cid, Ch.age - AVG(Cu.age) as Diff
from Chef Ch NATURAL JOIN Cooked Co,Customers Cu
where Co.orderid = Cu.orderid
group by cid
This solves the problem, but if you assume that customers has their unique id, it might not work,because then one can order two meals of the same chef and affect the calculation.
Now I know it can be answered with NOT EXISTS but I'm looking for a soultion which includes the group by function (something similar to what I wrote). So far I couldn't find (I searched and tried many ways, from select distinct , to manipulation in the where clause ,to "having count(distinct..)" )
Edit: People asked for an exmaple. i'm coding using SQLFiddle and it crashes alot, so I'll try my best:
cid | cuid | orderid | Cu.age
-----------------------------
1 333 1 20
1 200 2 41
1 200 5 41
2 4 3 36
Let's say Chef 1's age is 50 . My query will give you 50 - (20+40+40/3) = 16 and 2/3. althought it should actually be 50 - (20+40/2) = 20. (because the guy with id 200 ordered two recipes of our beloved Chef 1.).
Assume Chef 2's age is 47. My query will result:
cid | Diff
----------
1 16.667
2 11
Another edit: I wasn't taught any particular sql-query form.So I really have no idea what are the differences between Oracle's to MySql's to Microsoft Server's, so I'm basically "freestyle" querying.(I hope it will be good in my exam as well :O )
First, you should write your query as:
select cid, Ch.age - AVG(Cu.age) as Diff
from Chef Ch join
Cooked Co
on ch.cid = co.cid join
Customers Cu
on Co.orderid = Cu.orderid
group by cid;
Two different reasons:
NATURAL JOIN is just a bug waiting to happen. List the columns that you want used for the join, lest an unexpected field or spelling difference affect the results.
Never use commas in the FROM clause. Always use explicit JOIN syntax.
Next, the answer to your question is more complicated. For each chef, we can get the average age of the customers by doing:
select cid, avg(age)
from (select distinct co.cid, cu.cuid, cu.age
from Cooked Co join
Customers Cu
on Co.orderid = Cu.orderid
) c
group by cid;
Then, for the difference, you need to bring that information in as well. One method is in the subquery:
select cid, ( age - avg(cuage) ) as diff
from (select distinct co.cid, cu.cuid, cu.age as cuage, c.age as cage
from Chef c join
Cooked Co
on ch.cid = co.cid join
Customers Cu
on Co.orderid = Cu.orderid
) c
group by cid, cage;

Mysql (conditional?) query from two tables

Not sure if I have phrased the title properly, but here it goes. I have these two tables:
table:staff
id Name groupId Status
1 John Smith 1 1
2 John Doe 1 1
3 Jane Smith 2 1
4 Jerry Smith 1 1
table:jobqueue
id job_id staff_id jobStatus
1 1 1 1
2 2 1 1
3 5 2 1
4 7 3 0
Now, what I need to do is to find the staff with the least amount of job assigned to him which I am able to do by querying the jobqueue table.
SELECT min(cstaff),tmp.staff_id FROM (SELECT t.staff_id, count(staff_id) cstaff from jobqueue t join staff s on t.staff_id=s.id join group g on s.groupId=g.id where g.id=26 GROUP BY t.id ) tmp
This works fine, but the problem is if a staff is not assigned to any job at all, this query wont get them, because it only queries the jobqueue table, where that particular staff won't have any entry. I need to modify the query to include the staff table and if a staff is not assigned any job in the jobqueue then I need to get the staff details from the staff table. Basically, I need to find staff for a group who are not assigned any job and if all staffs are assigned job then find staff with the least amount of jobs assigned. Could use some help with this. Also, tagging as Yii as I would like to know if this is doable with Yii active-records. But I am okay with a plain sql query that will work with Yii sql commands.
not sure that it is optimal query, but it works:
select d.groupId, d.name, (select count(*) from jobqueue as e where e.staff_id=d.id) as jobassigned
from staff as d
where d.id in (
select
(
select a.id
from staff as a
left outer join
jobqueue as b
on (a.id = b.staff_id)
where a.groupId = c.groupId
group by a.id
order by count(distinct job_id) asc
limit 1
) as notassigneduserid
from (
select distinct groupId from staff
) as c)
maybe need some comments:
c query is needed to get all distinct groupId - if you have separate table for this, you can replace it
notassigneduserid statement for each groupId select user with minimal job count
d query is needed to fetch actual user names, groupId for all found "unassigned users" and present it
here is the results for data from question:
Group Staff Jobs assigned
1 Jerry Smith 0
2 Jane Smith 1
with
counts as (
select s.groupId
, s.id
, (select count(*) from jobqueue where staff_id = s.id) count
from staff s
group by s.id, s.groupId),
groups as (
select groupId, min(count) mincount
from counts
group by groupId)
select c.groupId, c.id, c.count
from counts c
join groups g on c.groupId = g.groupId
where c.count = g.mincount
This SQL will give you all the staff with the minimum number of jobs in each group. It might be that more than one staff has the same minimum number of jobs. The approach is to use common table expressions to build first a list of counts, and then to retrieve the minimum count for each group. Finally I join the counts and groups tables and retrieve the staff that have the minimum count for each group.
I tested this on SQL Server, but the syntax should work for MySQL as well. To your data I added:
id Name groupId Status
5 Bubba Jones 2 1
6 Bubba Smith 1 1
and
id job_id staff_id jobStatus
5 4 5 1
Results are
group name count
1 Bubba Smith 0
1 Jerry Smith 0
2 Bubba Jones 1
2 Jane Smith 1
BTW, I would not try to do this with active record, it is far too complex.
As Ilya Bursov said this answer wasn't respond exactly what was asked. So here is a more optimized solution:
SELECT *
FROM (
SELECT s.id as id_staff, s.Name, s.groupId, count(distinct t.id) as jobsXstaff
FROM staff s
LEFT JOIN jobqueue t ON s.id=t.staff_id
GROUP BY s.id, s.groupId
ORDER BY s.groupId, jobsXstaff
) tmp
GROUP BY groupId
Old answer below.
This works but without table group which I don't create. You can simply join table groups as you did:
SELECT min(cstaff),tmp.id
FROM (
SELECT s.id, count( staff_id ) cstaff
FROM jobqueue t
RIGHT JOIN staff s ON t.staff_id = s.id
GROUP BY t.id
) tmp
As you see you need to get all values from table staff (right join) and select the id staff from it's own table (s.id instead of t.staff_id). Also you have to get tmp.id instead of staff_id now.

mysql left join duplicates

ive been searching for hours but cant find a solution. its a bit complicated so i'll break it down into a very simple example
i have two tables; people and cars
people:
name_id firstname
1 john
2 tony
3 peter
4 henry
cars:
name_id car_name
1 vw gulf
1 ferrari
2 mustang
4 toyota
as can be seen, they are linked by name_id, and john has 2 cars, tony has 1, peter has 0 and henry has 1.
i simply want to do a single mysql search for who has a (1 or more) car. so the anwser should be john, tony, henry.
the people table is the master table, and im using LEFT JOIN to add the cars. my problem arises from the duplicates. the fact that the table im joining has 2 entries for 1 id in the master.
im playing around with DISTINCT and GROUP BY but i cant seem to get it to work.
any help is much appreciated.
EDIT: adding the query:
$query = "
SELECT profiles.*, invoices.paid, COUNT(*) as num
FROM profiles
LEFT JOIN invoices ON (profiles.id=invoices.profileid)
WHERE (profiles.id LIKE '%$id%')
GROUP BY invoices.profileid
";
try this
select distinct p.name_id, firstname
from people p, cars c
where p.name_id = c.name_id
or use joins
select distinct p.name_id, firstname
from people p
inner join cars c
on p.name_id = c.name_id
If you only want to show people that have a car, then you should use a RIGHT JOIN. This will stop any results from the left table (people) to be returned if they didn't have a match in the cars table.
Group by the persons name to remove duplicates.
SELECT firstname
FROM people P
RIGHT JOIN cars C ON C.name_id = P.name_id
GROUP BY firstname
SELECT DISTINCT firstname
FROM people
JOIN cars ON cars.name_id = people.name_id;
If this doesn't work you might have to show us the full problem.
The way to propose it there's no need for a left join since you need at least a car per person. Left join is implicitely an OUTER join and is intended to return the results with 0 corresponding records in the joinned table.

combine two tables in sql

By using Sql server 2008 I can return two table results like
College Department Course Section Passed
X CS B.Sc A 30
X CS B.Sc B 12
and the second result
College Department Course Section Failed
X CS B.Sc A 23
X CS B.Sc B 42
here, am joining one extra table than the first one, if I use the same query to get both the passed and failed count, the count goes wrong, thats what am using two different queries.
Am trying to combine these two tables, to get the result like
College Department Course Section Passed Failed
X CS B.Sc A 30 23
X CS B.Sc B 12 42
but I dunno how to do this, can anyone help me out here, thanks in advance.
Note : here am joining about 3 to 5 tables in both the queries.
select a.*b.failed
from ([first result query]) a
INNER JOIN ([second result query]) b on a.college=b.college and a.Department=b.Department and a.Course=b.course and a.Section=b.Section
Use case Statement
select College, Department , Course , Section ,
sum(case when <pass condition> then 1 else 0) as Passed ,
sum(case when <fail condition> then 1 else 0) as Failed
from <table1>
join <table2>
on (condition)
group by College, Department , Course , Section
You can use inner join for your required output.
For Example,
Select emp.name,emp.salary,Q.qualification from employee emp inner join qualification Q.empid=emp.empid
For detail refer
https://www.w3schools.com/sql/sql_join_inner.asp/
https://avtartime.com/sql-inner-join-with-examples/

Select query with MySQL database where distinct member id with least registration time are to be selected... How can I do it?

I am trying to work on a query where my database schema looks like:
MEMBER TABLE
member_id----------member_name--------member_nickname
1 prashant prash
2 christopher chris
3 nick nick
4 harvey harvey
----------------------------------------------------------
CHECKIN TABLE
checkin_id------------------member_id------------------reg_on
1 1 1287713712
2 1 1287723999
3 2 1287733712
4 1 1287735912
5 3 1287803712
6 2 1287833712
Now when I select distinct users, I want to join both tables to see the following result:
member_id----------------member_name---------------reg_on
1------------------------------prashant -----------------1287713712
2------------------------------christopher---------------1287733712
3------------------------------nick------------------------1287803712
Please Help!!!
Edit Don't know what I was thinking by overcomplicating my previous answer...
This is a simple JOIN with an aggregate:
SELECT
m.member_id,
m.member_name,
MIN(c.reg_on) AS reg_on
FROM
MEMBER m JOIN CHECKIN c ON m.member_id = c.member_id
GROUP BY m.member_id, m.member_name
If you select and group by the checkin table, that will give you the data you want, then join to the member table to fill out the rest of the query.
SELECT c.member_id, m.member_name, MIN(c.reg_on) AS reg_on
FROM checkin c
JOIN member m ON m.member_id = c.member_id
GROUP BY c.reg_on
ORDER BY c.member_id;
No subselect necessary.