SQL join and sub query in sql - mysql

I have a table employee having columns id (primary key), *employee_name* and another table called employee_works with columns *employee_id* (foreign key referencing employee.id), *start_date* (datetime), *finish_date* (datetime).
Here are some datas for employee table:
**id** **employee_name**
1 employee A
2 employee B
3 employee C
4 employee D
5 employee E
6 employee F
7 employee G
employee_works table:
1 2010-01-01 00:00:00 NULL
2 2010-01-01 00:00:00 2010-01-10 10:00:00"
2 2010-01-13 00:00:00 2010-01-15 10:00:00"
2 2010-01-31 00:00:00 NULL
4 2010-02-18 00:00:00 2011-01-31 00:00:00"
6 2010-02-18 00:00:00 NULL
NULL value means the employee still works.
I need to get a single query showing the list of persons in employee, if they worked with us, who still works in our company, who left and if possible, for how long they worked with us.
Example:
id employee_name status
1 Employee A Still with us
3 Employee C Never worked
4 Employee D Left
My attempt:
SELECT emp.id,emp.name,
CASE
WHEN occ.finish_date is NULL and occ.start_date is NOT NULL THEN 'Still working'
WHEN occ.finish_date is NULL and occ.start_date is NULL THEN 'Never Worked'
WHEN occ.finish_date is NOT NULL and occ.start_date is NOT NULL THEN 'Left'
END
AS status
FROM employee AS emp
LEFT JOIN employee_works AS occ ON emp.id=occ.employee_id
GROUP BY emp.id, occ.finish_date
I also want to get the total no of days the employees have worked in another column?

