My query is missing a row that has a zero - mysql

I searched for an answer to this but didn't find one for this specific problem. I am making a database for storing classes created by a teacher, and a lookup table for students enrolled in each class. I want to run a query that shows each teacher's class and the number of students. As you can see in the data below, the teacher has two classes with zero students, but only one of them is returning in the query.
The query:
SELECT `c`.`ClassID` AS `id`,
`c`.`Class_Name` AS `name`,
`c`.`Class_Code` AS `code`,
COUNT(`e`.`EnrollID`) AS `count`
FROM `CMP_Classes` `c`
LEFT OUTER JOIN `CMP_Student_Enrollment` `e`
ON `c`.`ClassID` = `e`.`Enroll_ClassID`
AND `Class_Teacher` = 1
GROUP BY `e`.`EnrollID`
The data:
CMP_Classes Table:
ClassID | Class_Name | Class_Code | Class_Teacher
1 | Mr. Jones' 1st Period Class | QYTNPCGK | 1
2 | Mr. Jones' 2nd Period Class | HZWNDZPM | 1
3 | Pizza | RRCXQNNE | 9
4 | Mr. Jones' 3rd Period Class | NFLBXFEQ | 1
CMP_Student_Enrollment Table:
EnrollID | Enroll_Student | Enroll_ClassID
3 | 2 | 1
Query results:
id | name | code | count
2 | Mr. Jones' 2nd Period Class | HZWNDZPM | 0
1 | Mr. Jones' 1st Period Class | QYTNPCGK | 1
So, as you can see, there is no row for "Mr. Jones' 3rd Period Class" but there should be.

You need to group by the unaggregated columns in the select:
SELECT c.ClassID AS id, c.Class_Name AS name, c.Class_Code AS code,
COUNT(e.EnrollID) AS `count`
FROM CMP_Classes c LEFT OUTER JOIN
CMP_Student_Enrollment e
ON c.ClassID = e.Enroll_ClassID
WHERE c.Class_Teacher = 1
GROUP BY c.ClassID, c.Class_Name, c.Class_Code;
All the backticks in your query make it harder to read and write.

Related

SQL insert query with some condition

I am using MySQL.
There are three tables presented: Patient, Occupies, Room and Privte_Patient. I need to identify the first available room and allocate the room to a newly admitted patient with PIN '314' (the patient is already added to a database). Note that the room should be either single or multiple occupancy depending whether the patient is private or not.
As a result, I want get the SQL query which will allocate the patient with PIN '314' to the room number 1. Because this patient is not a private patient and room number 1 is the first room with empty bed (size is 2 beds).
Any idea of how to identify this room? Can I do it using Conditional INSERT?
Table Patient
+-------+---------+
| PIN | name |
+-------+---------+
|314 | Lana |
|778899 | Michael |
|345566 | Jone |
+-------+---------+
Table Occupies
+--------+--------+
|patient | room |
+--------+--------+
|778899 | 1 |
|345566 | 4 |
+-------+---------+
Table Room
+--------+--------+
|number | size |
+--------+--------+
| 1 | 2 |
| 2 | 12 |
| 3 | 1 |
| 4 | 1 |
+-------+---------+
Private_Patient
+--------+--------+
|patient |consultant|
+--------+--------+
|345566 | 345566 |
+-------+---------+
Consider:
insert into occupies(patient, room)
select 314, r.number
from room r
left join (select room, count(*) size from occupies group by room) o
on o.room = r.number
where coalesce(o.size, 0) < r.size
order by r.number
limit 1
For each room, the query brings the number of occupants and uses that information to filter out rooms that are full already. The first room where at least in bed is available is selected.
It might be simpler to understand with a correlated subquery:
insert into occupies(patient, room)
select 314, r.number
from room r
where r.size > (
select count(*)
from occupies o
where o.room = r.number
)
order by r.number
limit 1
INSERT INTO Occupies(room, patient)
SELECT number, '314'
FROM Room r
INNER JOIN
(SELECT room, COUNT(patient) amountOfPatientsInRoom from Occupies GROUP BY
room) ON
o.room = r.number
WHERE r.size < o.amountOfPatientsInRoom
LIMIT 1

MySQL GROUP_CONCAT with SUM() and multiple JOINs inside subquery

