MYSQL left join and inner join - mysql

i have 3 tables
Employee => e_id (P.K), dep_id (F.k)
Department => d_id (P.K)
Attendance => id (P.K), date_of_absence (DATE), e_id_f (F.K)
i want to get the number of absent employees in certain department for a certain period.
i created a table called MyDates
CREATE TABLE MyDates (mydate date);
and this procedure to fill MyDates Table with Dates
CREATE PROCEDURE filldates(dateStart DATE, dateEnd DATE)
BEGIN
WHILE dateStart <= dateEnd DO
INSERT INTO MyDates (mydate) VALUES (dateStart);
SET dateStart = date_add(dateStart, INTERVAL 1 DAY);
END WHILE;
END;
then i called the procedure to fill MyDates with date CALL filldates('2017-01-01','2017-03-31');
Then i made this select statement :
select mydates.mydate, count(attendance.date_of_absence)
from mydates left join attendance on
mydates.mydate = attendance.date_of_absence
where mydates.mydate Between "2017-01-01" AND "2017-01-31"
group by mydates.mydate
This query gets what i need but for all departments, BUT for a certain department the number of rows is incorrect
select mydates.mydate, count(attendance.date_of_absence)
from mydates
left join attendance on mydates.mydate = attendance.date_of_absence
inner join employee on attendance.e_id_f = employee.e_id
where mydates.mydate Between "2017-01-01" AND "2017-01-31" AND employee.dep_id = 4
group by mydates.mydate;
This is a screenshot
IMG

There are 3 issues with your query I can see. The 1st 2 results in dropping the dates where no matching record is found, the 3rd potentially means that you get wrong counts:
inner join employee on attendance.e_id_f = employee.e_id - if there is no matching record for a day, then attendance.e_id_f will be null for that day. The inner join will eliminate this record from the resultset. Solution: make this join a left as well.
AND employee.dep_id = 4 - this would only keep records in the resultset, that have an employee record associated with it, so if you have a day with 0 absences, this criteria would eliminate it from the resultset. Solution: include this criteria in the join condition.
count(attendance.date_of_absence) - counts occurances from the attendance table, which will not be correct after adding the 2nd left join. Solution: use count(employee.dep_id) instead.
Modified query:
select mydates.mydate, count(employee.dep_id)
from mydates
left join attendance on mydates.mydate = attendance.date_of_absence
left join employee on attendance.e_id_f = employee.e_id AND employee.dep_id = 4
where mydates.mydate Between "2017-01-01" AND "2017-01-31" group by mydates.mydate
Alternative solution is to use nested joins, where you specifically instruct MySQL to execute attendance inner join employee join first. You still need to move the employee.dep_id = 4 condition to the join condition:
select mydates.mydate, count(attendance.date_of_absence)
from mydates
left join (attendance inner join employee)
on mydates.mydate = attendance.date_of_absence
and attendance.e_id_f = employee.e_id
and employee.dep_id = 4
where mydates.mydate Between "2017-01-01" AND "2017-01-31"
group by mydates.mydate
In the latter case the nested inner join ensures that only those attendance records are returned that belong to dep_id = 4 and these records are then left joined to your dates table. In this case there is no need to change the field you are counting.

Related

SQL : Getting last records for two conditions

i have two tables
Companies
company_payments
each company is doing two types of payments vat and witholding_tax, i am doing following query which returns me company's last payments for the current year
SELECT * FROM companies c
JOIN ( SELECT MAX(id) max_id, company_id FROM company_payments )
c_max ON (c_max.company_id = c.id)
JOIN company_payments cp ON (cp.id = c_max.max_id)
WHERE
YEAR(cp.last_payment) = YEAR(CURDATE())
Below is the structure of my company_payments table
Now instead of returning one last payment i want to return last payment for payment type 'vat' and 'witholding_tax' both , if its not there would need an empty record ,
Could someone please advise me how can i achieve this
You can use a correlated subquery:
select cp.*
from company_payments cp
where cp.last_payment = (select max(cp2.last_payment)
from company_payments cp2
where cp2.company_id = cp.company_id and
cp2.payment_type = cp.payment_type
);
If you want to filter only on the most recent year, you can add the date filter to the outer query.

Facing issue in mysql query