The problem is that you have a group by but no aggregations for the definition of status. Mysql does not give you a syntax error. Instead, it gives you a random status:
Try something like this instead:
select id, name,
(CASE WHEN statusint = 3
THEN 'Still working'
WHEN statusint = 1 or statusint is null
THEN 'Never Worked'
WHEN statusint = 2
THEN 'Left'
END) AS status,
days_worked
from (SELECT emp.id, emp.name,
max(CASE WHEN occ.departure_date is NULL and occ.start_date is NOT NULL
THEN 3
WHEN occ.departure_date is NULL and occ.start_date is NULL
THEN 1
WHEN occ.departure_date is NOT NULL and occ.start_date is NOT NULL
THEN 2
END) AS statusint,
sum(datediff(coalesce(departure_date, curdate()), occ.start_date
) as days_worked
FROM employee emp LEFT JOIN
employee_works occ
ON emp.id=occ.employee_id
GROUP BY emp.id, emp.name
) eg
This "feature" of mysql is called hidden columns. Folks who write mysql (and many who use it) think this is a great feature. Many people who use other databases just scratch their heads and wonder why any database would act so strangely.
By the way, you should check if someone who is employeed multiple times gets assigned a new id. If so, your query might need more advanced name matching methods.

Try to simplify your condition.
SELECT a.*,
CASE
WHEN b.employeeID IS NULL THEN 'NEVER WORKED'
WHEN b.finish_date IS NULL THEN 'STILL WORKING'
WHEN DATE(b.finish_date) < CURDATE() THEN 'LEFT'
END as `Status`
FROM employee a
LEFT JOIN employee_works b
on a.id = b.employeeID

Related

MySQL: Count Duplicate Rows in Multiple Tables Without Inflating Counts

I'm attempting to count how many completed events each person in my table have done. The problem I'm running into is that people have multiple jobs in my person table, which means there are purposeful multiple rows per person -- which is making my event table double when I try to do counts.
Here's a SQL Fiddle of my code. Easiest to see ID #1 only has two events completed but the table counts four because they also have two jobs.
Here's my sample schema:
CREATE TABLE persontable
(id INT NOT NULL
, name VARCHAR(255) NOT NULL
, employer VARCHAR(255) NOT NULL
, PRIMARY KEY(id,employer)
);
CREATE TABLE eventtable
(id INT NOT NULL
, name VARCHAR(255) NOT NULL
, eventname VARCHAR(255) NOT NULL
, eventdate DATE NOT NULL
, status VARCHAR(255) NOT NULL
, PRIMARY KEY (id,eventname,eventdate));
INSERT INTO persontable (id,name,employer) VALUES
(1,"Joe","Party Inc."),
(1,"Joe","Body Shop"),
(2,"Puddy","Body Shop"),
(3,"Newman","Postal Service"),
(3,"Newman","Computers Inc."),
(4,"Delores","Mulva LLC"),
(5,"Morty","Executive Raincoats"),
(6,"Helen","Body Shop"),
(7,"Frank","Retired"),
(7,"Frank","Mulva LLC"),
(8,"Estelle","Retired"),
(9,"Mandelbaum","Weight Lifters Guild"),
(9,"Mandelbaum","The Wiz"),
(10,"Fred","The Wiz");
INSERT INTO eventtable (id,name,eventname,eventdate,status) VALUES
(1,"Joe","Mayo Party",5/4/94,"Completed"),
(1,"Joe","Coat Shopping",1/2/95,"Completed"),
(4,"Delores","Play",5/9/94,"Completed"),
(4,"Delores","Name Guessing",3/9/98,"Completed"),
(9,"Mandelbaum","Working Out",3/2/97,"Declined"),
(10,"Fred","Store Sale",8/9/96,"Completed");
And my fairly simple query that's adding the additional counts:
SELECT
p.id,
e.id,
COUNT(DISTINCT CASE WHEN e.status="Completed" THEN e.id ELSE NULL END) AS EVENT,
COUNT(CASE WHEN e.status="Completed" THEN e.id ELSE NULL END) AS YTDAllShiftsComp
FROM persontable p
LEFT JOIN eventtable e ON p.id = e.id
GROUP BY p.id;
My desired outcome for the sample is:
id id EVENT YTDAllShiftsComp
1 1 1 2
2 (null) 0 0
3 (null) 0 0
4 4 1 2
5 (null) 0 0
6 (null) 0 0
7 (null) 0 0
8 (null) 0 0
9 9 0 0
10 10 1 1
Thanks for the help!
Thats what happens when you dont normalize your data.Since each person can attend multiple events and each event can host multiple persons you need an intermediate table which holds the primary keys of both tables,this is called many to many relation.So I Joined just on distinct persons id,eliminating the duplicates,but the real solution is to add a new table.
SELECT
x.id,
e.id,
COUNT(DISTINCT CASE WHEN e.status="Completed" THEN e.id ELSE NULL END) AS EVENT,
COUNT(CASE WHEN e.status="Completed" THEN e.id ELSE NULL END) AS YTDAllShiftsComp
FROM (SELECT id FROM persontable GROUP BY id)x
LEFT JOIN eventtable e ON x.id = e.id
GROUP BY x.id;
You can use correlated subqueries:
SELECT
p.id,
(SELECT COUNT(DISTINCT CASE WHEN e.status="Completed" THEN e.id END)
FROM eventtable e
WHERE p.id = e.id) AS EVENT,
(SELECT COUNT(CASE WHEN e.status="Completed" THEN e.id END)
FROM eventtable e
WHERE p.id = e.id) AS YTDAllShiftsComp
FROM persontable p
GROUP BY p.id;
Demo here
As Georgios mentioned, you need subqueries - but if that sometimes-null 2nd ID column is really needed, you'll want to wrap the main statement to NULL it out if the event count is zero.
SELECT id, if(event=0, NULL, event) as idagain, event, ytdallshiftscomp
FROM (SELECT distinct p.id,
(SELECT count(distinct id) FROM eventtable WHERE id=p.id AND status="Completed") AS EVENT,
(SELECT count(*) FROM eventtable WHERE id=p.id AND status="Completed") AS ytdallshiftscomp
FROM persontable p) q

MySQL: Using Case statements

I have three tables called 'Employees', 'Managers', 'Supervisors'
Employees
ID Name
1 Bob
2 Alex
3 Joe
4 Melissa
5 Hannah
Managers
MID Name
1 Bob
3 Joe
Supervisor
SID Name
3 Joe
4 Melissa
I am trying to check whether if an employee is either a manager or a supervisor or both, and have a result table that looks like
ID Manager Supervisor
1 Manager NULL
2 NULL NULL
3 Manager Supervisor
4 NULL SUPERVISOR
5 NULL NULL
I want to try using a case statement in order to determine whether a fellow Employee ID is either a manager and/or a supervisor. But i'm not sure how to do this.
I know that I have to do something like
SELECT e.id /*how do i select the last 2 columns bc they are new?*/
FROM Employees e, Managers m, Supervisor s
CASE WHEN e.id = m.mid THEN 'manager' /*i don't think this is the correct format*/
CASE WHEN e.id = s.sid THEN 'supervisor'
....
You want to use left join. Follow a simple rule: do not use commas in the from clause. Always use explicit join syntax. Ignore anyone or anything that tells you otherwise. Such advice is very out-of-date.
select e.id,
(case when m.mid is not null then 'Manager' end) as Manager,
(case when s.sid is not null then 'Supervisor' end) as Supervisor
from employees e left join
managers m
on e.id = m.mid left join
supervisors s
on e.id = s.sid;
Also note that your tables are not properly normalized. Because managers and supervisors are employees, you should only have the name column in the employees table. It should not be repeated in the other tables, unless the same person could have different names when they take on the different roles.
Try this:
SELECT ID , (
SELECT MID AS Manager
FROM Managers WHERE MID = ID
LIMIT 1
) AS Manager, (
SELECT SID AS Supervisor
FROM Supervisors WHERE SID = ID
LIMIT 1
) AS Supervisors
FROM Employees
This will display your result as follows
ID Manager Supervisor
1 1 NULL
2 NULL NULL
3 3 3
4 NULL 4
5 NULL NULL

I need help regarding JOIN query in mysql

I have started learning MySQL and I'm having a problem with JOIN.
I have two tables: purchase and sales
purchase
--------------
p_id date p_cost p_quantity
---------------------------------------
1 2014-03-21 100 5
2 2014-03-21 20 2
sales
--------------
s_id date s_cost s_quantity
---------------------------------------
1 2014-03-21 90 9
2 2014-03-22 20 2
I want these two tables to be joined where purchase.date=sales.date to get one of the following results:
Option 1:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 1 2014-03-21 90 9
2 2014-03-21 20 2 NULL NULL NULL NULL
NULL NULL NULL NULL 2 2014-03-22 20 2
Option 2:
p_id date p_cost p_quantity s_id date s_cost s_quantity
------------------------------------------------------------------------------
1 2014-03-21 100 5 NULL NULL NULL NULL
2 2014-03-21 20 2 1 2014-03-21 90 9
NULL NULL NULL NULL 2 2014-03-22 20 2
the main problem lies in the 2nd row of the first result. I don't want the values
2014-03-21, 90, 9 again in row 2... I want NULL instead.
I don't know whether it is possible to do this. It would be kind enough if anyone helps me out.
I tried using left join
SELECT *
FROM sales
LEFT JOIN purchase ON sales.date = purchase.date
output:
s_id date s_cost s_quantity p_id date p_cost p_quantity
1 2014-03-21 90 9 1 2014-03-21 100 5
1 2014-03-21 90 9 2 2014-03-21 20 2
2 2014-03-22 20 2 NULL NULL NULL NULL
but I want 1st 4 values of 2nd row to be NULL
Since there are no common table expressions or full outer joins to work with, the query will have some duplication and instead need to use a left join unioned with a right join;
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p LEFT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
UNION
SELECT p_id, p.date p_date, p_cost, p_quantity,
s_id, s.date s_date, s_cost, s_quantity
FROM (
SELECT *,(SELECT COUNT(*) FROM purchase p1
WHERE p1.date=p.date AND p1.p_id<p.p_id) rn FROM purchase p
) p RIGHT JOIN (
SELECT *,(SELECT COUNT(*) FROM sales s1
WHERE s1.date=s.date AND s1.s_id<s.s_id) rn FROM sales s
) s
ON s.date=p.date AND s.rn=p.rn
An SQLfiddle to test with.
In a general sense, what you're looking for is called a FULL OUTER JOIN, which is not directly available in MySQL. Instead you only get LEFT JOIN and RIGHT JOIN, which you can UNION together to get essentially the same result. For a very thorough discussion on this subject, see Full Outer Join in MySQL.
If you need help understanding the different ways to JOIN a table, I recommend A Visual Explanation of SQL Joins.
The way this is different from a regular FULL OUTER JOIN is that you're only including any particular row from either table at most once in the JOIN result. The problem being, if you have one purchase record and two sales records on a particular day, which sales record is the purchase record associated with? What is the relationship you're trying to represent between these two tables?
It doesn't sound like there's any particular relationship between purchase and sales records, except that some of them happened to take place on the same day. In which case, you're using the wrong tool for the job. If all you want to do is display these tables side by side and line the rows up by date, you don't need a JOIN at all. Instead, you should SELECT each table separately and do your formatting with some other tool (or manually).
Here's another way to get the same result, but the EXPLAIN for this is horrendous; and performance with large sets is going to be atrocious.
This is essentially two queries UNIONed together. The first query is essentially "purchase LEFT JOIN sales", the second query is essentially "sales ANTI JOIN purchase".
Because there is no foreign key relationship between the two tables, other than rows matching on date, we have to "invent" a key we can join on; we use user variables to assign ascending integer values to each row within a given date, so we can match row 1 from purchase to row 1 from sales, etc.
I wouldn't normally generate this type of result using SQL; it's not a typical JOIN operation, in the sense of how we traditionally join tables.
But, if I had to produce the specified resultset using MySQL, I would do it like this:
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #pl_i := IF(pl.date = #pl_prev_date,#pl_i+1,1) AS i
, #pl_prev_date := pl.date AS p_date
, pl.p_id
, pl.p_cost
, pl.p_quantity
FROM purchase pl
JOIN ( SELECT #pl_i := 0, #pl_prev_date := NULL ) pld
ORDER BY pl.date, pl.p_id
) p
LEFT
JOIN ( SELECT #sr_i := IF(sr.date = #sr_prev_date,#sr_i+1,1) AS i
, #sr_prev_date := sr.date AS s_date
, sr.s_id
, sr.s_cost
, sr.s_quantity
FROM sales sr
JOIN ( SELECT #sr_i := 0, #sr_prev_date := NULL ) srd
ORDER BY sr.date, sr.s_id
) s
ON s.s_date = p.p_date
AND s.i = p.i
UNION ALL
SELECT p.p_id
, p.p_date
, p.p_cost
, p.p_quantity
, s.s_id
, s.s_date
, s.s_cost
, s.s_quantity
FROM ( SELECT #sl_i := IF(sl.date = #sl_prev_date,#sl_i+1,1) AS i
, #sl_prev_date := sl.date AS s_date
, sl.s_id
, sl.s_cost
, sl.s_quantity
FROM sales sl
JOIN ( SELECT #sl_i := 0, #sl_prev_date := NULL ) sld
ORDER BY sl.date, sl.s_id
) s
LEFT
JOIN ( SELECT #pr_i := IF(pr.date = #pr_prev_date,#pr_i+1,1) AS i
, #pr_prev_date := pr.date AS p_date
, pr.p_id
, pr.p_cost
, pr.p_quantity
FROM purchase pr
JOIN ( SELECT #pr_i := 0, #pr_prev_date := NULL ) prd
ORDER BY pr.date, pr.p_id
) p
ON p.p_date = s.s_date
AND p.i = s.i
WHERE p.p_date IS NULL
ORDER BY COALESCE(p_date,s_date),COALESCE(p_id,s_id)

Mysql query left join access to variable

If someone can help me with this one I've got these tables And I would like to get all bookings between two dates, and to know whether any of it is done by someone else or it is fail.
Table:Bookings
id | date | date_end | repeat | day_of_week | user_id | price
---------------------------------------------------------------------
1 2013-07-01 0000-00-00 1 3 1 20
2 2013-07-16 0000-00-00 1 -1 1 10
Table:Bookings_done_by_other
id | date | user_id | booking_id
---------------------------------
1 2013-07-01 2 2
2 2013-07-13 2 2
Table:Bookings_fail
id | date | booking_id
---------------------------
1 2013-07-01 2
2 2013-07-11 2
Table:Tally
id
--
1
....
500
Here is my query which is working well, but I can't manage with it to give me all what I want.
SELECT f.date as fail, b.id, b.date, a.showdate, DATEDIFF(b.date, a.showdate) AS diff
FROM bookings as b
LEFT JOIN bookings_fail f ON (b.id = f.booking_id and DATE(f.date) = DATE(a.showdate) )
,
(
SELECT DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) <= '2013-07-20 00:00:00')
ORDER BY Id ASC
) a
WHERE
MOD(DATEDIFF(b.date, a.showdate), b.repeat) = 0
AND
DATE(a.showdate)>= DATE(b.date)
when is like this is throw error that cant see a.showdate in left join, when I move the left join next to "a" table is throw error that cant see b.id so Is there any way to get this join working?
I would like to get result something like this
id | showdate | fail | by_other |
-----------------------------
1 2013-07-01 NULL 2
2 2013-07-01 1 NULL //Or date
1 2013-07-03 NULL NULL
1 2013-07-04 NULL NULL
1 2013-07-05 NULL NULL
1 2013-07-06 NULL NULL
.............................
1 2013-07-20 NULL NULL
Sorry if My English is not too good, and Thanks in advance to everyone who can help.
Here some pictures hope is getting clear I cant explain what I want properly
This is bookings
Booking table
This is fail
Fail table
I cant put more than 2 links so that's why these two are just like text
so what's happening when I want to get my bookings for some date I do this
s16.postimg.org/l00k1math/image.jpg
What I want to have done is
s12.postimg.org/5o5hbnmdp/image.jpg
But not only for booking with id 1 I wont to be b.id = un.booking_id and then if there is record for fail to appear how is on the last picture
Thanks for your patience
I think I finally understand what you are trying to do. My suggestion is to format your query like this:
SELECT b.id, a.showdate, f.id as fail, o.id as other
FROM (
SELECT DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) as showdate
FROM `tally`
WHERE (DATE_ADD('2013-07-01 00:00:00',INTERVAL Id DAY) <= '2013-07-20 00:00:00')
ORDER BY Id ASC
) a
LEFT JOIN Bookings b ON (
MOD(DATEDIFF(b.date, a.showdate), b.repeat) = 0 AND
DATE(a.showdate)>= DATE(b.date)
)
LEFT JOIN Bookings_fail f ON (
b.id = f.booking_id AND
DATE(f.date) = DATE(a.showdate)
)
LEFT JOIN Bookings_done_by_other o ON (
b.id = o.booking_id AND
DATE(o.date) = DATE(a.showdate)
)
You start by selecting from the generated a table, and join that with the Bookings table. Once you have those two joined it's easy to join any other tables based on the a.showdate and the b.id fields.
I've created a SQL Fiddle example, more or less based on your data, so you can see how it works. http://sqlfiddle.com/#!2/7e151/1

mysql right join and conditions

In the following query I'm doing the join of a table calendar and a second table where I need to specify some parameters to the query.
The join of the tables is performed on the field calendar.datefield and store_product.created
The problem is that I'm filtering by id_store = 3 and another row in 07/20/2012 store_product table record has id_store = 4 and thus the query does not bring the result. Is there any way to show this result as NULL, forcing mysql to ignore this case?
Result: (the row containing the date 2012-07-20 is not being displayed because it has other id_store)
DATE price
2012-07-17 NULL
2012-07-18 700.00
2012-07-19 NULL
2012-07-21 NULL
2012-07-22 NULL
2012-07-23 NULL
I would like to display
DATE price
2012-07-17 NULL
2012-07-18 700.00
2012-07-19 NULL
->>> 2012-07-20 NULL
2012-07-21 NULL
2012-07-22 NULL
2012-07-23 NULL
The query:
set #id_store = 3;
set #id_product = 11;
SELECT
calendar.datefield as DATE,
t1.price
FROM
store_product t1
RIGHT JOIN
calendar ON (DATE(t1.created) = calendar.datefield)
WHERE
(calendar.datefield BETWEEN ('2012-07-17') and ('2012-07-23'))
AND (t1.id_store = #id_store OR t1.id_store is NULL)
AND (t1.id_product = #id_product OR t1.id_product is NULL)
AND (t1.created = (select
max(f2.created)
from
store_product f2
where
f2.id_store = t1.id_store
and f2.id_product = t1.id_product
and DATE(t1.created) = DATE(f2.created))
OR t1.created is NULL)
GROUP BY DATE , t1.id_store , t1.id_product
That record cannot be displayed (obviously) because you are filtering out stores only with ID of 3. The RIGHT JOIN does what it's supposed to do, then then you're further reducing results with the filter. I think you'll have to handle this in your main code to add the "missing" records for you. I don't think SQL can do what you want it to do.