Subtracting value based on the other table - mysql

--work--
day hours
4 8
5 9
6 8
--crew--
employee day
2 4
3 4
6 4
2 5
3 5
4 5
--break--
hours day employee
1 4 2
.5 5 3
Here is the sql query i have:
SELECT w.hours, w.hours-b.hours, c.employee
FROM work w
LEFT JOIN crew c ON w.day = c.day
LEFT JOIN break b ON w.day = b.day
WHERE w.day = 4
this is subtracting 1 hour from every employee
i want the break.hours to be subtracted only if that employee has taken a break.
if he hasn't, i simply want work.hours.

COALESCE is all you need.
SELECT a.employee,
a.day,
b.hours - COALESCE(c.hours,0) totalHours
FROM crew a
INNER JOIN work b
ON a.day = b.day
LEFT JOIN `break` c
ON a.employee = c.employee AND
a.day = c.day
-- WHERE b.day = 4
SQLFiddle Demo Link
SQLFiddle Demo Link (with WHERE clause)

Related

How to select 1:n related records which are minimum 2 per type in MySQL

I have the following database tables.
my_left_table
left_id name
1 A
2 B
3 C
my_right_tabe
right_id thing left_id_fk status
1 D 1 new
2 E 1 new
3 F 2 old
4 G 3 old
5 H 3 new
6 I 3 new
7 J 1 old
8 K 2 old
9 L 2 new
10 M 3 old
11 N 3 old
12 O 1 new
My desired result is as follow.
my_left_table
left_id name
3 C
How do I select the left records which its right records have AT LEAST 2 status is new AND 2 status is old. For example, left_id 1 is not the target because three of its right records have the status new but only one record has the status old.
So far I have is.
SELECT *, COUNT(my_right_tabe.left_id_fk) AS count_left_id_fk
FROM my_left_table
INNER JOIN my_right_tabe
ON my_left_table.id = my_right_tabe.left_id_fk
GROUP BY my_right_tabe.left_id_fk
Use the HAVING clause in MySQL
Like the following
SELECT my_left_table.left_id, my_left_table.name
FROM my_left_table
INNER JOIN my_right_tabe
ON my_left_table.left_id = my_right_tabe.left_id_fk
GROUP BY my_right_tabe.left_id_fk
HAVING SUM(my_right_tabe.status="new") >= 2 AND
SUM(my_right_tabe.status="old") >= 2
You can achieve desired results by first grouping the values and then check its total. If its >= 2 pull that record.
Here is the query
SELECT z.*
FROM
(
SELECT a.left_id, name, status, IF(COUNT(*) >=2, 1, 0) AS status_calc
FROM my_left_table a JOIN my_right_table b
ON a.left_id = b.left_id_fk
GROUP BY left_id, status
) z
GROUP BY z.left_id
HAVING SUM(status_calc) = 2;
Working Demo

For grouping with mysql query

SELECT mf.fm_id,ema.asc_code as 'Xaxis', count(*) as 'Total'
from els_jobsheet_data as jd
join master_asc as ma on (jd.asc_id=ma.asc_id )
join els_master_asc_l3l4 as ema on jd.els_asc_code =ema.asc_code
join master_city as mc on mc.city_id=ma.city_id
join master_state as ms on ms.state_id=mc.state_id
join master_region as mr on mr.region_id=ms.region_id
join master_fso as mf on mf.fm_id=mr.fso_id
where jd.insert_time is not null AND jd.insert_time>='2015-12-01'
AND jd.insert_time<='2015-12-31'
group by Xaxis
order by Xaxis ASC
A query returned-
fm_id Xaxis Total
2 DOA 43
3 DSRO 12
1 TRC 12
9 3423 10
5 5455 11
2 2443 13
7 55AS 2
But I want to group it like
fm_id Xaxis Total
2 DOA 43
3 DSRO 12
1 TRC 12
9 Other 342
(whatever be its total)
i m newbie.Pls help..
Use a case expression instead of the raw data.
SELECT
max(mf.fm_id) as fm_id
, CASE
WHEN ema.asc_code IN ('DOA', 'DSRO', 'TRC') THEN ema.asc_code
ELSE 'Other'
END AS Xaxis
, COUNT(*) AS Total
FROM els_jobsheet_data AS jd
JOIN master_asc AS ma ON (jd.asc_id = ma.asc_id)
JOIN els_master_asc_l3l4 AS ema ON jd.els_asc_code = ema.asc_code
JOIN master_city AS mc ON mc.city_id = ma.city_id
JOIN master_state AS ms ON ms.state_id = mc.state_id
JOIN master_region AS mr ON mr.region_id = ms.region_id
JOIN master_fso AS mf ON mf.fm_id = mr.fso_id
WHERE jd.insert_time IS NOT NULL
AND jd.insert_time >= '2015-12-01'
AND jd.insert_time <= '2015-12-31'
GROUP BY
CASE
WHEN ema.asc_code IN ('DOA', 'DSRO', 'TRC') THEN ema.asc_code
ELSE 'Other'
END
ORDER BY
Xaxis ASC