I am facing issue while joining the three table Employee,Eventcelibration,emp_event.
Employee table has three fields birth_date,date_of_joining,Anniversary_date.
want to make a query on basis of above three column.if employee has birthday then select the birth_day description from other table eventcelibration and if he has joining date today then select joining message from the table. like wise.
made the query on some dummy data as below.
Employee table
id
employee_id
name
designation
contactno
email
birth_date
date_of_joining
Table eventcelibration
Event_cl_id
Event_type
frequency
description
template
subject
Third Table
Emp_event
Event_id
employee_id.
I have tried the query,
select a.employee_id,a.name,a.email,a.birth_date,c.template,c.description,c.subject,c.event_type from
employee a inner join emp_event b on a.id = b.Event_id
inner join eventcelebration c on b.employee_id = c.event_cl_id
where CAST(birth_date as date) = CAST(curdate() as date) or CAST(date_of_joining as date) = CAST(curdate() as date)
It returns us the three result but I want only one that is birthday , because birthday is today date.
You are considering date datatype, which includes year.
In this case for an yearly event only month and day of month matters not the year.
I hope the following query would help
SELECT a.employee_id,a.name,a.email,a.birth_date,c.template,c.description,c.subject,c.event_type FROM
employee a INNER JOIN emp_event b ON a.id = b.Event_id
INNER JOIN eventcelebration c ON b.employee_id = c.event_cl_id
WHERE DATE_FORMAT(birth_date,'%m-%d') = DATE_FORMAT(CURDATE(),'%m-%d')
OR DATE_FORMAT(date_of_joining,'%m-%d') = DATE_FORMAT(CURDATE(),'%m-%d');
Hope this helps
Select a.birth_date only in the select query.
select a.birth_date from
employee a inner join emp_event b on a.id = b.Event_id
inner join eventcelebration c on b.employee_id = c.event_cl_id
where CAST(birth_date as date) = CAST(curdate() as date) or CAST(date_of_joining as date) = CAST(curdate() as date)

in mysql get count of actual record with join and group by

SELECT count(ap.id) AS appointment_count, sch.id AS schedule_id
FROM (schedules sch) JOIN schedule_times st ON sch.id=st.schedule_id
LEFT JOIN appointments ap ON sch.id=ap.schedule_id AND ap.status="p"
WHERE sch.id = 1 GROUP BY sch.id
ORDER BY st.starts_at
A there are two records in schedule_times field for a schedule id=1.
actual appointments count for schedule with id = 1 is one but appointment_count showing count as 2.
can any one help how to get actual appointment count using this sql query

MySQL query help (join, subselect)

I have 2 tables: orders and bookings.
The orders table contain unique orders from customers. Each customer has booked a locker for a period of 1 to 5 years and therefore the bookings table can contain 1 to 5 rows pr. order (1 row for each year). Each row in the booking table contains and end_date which is the same date every year (20XX-06-30).
I want to select all the orders where the corresponding final end_date in the bookings table is this year (2014-06-30).
SELECT DISTINCT orders.id
FROM orders,
bookings
WHERE orders.id = bookings.order_id
AND bookings.end_date = '2014-06-30'
The problem with this query is that it also selects the orders where the end_date in the booking rows continue the following years (2015-06-30, 2016-06-30 etc).
I am not sure I understood well, but here's a solution for what I understood, this should get you the order ids where there last end date (max) is 2014-06-30.
SELECT orders.id, MAX(bookings.end_date)
FROM orders INNER JOIN bookings
ON orders.id = bookings.order_id
GROUP BY bookings.order_id
HAVING MAX(bookings.end_date) = '2014-06-30'
Maybe join to the bookings again, checking for a larger booking date for the same order id:-
SELECT orders.id
FROM orders
INNER JOIN bookings ON orders.id = bookings.order_id
LEFT OUTER JOIN bookings2 ON orders.id = bookings2.order_id AND bookings2.end_date > bookings.end_date
WHERE bookings.end_date = '2014-06-30'
AND bookings2.end_date IS NULL

Display distinct record for each employee