I'm very average with MySQL, but usually I can write all the needed queries after reading documentation and searching for examples. Now, I'm in the situation where I spent 3 days re-searching and re-writing queries, but I can't get it to work the exact way I need. Here's the deal:
1st table (mpt_companies) contains companies:
| company_id | company_title |
------------------------------
| 1 | Company A |
| 2 | Company B |
2nd table (mpt_payment_methods) contains payment methods:
| payment_method_id | payment_method_title |
--------------------------------------------
| 1 | Cash |
| 2 | PayPal |
| 3 | Wire |
3rd table (mpt_payments) contains payments for each company:
| payment_id | company_id | payment_method_id | payment_amount |
----------------------------------------------------------------
| 1 | 1 | 1 | 10.00 |
| 2 | 2 | 3 | 15.00 |
| 3 | 1 | 1 | 20.00 |
| 4 | 1 | 2 | 10.00 |
I need to list each company along with many stats. One of stats is the sum of payments in each payment method. In other words, the result should be:
| company_id | company_title | payment_data |
--------------------------------------------------------
| 1 | Company A | Cash:30.00,PayPal:10.00 |
| 2 | Company B | Wire:15.00 |
Obviously, I need to:
Select all the companies;
Join payments for each company;
Join payment methods for each payment;
Calculate sum of payments in each method;
GROUP_CONCAT payment methods and sums;
Unfortunately, SUM() doesn't work with GROUP_CONCAT. Some solutions I found on this site suggest using CONCAT, but that doesn't produce the list I need. Other solutions suggest using CAST(), but maybe I do something wrong because it doesn't work too. This is the closest query I wrote, which returns each company, and unique list of payment methods used by each company, but doesn't return the sum of payments:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
Then I tried:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title), ':', CAST(SUM(mpt_payments.payment_amount) AS CHAR))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
...and many other variations, but all of them either returned query errors, either didn't return/format data I need.
The closest answer I could find was MySQL one to many relationship: GROUP_CONCAT or JOIN or both? but after spending 2 hours re-writing the provided query to work with my data, I couldn't do it.
Could anyone give me a suggestion, please?
You can do that by aggregating twice. First for the sum of payments per method and company and then to concatenate the sums for each company.
SELECT x.company_id,
x.company_title,
group_concat(payment_amount_and_method) payment_data
FROM (SELECT c.company_id,
c.company_title,
concat(pm.payment_method_title, ':', sum(p.payment_amount)) payment_amount_and_method
FROM mpt_companies c
INNER JOIN mpt_payments p
ON p.company_id = c.company_id
INNER JOIN mpt_payment_methods pm
ON pm.payment_method_id = p.payment_method_id
GROUP BY c.company_id,
c.company_title,
pm.payment_method_id,
pm.payment_method_title) x
GROUP BY x.company_id,
x.company_title;
db<>fiddle
Here you go
SELECT company_id,
company_title,
GROUP_CONCAT(
CONCAT(payment_method_title, ':', payment_amount)
) AS payment_data
FROM (
SELECT c.company_id, c.company_title, pm.payment_method_id, pm.payment_method_title, SUM(p.payment_amount) AS payment_amount
FROM mpt_payments p
JOIN mpt_companies c ON p.company_id = c.company_id
JOIN mpt_payment_methods pm ON pm.payment_method_id = p.payment_method_id
GROUP BY p.company_id, p.payment_method_id
) distinct_company_payments
GROUP BY distinct_company_payments.company_id
;

Mysql IN function

class_table
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1,2,3,4 |
+----+-------+--------------+
student_mark
+----+----------+--------+
| id |student_id| marks |
+----+----------+--------+
| 1 | 1 | 12 |
+----+----------+--------+
| 2 | 2 | 80 |
+----+----------+--------+
| 3 | 3 | 20 |
+----+----------+--------+
I have these two tables and i want to calculate the total marks of student and my sql is:
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN
(SELECT `student_id` FROM `class_table` WHERE `teac_id` = '1')
But this will return null, please help!!
DB fiddle
Firstly, you should never store comma separated data in your column. You should really normalize your data. So basically, you could have a many-to-many table mapping teacher_to_student, which will have teac_id and student_id columns.
In this particular case, you can utilize Find_in_set() function.
From your current query, it seems that you are trying to getting total marks for a teacher (summing up marks of all his/her students).
Try:
SELECT SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
In case, you want to get total marks per student, you would need to add a Group By. The query would look like:
SELECT sm.`student_id`,
SUM(sm.`marks`)
FROM `student_mark` AS sm
JOIN `class_table` AS ct
ON FIND_IN_SET(sm.`student_id`, ct.`student_id`) > 0
WHERE ct.`teac_id` = '1'
GROUP BY sm.`student_id`
Just in case you want to know why, The reason it returned null is because the subquery returned as '1,2,3,4' as a whole. What you need is to make it returned 1,2,3,4 separately.
What your query returned
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN ('1,2,3,4')
What you expect is
SELECT SUM(`marks`)
FROM `student_mark`
WHERE `student_id` IN (1,2,3,4)
The best way is it normalize as #madhur said. In your case you need to make the teacher and student as one to many link
+----+-------+--------------+
| id |teac_id| student_id |
+----+-------+--------------+
| 1 | 1 | 1 |
+----+-------+--------------+
| 2 | 1 | 2 |
+----+-------+--------------+
| 3 | 1 | 3 |
+----+-------+--------------+
| 4 | 1 | 4 |
+----+-------+--------------+
If you want to filter your table based on a comma separated list with ID, my approach is to
append extra commas at the beginning and at the end of a list as well as at the beginning and at the end of an ID, eg.
1 becomes ,1, and list would become ,1,2,3,4,. The reason for that is to avoid ambigious matches like 1 matches 21 or 12 in a list.
Also, EXISTS is well-suited in that situation, which together with INSTR function should work:
SELECT SUM(`marks`)
FROM `student_mark` sm
WHERE EXISTS(SELECT 1 FROM `class_table`
WHERE `teac_id` = '1' AND
INSTR(CONCAT(',', student_id, ','), CONCAT(',', sm.student_id, ',')) > 0)
Demo
BUT you shouldn't store related IDs in one cell as comma separated list - it should be foreign key column to form proper relation. Joins would become trivial then.