MySQL combining multiple rows into one row

I've two tables as below:
1. tbl_student:
id name
1 raj
2 raja
3 raju
4 rajan
tbl_attendance
id student_id month attended_days
1 1 1 6
2 1 2 16
3 8 1 8
4 7 2 14
5 8 2 13
6 7 1 11
I need to join these two tables combining multiple rows for each month for each student from tbl_attendance into a single row to obtain a result like this:
id name month attended_days month attended_days
1 raj 1 6 2 16
7 raja 1 11 2 14
8 rajan 1 8 2 13
Thanks in advance for any help.
Instead of displaying month value in each of the records,
you can use them as column headers and attendance as their value.
Use pivot type solution to achieve required solution.
Example:
select s.id as student_id
, s.name as student_name
, max( case when a.month = 1 then a.attended_days else null end ) as month_1
, max( case when a.month = 2 then a.attended_days else null end ) as month_2
, max( case when a.month = 3 then a.attended_days else null end ) as month_3
-- ...
, max( case when a.month = 12 then a.attended_days else null end ) as month_12
from table_student s
left join table_attendance a on s.id = a.student_id
group by s.id, s.name
Your question is not very complete, but i think you want something like this:
select s.*,
coalesce(a1.month, a2.month, a3.month) as month,
coalesce(a1.attended_days , a2.attended_days , a3.attended_days ) as attended_days
from table_student s
left join table_attendance a1 on s.id = a1.student_id and a1.month = 1
left join table_attendance a2 on s.id = a2.student_id and a2.month = 2
left join table_attendance a3 on s.id = a3.student_id and a3.month = 3
The previous code is used if you want to show all months in one column. For multiple columns, you can use this example:
select s.*,
a1.month as month_1,
a2.month as month_2,
a3.month as month_3,
a1.attended_days as attended_days_1,
a2.attended_days as attended_days_2,
a3.attended_days as attended_days_3
from table_student s
left join table_attendance a1 on s.id = a1.student_id and a1.month = 1
left join table_attendance a2 on s.id = a2.student_id and a2.month = 2
left join table_attendance a3 on s.id = a3.student_id and a3.month = 3
Do this for all the 12 months. I used 3 as example.

MySQL Greatest N Results with Join Tables

Selecting the Top n Results, I've seen the numerous posts and great articles on here about how to do it but I am struggling to do it with my data set. Most of the examples focus on data sets without the need for additional joins.
I've been trying to apply the examples from http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/ to my query without much success.
Three tables exist Person, Credit and Media.
Person links to Credit and Credit to Media.
The query below should return the top 5 medias per person, however it doesn't, where have I gone wrong?
SELECT
p.id AS person_id,
c.id AS credit_id,
m.id AS media_id, m.rating_average
FROM person p
INNER JOIN credit c ON c.person_id = p.id
INNER JOIN media m ON m.id = c.media_id
where (
select count(*) from media as m2
inner JOIN credit c2 on m2.id=c2.media_id
where c2.person_id = c.person_id and m2.rating_average >= m.rating_average
) <= 5
Clarification:
Top Medias are calculated from those with the highest rating_average.
Update:
SQLFiddle http://sqlfiddle.com/#!9/eb0fd
Desired output for top 3 medias (m) per person (p). Obviously I would like to do be able this for the top 5 medias but this is only test data.
p m c rating_average
1 9 27 9
1 7 28 8
1 1 1 8
2 1 5 8
2 4 8 8
2 7 29 8
3 4 10 8
3 3 9 6
3 5 11 5
4 3 13 6
4 5 14 5
4 6 15 3
5 4 16 8
5 5 17 5
5 6 18 3
6 6 19 3
7 7 20 8
8 9 23 9
8 1 21 8
8 8 22 0
9 1 24 8
9 7 26 8
9 5 25 5
i think i solve it :)
First here is one solution based on the way you started. But there is a catch I couldn't solve it to show exact 3 (or whatever number you choose i pick 3 for example) row for each person_id. Problem is that solution is based on counting how many rows is there with the rating_average greater then current row. So if you have 5 same top value you could choose to show all 5 or not to show them at all and that's not good. So here is the way you do that... (of course this is example where if you have 4 top value you show them all (I think that no make sense at all to not show the data))...
SELECT t1.person_id, t1.credit_id, t1.media_id, t1.rating_average
FROM (SELECT p.id AS person_id, c.id AS credit_id, m.id AS media_id,
m.rating_average AS rating_average
FROM person p
INNER JOIN credit c ON c.person_id = p.id
INNER JOIN media m ON m.id = c.media_id) as t1
WHERE (SELECT COUNT(*)
FROM (SELECT p.id AS person_id, c.id AS credit_id, m.id AS media_id,
m.rating_average AS rating_average
FROM person p
INNER JOIN credit c ON c.person_id = p.id
INNER JOIN media m ON m.id = c.media_id) AS t2
WHERE t2.person_id = t1.person_id AND t2.rating_average > t1.rating_average) < 3
ORDER BY person_id ASC, rating_average DESC
Important: This solution can work (to show exact 3 rows for each person) if you don't have value that repeat it self... Here is the Fiddle http://sqlfiddle.com/#!9/eb0fd/64 you can see the problem where person_id is 1!
After that i played a little more and make it work just as you wanted in the question i think. Here is a code for that:
SET #num := 0, #person := 0;
SELECT person_id, credit_id, media_id, rating_average, rowNumber
FROM (SELECT t1.person_id, t1.credit_id, t1.media_id, t1.rating_average,
#num := if(#person = t1.person_id, #num + 1, 1) AS rowNumber,
#person := t1.person_id
FROM (SELECT p.id AS person_id, c.id AS credit_id, m.id AS media_id,
m.rating_average AS rating_average
FROM person p
INNER JOIN credit c ON c.person_id = p.id
INNER JOIN media m ON m.id = c.media_id
ORDER BY p.id ASC, m.rating_average DESC) as t1) as t2
WHERE rowNumber <= 3
Here is the Fiddle for that http://sqlfiddle.com/#!9/eb0fd/65 ...
GL!
P. S. sorry for my English hope you could understand what i was talking about...

