SQL: Comparing two tables for missing records and then on the date fields - sql

I have two tables as below
work_assignments
emp_id | start_date | End Date
------------------------------------------
1 | May-10-2017 | May-30-2017
1 | Jun-05-2017 | null
2 | May-08-2017 | null
hourly_pay
emp_id | start_date | End Date | Rate
-----------------------------------------------
1 | May-20-2017 | Jun-30-2017 | 75
1 | Jul-01-2017 | null | 80
These 2 tables share the emp_id (employee id) foreign key and joining these two I should be able to:
find employee records missing in the hourly_pay table.
Given the data here, the query should return emp_id 2 from work_assignments table
find the records where the hourly_pay start_date that are later than the work assignments start_date. Again, given the data here, the query should return emp_id 1 (because work_assignments.start_date has May-10-2017, while the earliest hourly_pay.start_date is on May-20-2017)
I am able to achieve the first part of result using the join query below
select distinct emp_id from work_contracts
left join hourly_pay hr USING(emp_id)
where hr.emp_id is null
I am stuck on the second part where probably I need a correlated subquery to tell the hourly pay table records that did not start before the work_assignments start_date? or is there any other way?

Do the date comparison in an inner query then wrap it to filter it to the ones that satisfy the late pay criteria.
select * from (
select distinct c.emp_id,
case when c.start_date < hr.start_date then 1 else 0 end as latePay
from work_contracts c
left join hourly_pay hr USING(emp_id)
) result
where latePay = 1

You can achieve second part using query
select distinct wc.emp_id
from (select emp_id, min(start_date) start_date from work_contracts group by emp_id) wc
join (select emp_id, min(start_date) start_date from hourly_pay group by emp_id) hr
on wc.emp_id = hr.emp_id
where wc.start_date < hr.start_date

This hints at a between condition, with some twists, but I've had extremely bad luck using betweens in joins. They appear to perform some form of cross-join on the back and end then filter out the actual join where-clause style. I know that's not very technical, but I've never done a non-equality condition in a join that's turned out well.
So, this may seem counter-intuitive, but I think exploding all date possibilities might actually be your best bet. Without knowing how big your date ranges actually are it's hard to say.
Also, I think this will actually satisfy both conditions in your question at once -- by telling you all work assignments that do not have corresponding pay rates.
Try this against your actual data and see how it works (and how long it takes).
with pay_dates as (
select
emp_id, rate,
generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as pd
from hourly_pay
),
assignment_dates as (
select
emp_id, start_date,
generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as wd
from work_assignments
)
select
emp_id, min (wd)::date as from_date,
max (wd)::date as thru_date
from
assignment_dates a
where
not exists (
select null
from pay_dates p
where p.emp_id = a.emp_id
and a.wd = p.pd
)
group by
emp_id, start_date
The results should be all work assignment ranges with no rates:
emp from thru
1 '2017-05-10' '2017-05-19'
2 '2017-05-08' '2017-11-14'
The cool thing is it would also remove any overlaps, where a work assignment was partially covered.
-- Edit 3/20/2018 --
Per your request, here is a break-down of what the logic does.
with pay_dates as(
select
emp_id, rate,
generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as pd
from hourly_pay
)
This takes the hourly_pay data and breaks it into a record for each employee, for each day:
emp_id rate pay date
1 75 5/20/17
1 75 5/21/17
1 75 5/22/17
...
1 75 6/30/17
1 80 6/01/17
1 80 6/02/17
...
1 80 today
Next,
[implied "with"]
assignment_dates as (
select
emp_id, start_date,
generate_series (start_date, coalesce (end_date, current_date), interval '1 day') as wd
from work_assignments
)
Effectively does the same thing for the work assignments table, only preserving the "start date column" in each row.
Then the main query is this:
select
emp_id, min (wd)::date as from_date,
max (wd)::date as thru_date
from
assignment_dates a
where
not exists (
select null
from pay_dates p
where p.emp_id = a.emp_id
and a.wd = p.pd
)
group by
emp_id, start_date
Which draws from the two queries above. The important part is the anti-join:
not exists (
select null
from pay_dates p
where p.emp_id = a.emp_id
and a.wd = p.pd
)
That identifies every work assignment where there is no corresponding record for that employee, for that day.
So in essence, the query takes the data ranges from both tables, comes up with every possible date combination and then does an anti-join to see where they don't match.
While it seems counterintuitive, to take a single record and blow it up into multiple records, two things to consider:
Dates are very bounded creatures -- even in 10 years worth of data that only constitutes 4,000 or so records, which isn't much to a database, even when multiplied by an employee database. Your time frame looks much less than that.
I've had very, VERY bad luck using joins other than =, for example between or >. It seems in the background it does cartesians and then filters the results. By comparison, exploding the ranges at least gives you some control over how much data explosion occurs.
For grins, I did it with your sample data above and came up with this, which actually looks accurate:
1 '2017-05-10' '2017-05-19'
2 '2017-05-08' '2018-03-20'
Let me know if any of that is unclear.

