I've two tables (MySQL) in my application related to tracking jobs. The first table "jobs" contains one row for each job recorded in the system. This is supported by a jobs progress table which keeps a track of the progress of a job with a new row added each time a job moves to a new state. It is also possible that a job can be re-opened again and pushed back to a previous state. Therefore the job_progress table could have multiple entries for the same job state but on different date/times.
I'd like to be able to get the current state of a job by getting the max date for a particular state via the jobs_progress table. I'd also like to be able to get for each job the date that it progressed through each state. However I'm struggling to do this via SQL and would appreciate some help. See the following SQLFiddle: http://sqlfiddle.com/#!2/8b27f/4
Here's what I currently have:
SELECT j.*
, jp.job_state
, jp.effective_date
, jp.user_id
FROM jobs j
LEFT
JOIN jobs_progress jp
ON jp.job_id = j.id;
+----+-----------+-------------+-----------+-----------------------+-----------+---------------+---------------+---------------------+---------+
| id | agency_id | entity_type | entity_id | job_title | job_state | system_status | job_state | effective_date | user_id |
+----+-----------+-------------+-----------+-----------------------+-----------+---------------+---------------+---------------------+---------+
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | NEW | 2014-07-08 12:27:54 | 102 |
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | APPROVED | 2014-07-08 12:28:02 | 102 |
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | ASSIGNED | 2014-07-08 12:29:02 | 102 |
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | WORK_COMPLETE | 2014-07-08 12:29:11 | 102 |
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | INVOICED | 2014-07-08 12:29:27 | 102 |
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | ASSIGNED | 2014-08-21 12:29:02 | 103 |
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | WORK_COMPLETE | 2014-08-30 12:29:11 | 103 |
| 1 | 123 | PROPERTY | 61 | set of keys to be cut | INVOICED | ACTIVE | INVOICED | 2014-09-01 12:29:27 | 103 |
+----+-----------+-------------+-----------+-----------------------+-----------+---------------+---------------+---------------------+---------+
Here's what I want:
+----+-----------+-----------------------+---------------------+--------------------------+--------------------------+--------------------------------+-------------------------+
| id | agency_id | job_title | raised_date (NEW) | approved_date (APPROVED) | assigned_date (ASSIGNED) | completed_date (WORK_COMPLETE) | invoiced_date (INVOICED)|
+----+-----------+-----------------------+---------------------+--------------------------+--------------------------+--------------------------------+-------------------------+
| 1 | 123 | set of keys to be cut | 2014-07-08 12:27:54 | 2014-07-08 12:28:02 | 2014-08-21 12:29:02 | 2014-08-30 12:29:11 | 2014-09-01 12:29:27 |
+----+-----------+-----------------------+---------------------+--------------------------+--------------------------+--------------------------------+-------------------------+
And here's what I tried:
This will show you the maximum date for each status, so should contain everything you want?
select
j.id,
j.agency_id,
j.entity_type,
j.entity_id,
j.job_title,
j.system_status,
jp.jobstate2 job_state,
jp.effectivedate
from jobs j
inner join (select job_id,job_state jobstate2,max(effective_date) effectivedate
from jobs_progress
group by job_id,job_state) jp
on jp.job_id = j.id
order by effectivedate desc
EDIT: Following some more requirements being added
It looks liek you're after a PIVOTed output. As far as I know, there isn;t an easy way to do this in MySQL, but you could try this, which isn't pretty, but does produce the result you're after:
select
j.id,
j.agency_id,
j.entity_type,
j.entity_id,
j.job_title,
j.system_status,
j.job_state,
jp_new.effectivedate raised_date,
jp_approved.effectivedate approved_date,
jp_assigned.effectivedate assigned_date,
jp_complete.effectivedate complete_date,
jp_invoiced.effectivedate invoice_date
from jobs j
inner join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'NEW'
group by job_id,job_state) jp_new
on jp_new.job_id = j.id
inner join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'APPROVED'
group by job_id,job_state) jp_approved
on jp_approved.job_id = j.id
inner join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'ASSIGNED'
group by job_id,job_state) jp_assigned
on jp_assigned.job_id = j.id
inner join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'WORK_COMPLETE'
group by job_id,job_state) jp_complete
on jp_complete.job_id = j.id
inner join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'INVOICED'
group by job_id,job_state) jp_invoiced
on jp_invoiced.job_id = j.id
EDIT: Following some more requirements being added
If you want to show jobs that haven't been through all of the stages, then use LEFT OUTER JOIN instead of INNER JOIN:
select
j.id,
j.agency_id,
j.entity_type,
j.entity_id,
j.job_title,
j.system_status,
j.job_state,
jp_new.effectivedate raised_date,
jp_approved.effectivedate approved_date,
jp_assigned.effectivedate assigned_date,
jp_complete.effectivedate complete_date,
jp_invoiced.effectivedate invoice_date
from jobs j
left outer join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'NEW'
group by job_id,job_state) jp_new
on jp_new.job_id = j.id
left outer join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'APPROVED'
group by job_id,job_state) jp_approved
on jp_approved.job_id = j.id
left outer join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'ASSIGNED'
group by job_id,job_state) jp_assigned
on jp_assigned.job_id = j.id
left outer join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'WORK_COMPLETE'
group by job_id,job_state) jp_complete
on jp_complete.job_id = j.id
left outer join (select job_id,max(effective_date) effectivedate
from jobs_progress
where job_state = 'INVOICED'
group by job_id,job_state) jp_invoiced
on jp_invoiced.job_id = j.id
This will get you the last date a row was invoiced. If you want all the transition dates, how do you want that displayed?
select jobs.*, LastEntry.effective_date
from jobs
INNER JOIN (SELECT job_id, MAX(effective_date) AS effective_date FROM jobs_progress WHERE job_state = 'INVOICED' GROUP BY job_id) AS LastEntry ON jobs.id = LastEntry.job_id
This will only show jobs that have been invoiced. If you want to include jobs that have not been invoiced, then use a LEFT OUTER JOIN on the derived table instead of an inner join
You can get job with latest effective_date without sub query. Joins will perform fast than sub queries when you are working with more data.
--> latest job
SELECT jb.id,
jb_pr1.job_state,
jb_pr1.effective_date
FROM
jobs jb
INNER JOIN jobs_progress jb_pr1 on jb_pr1.job_id = jb.id
LEFT JOIN jobs_progress jb_pr2
ON (jb_pr1.job_id = jb_pr2.job_id AND jb_pr1.effective_date < jb_pr2.effective_date)
WHERE jb_pr2.effective_date IS NULL;
SQL Fiddle Link : http://sqlfiddle.com/#!2/8b27f/41
Related
I'm stuck on this for hours, I'm trying to COUNT how many subscribers are there in Group A, Group B, Group C for this particular query:
SELECT rh.id_subscriber, rh.bill_month, rh.bill_year,
(
SELECT tbl_gen_info.gen_data_03
FROM tbl_subscriber
LEFT JOIN tbl_gen_info ON tbl_subscriber.bill_area_code = tbl_gen_info.gen_data_01
WHERE rh.id_subscriber = tbl_subscriber.id_subscriber
) AS group_area
FROM tbl_reading_head AS rh
WHERE rh.id_soa_head IS NULL
AND rh.read_status <> 'Beginning'
AND rh.rec_status = 'active'
ORDER BY rh.id_subscriber
The sub-query gets the Group area gen_data_03 from tbl_gen_info
Tables contain this information:
tbl_gen_info
--------------------------------------------
| gen_category | gen_data_01 | gen_data_03 |
--------------------------------------------
| Area Code | Camacho St. | Group A |
--------------------------------------------
tbl_subscriber
----------------------------------
| id_subscriber | bill_area_code |
----------------------------------
| 1 | Camacho St. |
----------------------------------
tbl_reading_head
----------------------------------------------------------------------
| id_subscriber | id_soa_head | read_status | bill_month | bill_year |
----------------------------------------------------------------------
| 1 | NULL | Metered | 10 | 2017 |
----------------------------------------------------------------------
Notice that each id_subscriber has two (2) rows (one for electric, one for water). After grouping by id_subscriber:
GROUP BY rh.id_subscriber
I got this:
I tried adding COUNT before the sub-query making it:
COUNT(SELECT tbl_gen_info.gen_data_03 ...) AS group_area
but that doesn't work.
Use a subquery:
SELECT rh.group_area, COUNT(*)
FROM (SELECT rh.id_subscriber, rh.bill_month, rh.bill_year,
(SELECT tbl_gen_info.gen_data_03
FROM tbl_subscriber LEFT JOIN
tbl_gen_info
ON tbl_subscriber.bill_area_code = tbl_gen_info.gen_data_01
WHERE rh.id_subscriber = tbl_subscriber.id_subscriber
) as group_area
FROM tbl_reading_head rh
WHERE rh.id_soa_head IS NULL AND
rh.read_status <> 'Beginning' AND
rh.rec_status = 'active'
) rh
GROUP BY rh.group_area;
I have two tables:
Employees
id | fullName | birth | speciality
1 | A A A | 01/01/1980 | Manager
2 | B B B | 01/01/1980 | Developer
3 | C C C | 01/01/1980 | User
EmployeesStatus
ID | status | dateChange
1 | 1 | 01/01/2010
2 | 1 | 01/01/2013
3 | 1 | 01/01/2015
3 | 2 | 01/01/2016
and I want to seletect the following data
ID | Full name | Bith date | speciality | Date hired | Date fired
Result has to be:
ID | Full name | Bith date | speciality | Date hired | Date fired
1 | A A A | 01/01/1980 | Manager | 01/01/2010 | null
2 | B B B | 01/01/1980 | Developer | 01/01/2013 | null
3 | C C C | 01/01/1980 | User | 01/01/2015 | 01/01/2016
3 | C C C |01/01/1980 | User | 01/01/2017 | null
my code:
SELECT Employees.id , Employees.fullName, Employees.birth, Employees.speciality,
(SELECT dateChange FROM EmployeesStatus WHERE status=1 AND id=Employees.id) datehired,
(SELECT dateChange FROM EmployeesStatus WHERE status=2 AND id=Employees.id) datefired FROM Employees
hase as result the following message:
Msg 512, Level 16, State 1, Line 1 Subquery returned more than 1
value. This is not permitted when the subquery follows =, !=, <, <= ,
, >= or when the subquery is used as an expression.
Any thoughts?
You should use a join instead of an = based on subquery
SELECT
Employees.id
, Employees.fullName
, Employees.birth
, Employees.speciality
, e1.dateChange as datehired, e2.dateChange as datefired
FROM Employees
INNER JOIN EmployeesStatus es1 on e1.status=1 AND e1.id=Employees.id
LEFT JOIN EmployeesStatus es2 on e2.status=2 AND e2.id=Employees.id
Or you could use in clause instead of = on subquery
In addition to #scaisEdge answer:
SELECT
Employees.id,
Employees.fullName,
Employees.birth,
Employees.speciality
e1.dateChange as datehired,
MIN(e2.dateChange) as datefired
FROM Employees
INNER JOIN EmployeesStatus es1 on e1.status=1 AND e1.id=Employees.id
LEFT JOIN EmployeesStatus es2 on e2.status=2 AND e2.id=Employees.id
AND e2.dateChange > e1.dateChange
GROUP BY Employees.id,
Employees.fullName,
Employees.birth,
Employees.speciality,
e1.dateChange
Try following:
SELECT E.ID,E.FULLNAME,E.BIRTH,E.SPECIALITY,ED.DATE_HIRED,ED.DATE_FIRED
FROM EMPLOYEES E,
(SELECT ID,
MAX(CASE WHEN STATUS=1 THEN DATECHANGE ELSE NULL END)DATE_HIRED,
MAX(CASE WHEN STATUS=2 THEN DATECHANGE ELSE NULL END)DATE_FIRED
FROM EMPLOYEESSTATUS
GROUP BY ID)ED
WHERE E.ID=ED.ID
This query will be faster in term of performance and will give you same result.
try this
SELECT e.id ,
e.fullName,
e.birth,
e.speciality,
CASE WHEN t1.status = 1 then max(t1.dateChange) else null end as "Date Hired",
CASE WHEN t2.status = 2 then max(t2.dateChange) else null end as "Date Fired",
FROM Employees e
LEFT JOIN EmployeesStatus t1 on t1.id = e.id and t1.status = 1
LEFT JOIN EmployeesStatus t2 on t2.id = e.id and t1.status = 2
Hope this works..
I'm trying to find number of attendees who have actually joined the sessions of the event. I've 5 tables.
event -> day -> session
attendee
session_attendee
Schema
+------------------------------+
| attendee |
+------------------------------+
| id username password |
| 1 user1 test |
| 2 user2 test |
| ------ |
| event |
| id name |
| 1 event 1 |
| 2 event 2 |
| ----- |
| day |
| id date event_id |
| 1 '2015-06-01' 1 |
| 2 '2015-06-02' 1 |
| 3 '2015-07-01' 2 |
| 4 '2015-07-02' 2 |
| ------ |
| session |
| id name day_id |
| 1 session a 1 |
| 2 session b 1 |
| 3 session c 2 |
| 4 session d 2 |
| ------ |
| session_attendee |
| id session_id attendee_id |
| 1 1 1 |
| 2 2 1 |
| 3 1 2 |
| 4 2 2 |
+------------------------------+
Expectation
+-----------------------------+
| id name attendees |
+-----------------------------+
| 1 'event 1' 2 |
| 2 'event 2' 0/null etc. |
+-----------------------------+
Subquery
SQL query I've tried using Joins but it's returning more than one record (i.e. I can't use it as a subquery)
SELECT count(*) FROM event
INNER JOIN day ON event.id = day.event_id
INNER JOIN session ON day.id = session.day_id
INNER JOIN session_attendee ON session.id = session_attendee.session_id
WHERE event.id = 1
GROUP BY attendee_id
Parent Query
with above subquery substituted
SELECT id, name,
(SELECT COUNT(*) FROM day WHERE day.event_id = event.id) AS days,
(SELECT COUNT(*) FROM event
INNER JOIN day ON event.id = day.event_id
INNER JOIN session ON day.id = session.day_id
INNER JOIN session_attendee ON session.id = session_attendee.session_id
GROUP BY session_attendee.attendee_id) AS attendees
FROM event;
SQL Fiddle
http://sqlfiddle.com/#!9/72ffc
More Work:
SELECT event.id, event.name,
(SELECT count(*) FROM event e INNER JOIN day AS d ON e.id = d.event_id WHERE e.id = event.id) AS total_days,
day.date, session.name, session_attendee.attendee_id
FROM event
LEFT JOIN day ON event.id = day.event_id
LEFT JOIN session ON day.id = session.day_id
LEFT JOIN session_attendee ON session.id = session_attendee.session_id
ORDER BY event.id;
-- COUNT(distinct attendee_id) AS attendees
As soon as I enter COUNT(distinct attendee_id), my result set gets reduced to one row, i.e. I don't get to see records for other events.
Your first query looks basically right with one exception: if you want information for an event, you should aggregate by the event id, not the attendee id:
SELECT e.id, e.name, count(*)
FROM event e INNER JOIN
day d
ON e.id = d.event_id INNER JOIN
session s
ON d.id = s.day_id INNER JOIN
session_attendee sa
ON s.id = sa.session_id
GROUP BY e.id;
Of course, if you want information for only one event, you can replace the GROUP BY with:
WHERE e.id = 1
EDIT:
You want a LEFT JOIN to keep all the events:
SELECT e.id, e.name, count(distinct sa.attendee_id)
FROM event e LEFT JOIN
day d
ON e.id = d.event_id LEFT JOIN
session s
ON d.id = s.day_id LEFT JOIN
session_attendee sa
ON s.id = sa.session_id
GROUP BY e.id;
Try this
SELECT event.id, name,
(SELECT COUNT(*) FROM day WHERE day.event_id = event.id) AS days,
b.number_of_attendee
FROM event
left join (
SELECT a.id, count(*) as number_of_attendee from
(
SELECT event.id, attendee_id FROM event
INNER JOIN day ON event.id = day.event_id
INNER JOIN session ON day.id = session.day_id
INNER JOIN session_attendee ON session.id = session_attendee.session_id
group by event.id, attendee_id
) as a
group by a.id) as b
on b.id = event.id
which has the result like
id name days number_of_attendee
1 Event 1 2 2
2 Event 2 2 (null)
I want to make a report of time entry of particular projects. I tried below query.
Table1: Projects
id | Name
------------
1 | A
2 | B
Table2: EmployeeTimeEntry
proj | activity |time
----------------------
1 | coding | 5
2 | coding | 2
1 | testing | 2
1 | coding | 2
My desired Outpput for proj A:
proj | TotalDur | activity | Activitytime
--------------------------------------------
A | 9 | coding | 7
A | 9 | testing | 2
My Query :
$query = "SELECT
name as 'Proj',
TimeEntry.Total as 'TotalDur',
ATimeEntry.ADetails as 'activity',
ATimeEntry.ATotal as 'Activitytime'
FROM Projects pr
INNER JOIN(SELECT project,SUM(time) as Total from EmployeeTimeEntry group by project ) TimeEntry on pr.id = TimeEntry.project
INNER JOIN(SELECT project,details as ADetails,SUM(time) as ATotal from EmployeeTimeEntry where id = pr.id group by details ) ATimeEntry on pr.id = TimeEntry.project";
But i got output as
proj | TotalDur | activity | Activitytime
--------------------------------------------
A | 9 | coding | 9
A | 9 | testing | 2
All activity times for all projects get added .
I use combobo to select which projects to show the report.
I think you are over complicating it
select
p.name as Proj,
x.TotalDur,
et.activity,
sum(et.time) as Activitytime
from Projects p
join (
select proj, sum(time) as TotalDur from EmployeeTimeEntry group by proj
)x on x.proj = p.id
join EmployeeTimeEntry et on et.proj = p.id
where p.name = 'A'
group by p.name,et.activity
DEMO
Maybe this is what you want?
select
p.Name as Proj,
(select sum(time) as TotalDur from EmployeeTimeEntry where proj = p.id group by proj) TotalDur,
activity,
sum(e.time) as ActivityTime
from Projects p
inner join EmployeeTimeEntry e on e.proj = p.id
where p.Name = 'A'
group by name, activity, p.id
Sample SQL Fiddle
need some help to build a query, this is my current scheme:
users:
+----+------------+
| id | username |
+----+------------+
| 1 | rob |
| 2 | john |
| 3 | jane | <--- jane never has donated
| 4 | mike |
+----+------------+
donations:
+--------------------+------------+
| uid | amount | date |
+---------+----------+------------+
| 1 | 20 | 2013-10-10 |
| 2 | 5 | 2013-10-03 |
| 2 | 50 | 2013-09-25 |
| 2 | 5 | 2013-10-01 |
| 4 | 100 | 2012-10-01 | <-- past year
+---------+----------+------------+
Result I want:
+---------+-------------+---------+-------------+---------------+----------+
| id | username | amount | monthly | totalamount | total |
+---------+-------------+---------+-------------+ --------------+----------+
| 1 | rob | 20 | 1 | 20 | 1 |
| 2 | john | 60 | 3 | 60 | 3 |
| 3 | jane | 0 | 0 | 0 | 0 |
| 4 | mike | 0 | 0 | 100 | 1 |
+---------+-------------+-----------------------+---------------+----------+
This is my query:
SELECT
u.*,
COALESCE(sum(d.amount), 0) amount,
COUNT(d.uid) monthly,
COUNT(d.amount) as Total, <-- need to get sum all time donations and number of times donated
FROM users u
LEFT JOIN donations d
ON u.id = d.uid
AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
GROUP BY u.id ORDER BY u.id ASC
So i need to add 2 different sums from same data.
EDIT: http://sqlfiddle.com/#!2/20a974/9 schema and data
How I can do this?
For this we need to filter the data on the select and not on the join.
Remove this condition:
AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
and add this to the select:
SUM (CASE WHEN (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE())) THEN 1 ELSE 0 END) as monthly
Edit:
whole query:
SELECT users.id, users.username,
COALESCE(sum(CASE WHEN (month(donations.date), year(donations.date)) = (month(CURDATE()), year(CURDATE())) THEN donations.amount ELSE 0 END), 0) monthly_sum,
COALESCE(sum(CASE WHEN (month(donations.date), year(donations.date)) = (month(CURDATE()), year(CURDATE())) THEN 1 ELSE 0 END), 0) monthly_amount,
COALESCE(sum(donations.amount), 0) total_sum,
count(*) total_amount
from users
left join donations
on donations.uid = users.id
group by users.id, users.username
http://sqlfiddle.com/#!2/20a974/20/0
For me the easiest way to think about the separately grouped information is to put it into separate queries and then just join the results back together. This is not likely to be the most efficient, but it helps to get something working.
select auo.id, auo.username,
coalesce(monthly_count, 0), coalesce(monthly_total, 0),
coalesce(total, 0), coalesce(total_amount, 0)
from aaa_users auo
left join (
select au.id as id, count(adm.amount) as monthly_count, SUM(adm.amount) as monthly_total
from aaa_users au join aaa_donations adm on au.id = adm.uid and adm.donate_date > GETDATE()-30
group by au.id
) as monthly on monthly.id = auo.id
left join (
select au.id as id, count(ady.amount) total, SUM(ady.amount) as total_amount
from aaa_users au join aaa_donations ady on au.id = ady.uid and ady.donate_date > getDate()-450
group by au.id
) as yearly on yearly.id = auo.id
As #CompuChip said, it's cleaner to just join to the donations table twice, but I have something wrong in my join logic as the values for john are getting duplicated. I think there would need to be a donations.id column to prevent the monthly and total donations from being combined. Anyway, here's an example even though it isn't working correctly
select au.id, au.username,
count(adm.amount), SUM(adm.amount) as monthly_total,
count(ady.amount), SUM(ady.amount) as total_amount
from aaa_users au
left outer join aaa_donations adm on au.id = adm.uid and adm.donate_date > GETDATE()-60
left outer join aaa_donations ady on au.id = ady.uid and ady.donate_date > getDate()-450
group by au.id, au.username
order by au.id, au.username
You can do another join to donations, giving it a different alias: LEFT JOIN donations d2 on d2.uid = u.id. Then sum over d2.amount for the last two fields, e.g.
SELECT u.*,
COALESCE(sum(d.amount), 0) amount,
COUNT(d.uid) monthly,
COUNT(d.amount) as Total,
COALESCE(sum(d2.amount), 0) amountAll,
COUNT(d2.uid) monthlyAll,
COUNT(d2.amount) as TotalAll
FROM users u
LEFT JOIN donations d ON u.id = d.uid AND (month(d.date), year(d.date)) = (month(CURDATE()), year(CURDATE()))
LEFT JOIN donations d2 ON u.id = d2.uid
GROUP BY u.id ORDER BY u.id ASC