I have two tables appointment app and faculty fac. they are multiple record with same emp ids
I need to join two tables by app.employeeid=fac.empid (here i will get duplicate records)
and app.begindate<=now and app.enddate>=now and app.percent>.50 (here also i will get duplicate records because empid(multiple records with same id) can have same begindate,end date and >.50 )
max(app.amt) (here also i will get duplicate records because there can be a chance where empid(multiple records with same id) have same amt) . Finally at the end i want to select distinct(max(app.amt)) to get only single record for empid.
I used the query:
select max(app.amt) ,fac.email,fac.employee_id,fac.empid,fac.first_name,fac.last_name,fac.department_id,fac.titlecode,app.begindate,app.enddate,app.percent,
app.title_name,app.department_name from faculty fac, appointment app where
app.employeeid=fac.empid and app.begindate <= now() and app.enddate >= now() and app.percent>.50 group by fac.empid
/
i am having an issue i am getting max(app.amt) but corresponding column values are not properly matched with max amt record.the other column values are taking in random.I want the exact corresponding values to display.
sample date in appointment table for few fileds:
SELECT app.amt, fac.email, fac.employee_id, fac.empid,
fac.first_name, fac.last_name, fac.department_id, fac.titlecode,
app.begindate, app.enddate, app.percent,
app.title_name, app.department_name
FROM appointment app
LEFT JOIN appointment app2 -- self-join to locate max
ON app.employee_id = app2.employee_id AND app.amt < app2.amt -- of app.amt
INNER JOIN faculty fac ON app.employee_id=fac.empid -- join to fac
WHERE app2.amt IS NULL -- isolate rows with max(app.amt)
AND app.begindate <= NOW()
AND app.enddate >= NOW()
AND app.percent>.50 group by fac.empid
employee_id begindate enddate amt percent department_name
5528 7/1/2011 9/30/2011 0 1 m1
5528 7/1/2011 9/30/2011 193100 1 m1
5528 10/1/2011 6/30/2013 79000 1 m1
5528 10/1/2011 6/30/2013 118500 1 m2
5528 10/1/2011 6/30/2013 0 1 m2
To select not only a MAX(column) but the row corresponding to it, for each employee_id, you usually use a LEFT JOIN to join the table to itself. (This is also known as "greatest-n-per-group" and there are a number of questions in this vein on stackoverflow under this tag).
e.g. to select the max(app.amt) per employee_id with its corresponding row you would do:
SELECT app.*
FROM appointment app
LEFT JOIN appointment app2
ON app.employee_id = app2.employee_id AND app.amt < app2.amt
WHERE app2.amt IS NULL;
Why does this work? In essence, this says "pick the row with the amt for which there is no higher amt for that employee", for each employee.
The LEFT JOIN appointment app2 ON app.employee_id=app2.employee_id joins app to itself on the employee_id (note this is what you wanted to GROUP BY). This produces a table of appointment with every possible pair of amt for each employee_id.
Then, we also specify that app.amt < app2.amt. So we only display rows where the (app.amt,app2.amt) pair has the former less than the latter.
Since this is a LEFT JOIN, if we happen to find an app.amt for which we can't find a corresponding row in app2 with the same employee_id and a greater app2.amt, then app2.amt is set to NULL. This occurs precisely when we've found the greatest app.amt for that employee.
Solution
So, adapt your query using this "LEFT JOIN to myself to get the corresponding row" method:
SELECT app.amt, fac.email, fac.employee_id, fac.empid,
fac.first_name, fac.last_name, fac.department_id, fac.titlecode,
app.begindate, app.enddate, app.percent,
app.title_name, app.department_name
FROM appointment app
LEFT JOIN appointment app2 -- self-join to locate max
ON app.employee_id = app2.employee_id AND app.amt < app2.amt -- of app.amt
INNER JOIN faculty fac ON app.employee_id=fac.empid -- join to fac
WHERE app2.amt IS NULL -- isolate rows with max(app.amt)
AND app.begindate <= NOW()
AND app.enddate >= NOW()
AND app.percent>.50
So, the only things that have changed are:
added a LEFT JOIN appointment app2 ... WHERE app2.amt IS NULL to locate the row with the max app.amt for each employeeid.
changed your FROM app, fac .. WHERE app.employeeid=fac.empid into a JOIN (see note below)
no need for the GROUP BY or MAX(app.amt) -- the INNER JOIN takes care of that.
Note on converting the FROM app, fac ... WHERE join_condition to FROM app LEFT JOIN fac ON join_condition: a join tends to be more efficient than selecting from multiple tables and joining in the WHERE.
What type of join you use (app to fac) may make a difference to your results; to recover your former behaviour use INNER JOIN. This will only display rows where the employee exists in both the app table and the fac table.
Using a LEFT JOIN is slightly more efficient than the INNER JOIN, and the results will be identical unless there is an employee in your appointments table who does not have an entry in the faculty table. Then they will be displayed in the results list with NULL for all the fac.xxx fields.
Using a RIGHT JOIN will show every employee from the faculty table regardless of whether they have appointments or not -- if they have no appointments they'll still be shown in your results, just with NULL for all their appointment-related fields.
Just subtle differences in the JOINs (ahh, but that's another question).