Inner join on a distinct set of parameters

I've tried a few of the similar SO questions, but I can't seem to figure it out.
On the first inner join, I only want to bring in DISTINCT function columns code and serial_id. So when I do my SUM selects, it calculates one per distinct. Ie there are multiple rows with the same func.code and func.serial_id. I only want 1 of them.
SELECT
sl.imp_id,
lat.version,
SUM(IF(lat.status = 'P',1,0)) AS powered,
SUM(IF(lat.status = 'F',1,0)) AS functional
FROM slots sl
INNER JOIN functions func ON sl.id = func.slot_id
INNER JOIN latest_status lat ON lat.code = func.code
AND lat.serial_id = func.serial_id
WHERE sl.id=55
GROUP BY sl.imp_id, lat.version
EDIT 2 - sample data explanation -------------------
slots - id, imp_id, name
functions - id, slot_id, code, serial_id
latest_status - id, code, serial_id, version, status
**slots**
id imp_id name
1 5 'the name'
2 5 'another name'
3 5 'name!'
4 5 'name!!'
5 5 'name!!!'
6 5 'testing'
7 5 'hi'
8 5 'test'
**functions**
id slot_id code serial_id
1 1 11HRK 10
2 2 22RMJ 11
3 3 26OLL 01
4 4 22RMJ 00
6 6 11HRK 10
7 7 11HRK 10
8 8 22RMJ 00
**latest_status**
id code serial_id version status
1 11HRK 10 1 F
1 11HRK 10 2 P
3 22RMJ 11 1 P
4 22RMJ 11 2 F
5 26OLL 01 1 F
6 26OLL 01 2 P
7 22RMJ 00 1 F
8 22RMJ 00 2 F
After running the query, the result should look like this:
imp_id version powered functional
5 1 1 3
5 2 2 2
The function table gets rolled up based on the code, serial_id. 1 row per code, serial_id.
It then gets joined onto the latest_status table based on the serial_id and code, which is a one (functions) to many (latest_status) relationship, so two rows come out of this, one for each version.
How about using DISTINCT?
SELECT
SUM(IF(lat.status = 'P',1,0)) AS powered,
SUM(IF(lat.status = 'F',1,0)) AS functional
FROM slots sl
INNER JOIN (Select DISTINCT id1, code, serial_id from functions) f On sl.rid = f.id1
INNER JOIN latest_status lat ON lat.code = f.code
AND lat.serial_id = f.serial_id
WHERE sl.id=55
GROUP BY sl.imp_id, lat.version
If you want only the distinct code and serial_id, you need to group by those not the imp_id and version. And end up with something like
SELECT
SUM(IF(lat.status = 'P',1,0)) AS powered,
SUM(IF(lat.status = 'F',1,0)) AS functional
FROM slots sl
INNER JOIN functions func ON sl.rid = func.id1
INNER JOIN latest_status lat ON lat.code = func.code
AND lat.serial_id = func.serial_id
WHERE sl.id=55
GROUP BY func.code, func.serial_id
However, this could all be rubish, without more data as tgo what some of those other columns are, but they dont seem to be the ones you wanted to group by.