You can solve this with using the daterange type (because, what you basically want is the missing ranges in hourly_pay table.).
I used the following operators in it:
+ range union
- range subtraction
&& test for range intersection
#> test for range containment
With these and a simple left join, you can write a query to find out which ranges are missing in the hourly_pay table.
select wa.emp_id, lower(dr) start_date, upper(dr) - 1 end_date
from work_assignments wa
left join hourly_pay hp on wa.emp_id = hp.emp_id
and daterange(wa.start_date, wa.end_date, '[]') && daterange(hp.start_date, hp.end_date, '[]')
cross join lateral (select case
when hp is null then daterange(wa.start_date, wa.end_date, '[]')
else daterange(wa.start_date, wa.end_date, '[]')
+ daterange(hp.start_date, hp.end_date, '[]')
- daterange(hp.start_date, hp.end_date, '[]')
end dr) dr
where not exists (select 1
from hourly_pay p
where p.emp_id = wa.emp_id
and daterange(p.start_date, p.end_date, '[]') #> dr)
-- emp_id | start_date | end_date
----------+------------+-------------
-- 1 | 2017-05-01 | 2017-05-19
-- 2 | 2017-05-08 | (null)
http://sqlfiddle.com/#!17/4bac0/14

Maybe I am a little caught up by the wording, but would this not suffice? This would return any emp_id where there is a record for which the hourly start date is after a work assignment start date
select distinct wc.emp_id from work_contracts wc
left join hourly_pay hr USING(emp_id)
where hr.start_date > wc.start_date

select distinct p.emp_id <br>
from hourly_pay p <br>
join work_assignments w on p.emp_id = w.emp_id <br>
where p.start_date < w.start_date <br>
Based on the stated requirement in the original question: find the records where the hourly_pay start_date that are later than the work assignments start_date. Again, given the data here, the query should return emp_id 1 (because work_assignments.start_date has May-10-2017, while the earliest hourly_pay.start_date is on May-20-2017)
This means to me that they only want the employee id number.

I would use not exists/exists:
select wa.empid
from work_assignments wa
where not exists (select 1 from hourly_pay hp where wa.emp_id = hp.emp_id);
and for the second:
select wa.*
from work_assignments wa
where not exists (select 1
from hourly_pay hp
where wa.emp_id = hp.emp_id and ep.start_date <= wp.start_date
);
The question is very particular on (2). However, I would expect that you would want hourly pay for the entire period of the assignment, not just the start date. If that is the case, then the OP should ask a new qustion.

Second query is very simple,
Try below query
select distinct h.emp_id
from work_assignments w inner join hourly_pay h
on
w.emp_id = h.emp_id
and h.start_date > w.start_date;

Looking at your data, I can make following assumptions:
1) There can be max one record for an employee that has end_date as null this condition applied to both tables.
2) Multiple records dates for same employee don't overlap When employee has multiple records (like Emp 1) , he/she can't have dates like [jan 1 - feb 1] and next record as [jan 15-feb 20] or [jan 15 - null] (they must be for non overlapping periods).
With these in mind, below query should work for you.
SELECT hourly_pay.*
FROM work_assignments
INNER JOIN hourly_pay USING(emp_id)
WHERE hourly_pay.start_date > work_assignments.start_date
AND ( hourly_pay.start_date < work_assignments.end_date
OR (work_assignments.end_date is null
AND hourly_pay.end_date is null) );
Explanation: The query joins both tables on emp_id then filters records that
1) Have start_date in hourly_pay > start_date in work_assignments
-AND-
2) Have start_date in hourly_pay < end_date in work_assignments (This is needed, so we can avoid comparing un-related time period records from both tables
-OR-
End dates of both table records are null, using assumption 1 (stated above)
there can be max one record for an employee that has end_date as null.
Based on your data, this query should return both records of EMP 1 in hourly_pay as start_date there is > start_date in work_assignments.
If you just need list of EMP IDs you can just select that column SELECT DISTINCT hourly_pay.emp_id ...(rest of the query)

http://sqlfiddle.com/#!17/f4595/1
Records missing in hourly_pay table;
Instead of using left join and then filtering null valued records, I suggest you to use not exists, It will work way faster.
SELECT w.emp_id, 'missing in the hourly_pay table' FROM work_assignments w
WHERE NOT exists (SELECT 1 FROM hourly_pay h WHERE h.emp_id = w.emp_id)
Records hourly_pay start_date is later than the work assignment start_date;
SELECT w.emp_id FROM work_assignments w
WHERE
NOT exist (
SELECT 1 FROM hourly_pay hp
WHERE
hp.start_date < w.start_date AND w.emp_id = hp.emp_id )
Second query actually includes the results from first query, so you can merge them like below:
SELECT
w.emp_id,
(CASE WHEN ( EXISTS
(SELECT 1 FROM hourly_pay h
WHERE
h.emp_id = w.emp_id ) )
THEN
'hourly_pay start_date is later'
ELSE
'missing in the hourly_pay table'
END)
FROM
work_assignments w
WHERE
NOT EXISTS (
SELECT
1
FROM
hourly_pay hp
WHERE
hp.start_date < w.start_date
AND w.emp_id = hp.emp_id
)

this will do the job nicely.
SELECT DISTINCT emp_id
FROM work_assingment
JOIN hourly_pay hr USING(emp_id)
WHERE hr.start_date < work_assingment.start_date;

Related

Show all results in date range replacing null records with zero

I am querying the counts of logs that appear on particular days. However on some days, no log records I'm searching for are recorded. How can I set count to 0 for these days and return a result with the full set of dates in a date range?
SELECT r.LogCreateDate, r.Docs
FROM(
SELECT SUM(TO_NUMBER(REPLACE(ld.log_detail, 'Total Documents:' , ''))) AS Docs, to_char(l.log_create_date,'YYYY-MM-DD') AS LogCreateDate
FROM iwbe_log l
LEFT JOIN iwbe_log_detail ld ON ld.log_id = l.log_id
HAVING to_char(l.log_create_date , 'YYYY-MM-DD') BETWEEN '2020-01-01' AND '2020-01-07'
GROUP BY to_char(l.log_create_date,'YYYY-MM-DD')
ORDER BY to_char(l.log_create_date,'YYYY-MM-DD') DESC
) r
ORDER BY r.logcreatedate
Current Result - Id like to include the 01, 04, 05 with 0 docs.
LOGCREATEDATE
Docs
2020-01-02
7
2020-01-03
3
2020-01-06
6
2020-01-07
1
You need a full list of dates first, then outer join the log data to that. There are several ways to generate the list of dates but now common table expressions (cte) are an ANSI standard way to do this, like so:
with cte (dt) as (
select to_date('2020-01-01','yyyy-mm-dd') as dt from dual -- start date here
union all
select dt + 1 from cte
where dt + 1 < to_date('2020-02-01','yyyy-mm-dd') -- finish (the day before) date here
)
select to_char(cte.dt,'yyyy-mm-dd') as LogCreateDate
, r.Docs
from cte
left join (
SELECT SUM(TO_NUMBER(REPLACE(ld.log_detail, 'Total Documents:' , ''))) AS Docs
, trunc(l.log_create_date) AS LogCreateDate
FROM iwbe_log l
LEFT JOIN iwbe_log_detail ld ON ld.log_id = l.log_id
HAVING trunc(l.log_create_date) BETWEEN to_date('2020-01-01','yyyy-mm-dd' AND to_date('2020-01-07','yyyy-mm-dd')
GROUP BY trunc(l.log_create_date)
) r on cte.dt = r.log_create_date
order by cte.dt
also, when dealing with dates I prefer to not convert them to strings until final output which allows you to still get proper date order and maximum query efficiency.

What is a better alternative to a "helper" table in an Oracle database?

Let's say I have an 'employees' table with employee start and end dates, like so:
employees
employee_id start_date end_date
53 '19901117' '99991231'
54 '19910208' '20010512'
55 '19910415' '20120130'
. . .
. . .
. . .
And let's say I want to get the monthly count of employees who were employed at the end of the month. So the resulting data set I'm after would look like:
month count of employees
'20150131' 120
'20150228' 118
'20150331' 122
. .
. .
. .
The best way I currently know how to do this is to create a "helper" table to join onto, such as:
helper_tbl
month
'20150131'
'20150228'
'20150331'
.
.
.
And then do a query like so:
SELECT t0b.month,
count(t0a.employee_id)
FROM employees t0a
JOIN helper_tbl t0b
ON t0b.month BETWEEN t0a.start_date AND t0a.end_date
GROUP BY t0b.month
However, this is somewhat annoying solution to me, because it means I'm having to create these little helper tables all the time and they clutter up my schema. I feel like other people must run into the same need for "helper" tables, but I'm guessing people have figured out a better way to go about this that isn't so manual. Or do you all really just keep creating "helper" tables like I do to get around these situations?
I understand this question is a bit open-ended up for stack overflow, so let me offer a more closed-ended version of the question which is, "Given just the 'employees' table, what would YOU do to get the resulting data set that I showed above?"
You can use a CTE to generate all the month values, either form a fixed starting point or based on the earliest date in your table:
with months (month) as (
select add_months(first_month, level - 1)
from (
select trunc(min(start_date), 'MM') as first_month from employees
)
connect by level <= ceil(months_between(sysdate, first_month))
)
select * from months;
With data that was an earliest start date of 1990-11-17 as in your example, that generates 333 rows:
MONTH
-------------------
1990-11-01 00:00:00
1990-12-01 00:00:00
1991-01-01 00:00:00
1991-02-01 00:00:00
1991-03-01 00:00:00
...
2018-06-01 00:00:00
2018-07-01 00:00:00
You can then use that in a query that joins to your table, something like:
with months (month) as (
select add_months(first_month, level - 1)
from (
select trunc(min(start_date), 'MM') as first_month from employees
)
connect by level <= ceil(months_between(sysdate, first_month))
)
select m.month, count(*) as employees
from months m
left join employees e
on e.start_date <= add_months(m.month, 1)
and (e.end_date is null or e.end_date >= add_months(m.month, 1))
group by m.month
order by m.month;
Presumably you wan to include people who are still employed, so you need to allow for the end date being null (unless you're using a magic end-date value for people who are still employed...)
With dates stored as string it's a bit more complicated but you can generate the month information in a similar way:
with months (month, start_date, end_date) as (
select add_months(first_month, level - 1),
to_char(add_months(first_month, level - 1), 'YYYYMMDD'),
to_char(last_day(add_months(first_month, level - 1)), 'YYYYMMDD')
from (
select trunc(min(to_date(start_date, 'YYYYMMDD')), 'MM') as first_month from employees
)
connect by level <= ceil(months_between(sysdate, first_month))
)
select m.month, m.start_date, m.end_date, count(*) as employees
from months m
left join employees e
on e.start_date <= m.end_date
and (e.end_date is null or e.end_date > m.end_date)
group by m.month, m.start_date, m.end_date
order by m.month;
Very lightly tested with a small amount of made-up data and both seem to work.
If you want to get the employees who were employed at the end of the month, then you can use the LAST_DAY function in the WHERE clause of the your query. Also, you can use that function in the GROUP BY clause of your query. So your query would be like below,
SELECT LAST_DAY(start_date), COUNT(1)
FROM employees
WHERE start_date = LAST_DAY(start_date)
GROUP BY LAST_DAY(start_date)
or if you just want to count employees employed per month then use below query,
SELECT LAST_DAY(start_date), COUNT(1)
FROM employees
GROUP BY LAST_DAY(start_date)

ORACLE How to get data from a table depending on year and the amount of duplicates

so the question is "Produce a list of those employees who have made bookings at the Sports Club more than 5 times in the last calendar year (this should be calculated and not hard coded). Order these by the number of bookings made."
what i am struggling with is being able to get todays year and then subtract 1 year aswell as showing ONLY one name of people appearing 5 times.
this is what i have so far, but it doesnt work.
SELECT DISTINCT
FIRSTNAME,SURNAME,DATEBOOKED,MEMBERSHIPTYPEID,BOOKINGID,MEMBER.MEMBERID
FROM MEMBERSHIP,MEMBER,BOOKING
WHERE MEMBER.MEMBERID = BOOKING.MEMBERID
AND MEMBERSHIP.MEMBERSHIPTYPEID = 3 AND BOOKING.DATEBOOKED = (SYSDATE,
'DD/MON/YY -1') AND FIRSTNAME IN (
SELECT FIRSTNAME
FROM MEMBER
GROUP BY FIRSTNAME,SURNAME
HAVING COUNT(FIRSTNAME) >= 5
)
ORDER BY MEMBER.MEMBERID;
EDR
tabele structures
What you need is this day last year. There are various different ways of calculating that. For instance,
add_months(sysdate, -12)
or
sysdate - interval '1' year
The subquery looks a bit whacky too. What you're after is the number of BOOKING records in the last year. So the subquery should drive off the BOOKING table, and the date filter should be there.
Finally, there is a missing join condition between MEMBER and MEMBERSHIP. That's probably why you think you need that distinct; fix the join and you'll get the result set you want and not a product. One advantage of the ANSI 92 explicit join syntax is that it stops us from missing joins.
Your query needs to be sorted by the number of bookings, so you need to count those and sort by the total. This means you don't actually need a subquery at all.
So your query should look something like:
SELECT MEMBER.MEMBERID
, MEMBER.FIRSTNAME
, MEMBER.SURNAME
, count(BOOKING.BOOKID) as no_of_bookings
FROM MEMBER
inner join MEMBERSHIP
on MEMBER.MEMBERID = MEMBERSHIP.MEMBERID
inner join BOOKING
on MEMBER.MEMBERID = BOOKING.MEMBERID
WHERE MEMBERSHIP.MEMBERSHIPTYPEID = 3
and BOOKING.DATEBOOKED >= add_months(trunc(sysdate), -12)
GROUP BY MEMBER.MEMBERID
, MEMBER.FIRSTNAME
, MEMBER.SURNAME
HAVING COUNT(*) >= 5
ORDER BY no_of_bookings desc;
Here is a SQL Fiddle demo for my query.
SELECT
FIRSTNAME,SURNAME,DATEBOOKED,MEMBERSHIPTYPEID,BOOKINGID,MEMBER.MEMBERID
FROM MEMBERSHIP,MEMBER,BOOKING
WHERE MEMBER.MEMBERID = BOOKING.MEMBERID
AND MEMBERSHIP.MEMBERSHIPTYPEID = 3 AND BOOKING.DATEBOOKED between (SYSDATE) and
(sysdate - interval '1' year)
AND FIRSTNAME IN (
SELECT distinct FIRSTNAME
FROM MEMBER
GROUP BY FIRSTNAME,SURNAME
HAVING COUNT(FIRSTNAME) >= 5
)
ORDER BY MEMBER.MEMBERID;
I think this answers the question:
SELECT m.FIRSTNAME, m.SURNAME, m.MEMBERID
FROM MEMBER m JOIN
BOOKING b
ON m.MEMBERID = b.MEMBERID JOIN
MEMBERSHIP ms
ON ms.MEMBERID = m.MEMBERID
WHERE ms.MEMBERSHIPTYPEID = 3 AND
B.DATEBOOKED >= SYSDATE - INTERVAL '1 YEAR'
GROUP BY m.FIRSTNAME, m.SURNAME, m.MEMBERID
HAVING COUNT(*) >= 5
ORDER BY COUNT(* DESC;
Table aliases make the query much easier to write and to read.

Calculating business days in Teradata

I need help in business days calculation.
I've two tables
1) One table ACTUAL_TABLE containing order date and contact date with timestamp datatypes.
2) The second table BUSINESS_DATES has each of the calendar dates listed and has a flag to indicate weekend days.
using these two tables, I need to ensure business days and not calendar days (which is the current logic) is calculated between these two fields.
My thought process was to first get a range of dates by comparing ORDER_DATE with TABLE_DATE field and then do a similar comparison of CONTACT_DATE to TABLE_DATE field. This would get me a range from the BUSINESS_DATES table which I can then use to calculate count of days, sum(Holiday_WKND_Flag) fields making the result look like:
Order# | Count(*) As DAYS | SUM(WEEKEND DATES)
100 | 25 | 8
However this only works when I use a specific order number and cant' bring all order numbers in a sub query.
My Query:
SELECT SUM(Holiday_WKND_Flag), COUNT(*) FROM
(
SELECT
* FROM
BUSINESS_DATES
WHERE BUSINESS.Business BETWEEN (SELECT ORDER_DATE FROM ACTUAL_TABLE
WHERE ORDER# = '100'
)
AND
(SELECT CONTACT_DATE FROM ACTUAL_TABLE
WHERE ORDER# = '100'
)
TEMP
Uploading the table structure for your reference.
SELECT ORDER#, SUM(Holiday_WKND_Flag), COUNT(*)
FROM business_dates bd
INNER JOIN actual_table at ON bd.table_date BETWEEN at.order_date AND at.contact_date
GROUP BY ORDER#
Instead of joining on a BETWEEN (which always results in a bad Product Join) followed by a COUNT you better assign a bussines day number to each date (in best case this is calculated only once and added as a column to your calendar table). Then it's two Equi-Joins and no aggregation needed:
WITH cte AS
(
SELECT
Cast(table_date AS DATE) AS table_date,
-- assign a consecutive number to each busines day, i.e. not increased during weekends, etc.
Sum(CASE WHEN Holiday_WKND_Flag = 1 THEN 0 ELSE 1 end)
Over (ORDER BY table_date
ROWS Unbounded Preceding) AS business_day_nbr
FROM business_dates
)
SELECT ORDER#,
Cast(t.contact_date AS DATE) - Cast(t.order_date AS DATE) AS #_of_days
b2.business_day_nbr - b1.business_day_nbr AS #_of_business_days
FROM actual_table AS t
JOIN cte AS b1
ON Cast(t.order_date AS DATE) = b1.table_date
JOIN cte AS b2
ON Cast(t.contact_date AS DATE) = b2.table_date
Btw, why are table_date and order_date timestamp instead of a date?
Porting from Oracle?
You can use this query. Hope it helps
select order#,
order_date,
contact_date,
(select count(1)
from business_dates_table
where table_date between a.order_date and a.contact_date
and holiday_wknd_flag = 0
) business_days
from actual_table a

How to select all dates in SQL query

SELECT oi.created_at, count(oi.id_order_item)
FROM order_item oi
The result is the follwoing:
2016-05-05 1562
2016-05-06 3865
2016-05-09 1
...etc
The problem is that I need information for all days even if there were no id_order_item for this date.
Expected result:
Date Quantity
2016-05-05 1562
2016-05-06 3865
2016-05-07 0
2016-05-08 0
2016-05-09 1
You can't count something that is not in the database. So you need to generate the missing dates in order to be able to "count" them.
SELECT d.dt, count(oi.id_order_item)
FROM (
select dt::date
from generate_series(
(select min(created_at) from order_item),
(select max(created_at) from order_item), interval '1' day) as x (dt)
) d
left join order_item oi on oi.created_at = d.dt
group by d.dt
order by d.dt;
The query gets the minimum and maximum date form the existing order items.
If you want the count for a specific date range you can remove the sub-selects:
SELECT d.dt, count(oi.id_order_item)
FROM (
select dt::date
from generate_series(date '2016-05-01', date '2016-05-31', interval '1' day) as x (dt)
) d
left join order_item oi on oi.created_at = d.dt
group by d.dt
order by d.dt;
SQLFiddle: http://sqlfiddle.com/#!15/49024/5
Friend, Postgresql Count function ignores Null values. It literally does not consider null values in the column you are searching. For this reason you need to include oi.created_at in a Group By clause
PostgreSql searches row by row sequentially. Because an integral part of your query is Count, and count basically stops the query for that row, your dates with null id_order_item are being ignored. If you group by oi.created_at this column will trump the count and return 0 values for you.
SELECT oi.created_at, count(oi.id_order_item)
FROM order_item oi
Group by io.created_at
From TechontheNet (my most trusted source of information):
Because you have listed one column in your SELECT statement that is not encapsulated in the count function, you must use a GROUP BY clause. The department field must, therefore, be listed in the GROUP BY section.
Some info on Count in PostgreSql
http://www.postgresqltutorial.com/postgresql-count-function/
http://www.techonthenet.com/postgresql/functions/count.php
Solution #1 You need Date Table where you stored all date data. Then do a left join depending on period.
Solution #2
WITH DateTable AS
(
SELECT DATEADD(dd, 1, CONVERT(DATETIME, GETDATE())) AS CreateDateTime, 1 AS Cnter
UNION ALL
SELECT DATEADD(dd, -1, CreateDateTime), DateTable.Cnter + 1
FROM DateTable
WHERE DateTable.Cnter + 1 <= 5
)
Generate Temporary table based on your input and then do a left Join.