SQL n:m relation

I have the following tables (unrelated columns left out):
studios:
id | user_id
1 | 1
2 | 1
equipment:
id
1
2
studio_equipment:
id | studio_id | equipment_id
1 | 1 | 1
2 | 1 | 1
I have "studios" and "equipment". A studio belongs to a user. Equipment can be assigned to studios (studio_equipment table). An equipment can be assigned multiple times to a studio but there can also be studios that have no equipment yet.
I want to retrieve all studios for a certain user together with all the possible equipment that could be assigned to these studios. If an equipment has already been assigned to a studio, then show this aswell.
For the example above that would mean the following (for user_id 1):
desired results:
studio.id | equipment.id | studio_equipment.id
1 | 1 | 1
1 | 1 | 2
1 | 2 | null
2 | 1 | null
2 | 2 | null
This is my SQL statement thus far:
SELECT `s`.*, `e`.*, `se`.*
FROM (`studios` AS s)
LEFT JOIN `studio_equipment` AS se ON `s`.`id`=`se`.`studio_id`
LEFT OUTER JOIN `equipment` AS e ON `se`.`equipment_id`=`e`.`id`
WHERE `s`.`user_id` = '1'
But this does not retrieve all the data i want. For Example studio 2 is retrieved but not paired with all the possible equipment.
Thanks in advance for your help!
SELECT
s.id AS studio_id,
e.id AS equipment_id,
se.id AS studio_equipment_id
FROM
studios AS s
CROSS JOIN
equipment AS e
LEFT JOIN
studio_equipment AS se
ON se.studio_id = s.id
AND se.equipment_id = e.id ;

Join 2 tables, print data from 2nd table when joined rows occur

The case:
I have 2 tables, 'contracts' and 'members' tied with contract.id = members.cid.
Each contract has one main member and several secondary members (usually the children and spouse of the main member). The main member's details (name, address etc) are stored in table contracts whereas, extra members details are kept in table members. (bad logic, i know but this was a mess to begin with and i need time to redesign the db and website)
The desired output:
When I run a batch print of all contracts (lets say, every Friday) I need to also print a copy of the contract for each member, too but with the member's details on the contract instead of the main member.
The question:
How does this translate into a mysql query? Ok, its a left join, but how do I say "print data from table members instead of contracts for the joined rows"?
Main fields that occur in the 2 tables are name + surname, those should be enough for a draft query example.
Example tables and data:
contracts
-------------------------
id | name | surname |
-------------------------
1 | Tom | Jones |
2 | Jamie | Oliver |
members
--------------------------------
id | cid | name | surname |
--------------------------------
1 | 1 | Jack | Jones |
2 | 1 | Anne | Jones |
3 | 2 | Cathy | Wilson |
So the results I want shoudld be:
cid | name | surname |
--------------------------
1 | Tom | Jones |
1 | Jack | Jones |
1 | Anne | Jones |
2 | Jamie | Oliver |
2 | Cathy | Wilson |
If i write
SELECT c.name as name, c.surname as surname, m.name as name, m.surname as surname
FROM contracts c
join members m on c.id = m.cid
I simply end up with
name and name_1, surname and surname_1 but I want ALL names to fall under name and likewise for all other matching columns.
Hope this works :::
select c1.id, c1.name, c1.surname
from contracts c1
union
(Select m.id, m.name, m.surname
from
members m left join contracts c on (c.id = m.cid))
This is what I finally did and it worked (field names are different but the syntax is what matters):
SELECT u.id, u.onoma_u, u.name_u,
coalesce(u.programa, aa.programa) as programa,
coalesce(u.barcode, aa.barcode) as barcode,
coalesce(u.plan, aa.plan) as plan,
coalesce(u.im_exp, aa.im_exp) as im_exp,
coalesce(u.symb, aa.symb) as symb
FROM (SELECT a1.id, a1.onoma_u, a1.name_u, a1.programa, a1.barcode, a1.plan, a1.im_exp, a1.symb
FROM aitisi a1
UNION
SELECT a2.id, m.name, m.surname, NULL, NULL, NULL, NULL, NULL
FROM members m
JOIN aitisi a2 ON a2.id = m.symbid) u
JOIN aitisi aa ON aa.id = u.id;
I used aliases and NULLS as dummy fields to fill in the blanks.