Set default value to zero when count is null - sql

select rd.description||'('||rs.department_code||')' DepartmentName,
rd.code DepartmentCode,
rs.description||'('||emv.section_code||')' SectionName,
emv.section_code SectionCode,
rsg.description||'('||emv.staff_group_code||')' StaffGroupName,
emv.staff_group_code StaffGroupCode,
e.staff_id STAFFID,
e.surname || ', ' || e.given_name FullName,
hp.description Position,
het.description EmploymentType,
to_char(e.Join_Date, 'dd/MM/yyyy') JoinDate,
e.employee_no EmployeeNo,
edt.shift Type,
to_char(edt.timesheet_date,'Mon') Mth,
to_char(to_Date(edt.timesheet_date),'mm') MthNum,
nvl(count(*), 0) Days
FROM employee e,
employee_daily_timesheet edt,
employee_assignment_vw emv,
ref_section rs,
ref_department rd,
hris_position hp,
hris_employment_type het,
ref_staffgroup rsg
WHERE e.employee_no = emv.employee_no
AND edt.assignment_no = emv.assignment_no
AND to_char(timesheet_date,'yyyy')=TO_CHAR(TO_DATE('01/31/2015','MM/dd/yyyy'),'yyyy')
AND emv.section_code = rs.code
AND rs.department_code = rd.code
AND e.position_code = hp.code(+)
AND e.employment_type_code = het.code(+)
AND emv.staff_group_code = rsg.code
AND edt.shift='OFF'
GROUP BY rd.description||'('||rs.department_code||')',
rd.code,
rs.description||'('||emv.section_code||')',
emv.section_code,
rsg.description||'('||emv.staff_group_code||')',
emv.staff_group_code,
e.staff_id,
e.surname || ', ' || e.given_name,
hp.description ,
het.description ,
to_char(e.Join_Date, 'dd/MM/yyyy') ,
e.employee_no,
edt.shift,
to_char(edt.timesheet_date,'Mon'),
to_char(to_Date(edt.timesheet_date),'mm')
Im trying to count the days where the Shift is equal to 'OFF', but it doesnt display records when count is zero or when a month doesnt have an 'OFF' shift. How can I set the value to zero when count is null or when the month doesnt have an 'OFF' shift.

I can't understand your query without knowing the data model (and why in 2016 are you using the ancient (+) syntax for outer joins?!) but the general principle is simple. A group by query only returns rows for groups that have some data. For example:
select deptno, count(*)
from emp
group by deptno;
This will only returns rows for departments that have at least one employee. But you say "I want to see all departments, even if they have no employees". Well, the EMP table doesn't contain all the departments, but table DEPT does. So we can use an outer join (I'll use ANSI syntax) like this:
select d.deptno, count(e.empno)
from dept d
left join emp e on e.deptno = d.deptno
group by d.deptno;
Note that the driving table has changed from EMP, which doesn't have all the deptno values, to DEPT which does. If there are no employees for deptno=60 then the query will still return a row for that DEPT, thanks to the outer join.
Note also that I have used count(e.empno) not count(*), because I am trying to count employees in the department, not rows returned by the query prior to grouping. If I used count(*) then deptno=60 would return a count of 1 (because there is 1 DEPT row with deptno=60, outer joined to 0 EMP rows, resulting in one result).
If you understand this principle and your data model then you should be able to write an analogous query for your case.

Related

Recursive SQL query for finding matches

I have 5 SQL Tables with the following columns:
tbl_department:
department_id, parent_id
tbl_employee
employee_id, department_id
tbl_department_manager
department_id, employee_manager_id
tbl_request_regular_employee
request_id, employee_id
tbl_request_special_employee
request_id, employee_id
As input data I have employee_id and request_id.
I need to figure out whether the employee has access to the request (whether he's a manager or not)
We cannot use ORM here since app's responsiveness is our priority and the script might be called a lot.
Here's the logic I want to implement:
First we query to tbl_department_manager based on employee_id to check whether the current employee is a manager or not (also the employee can be a manager in a few departments). If so, we get a list of department_id (if nothing is found, just return false)
If we got at least one id in tbl_department_manager we query to tbl_request_regular_employee AND tbl_request_special_employee based on request_id and get employee_id from both tables (they are the same)
Based on employee_id collected above we query to tbl_employee to get a unique list of department_id that the employee belongs to.
Finally have a list of unique department_id from p.3 which we can compare to the one (ones) that we got in p.1.
The catch is, however, that in tbl_department there might be departments which inherit from the one (ones) that we got from p.1 (so we might need to find it recursively based on parent_id until we find at least one match with one element from p.1). If there's at least one match between one element in p.1 and one element in p.3 return true. So there's a need to look for it recursively.
Could someone give a clue how to implement it in MSSQL? Any help would be greatly appreciated.
declare #employee_id int, #request_id int;
with reqEmployees as (
select regular_employee_id as employee_id
from tbl_request_regular_employee
where request_id = #request_id
union all --concatenate the two tables
select special_employee_id
from tbl_request_special_employee
where request_id = #request_id
),
cte as (
select e.department_id, null as parent_id
from reqEmployees r
join tbl_employee e on e.employee_id = r.employee_id -- get these employees' departments
union all
select d.department_id, d.parent_id
from cte -- recurse the cte
join tbl_department d on d.department_id = cte.parent_id -- and get parent departments
)
-- we only want to know if there is any manager row, so exists is enough
select case when exists (select 1
from cte --join on managers
join tbl_department_manager dm on dm.department_id = cte.department_id
where dm.employee_manager_id = #employee_id)
then 1 else 0 end;

How to keep a bucket using case statement even if the count for items in that bucket is 0?

Here's the data table named "Salary_table" that i've created for this question:
So I want to find the number of employees in each salary bucket in each department. the buckets are
"<$100" "$100-$200" and ">$200"
The desired output is:
Below is my code for achieving this task:
select distinct(st.department) as "Department",
sb.salary_bucket as "salary range", count(*)
from Salary_table st
Left join (
select department, employee, case
when salary < 100 then "<$100"
when salary between 100 and 200 then "$100-$200"
else ">$200"
end
as salary_bucket
from Salary_table
) sb
on sb.employee = st.employee
group by st.department, sb.salary_bucket
order by st.department, sb.salary_bucket
;
but my output is a bit short of what im expecting:
There are TWO problems with my current output:
The buckets with 0 employees earning the salary in the bucket range are not listed; I want it to be listed with a value "0"
The salary bucket is NOT in the right order, even though I added in the statement "order by" but I think it's b/c its texts so can't really do that.
I would really appreciate some hints and pointers on how to fix/achieve these two issues I've addressed above. Thank you so much!
what i've tried
I tried use "left join" but output came out the same
I tried adding the "order by" clause but doesnt seem to work on text buckets
You are sort of on the right track, but the idea is a bit more complicated. Use a cross join to get all the rows -- the buckets and departments. Then use left join to bring in the matching information and finally group by for the aggregation:
select d.department, b.salary_bucket,
count(sb.department) as cnt
from (select '<$100' as salary_bucket union all
select '$100-$200' union all
select '>$200'
) b cross join
(select distinct department from salary_table
) d left join
(select department, employee,
(case when salary < 100 then '<$100'
when salary between 100 and 200 then '$100-$200'
else '>$200'
end) as salary_bucket
from Salary_table
) sb
on sb.department = d.department and
sb.salary_bucket = b.salary_bucket
group by d.department, b.salary_bucket;

SQL query within SQL query on sum

I have two tables, Employees and EmployeeVacations. I am trying to do a SQL query to get the sum of how much vacation time each employee has taken and their current balance as of today. Here is my current SQL query:
SELECT
e.PIN,
e.FirstName,
e.LastName,
e.Uniform,
e.AL_Cap,
ev.Value AS '10/1 Balance',
(SELECT
SUM(value)
FROM EmployeeVacations
WHERE CreationDate >= '2016-10-01'
AND Vacation_Type = 'Taken'
AND Vacation_Kind = 'AL'
AND EmployeeId = 13)
AS Taken
FROM employees e,
EmployeeVacations ev
WHERE e.Id = ev.EmployeeId
AND ev.IsHistory = 0
AND ev.Vacation_Type = 'Forward'
AND ev.Vacation_Kind = 'AL'
AND EmployeeId = 13
ORDER BY e.LastName, e.FirstName
This works if I pick a single employee. If I remove the "where EmployeeId = 13", I get a list of all the employees with the sum of everyone's total vacation in every row (like 1,300 hours). How do I break it down so it only shows the Taken for each employee specifically?
You need a corelated query, where the subquery uses a value from the "parent" query.
SELECT e.PIN ...
(select SUM(value) .... WHERE EmployeeID = e.id) as taken
^^^^^
Note that these can be very inefficient, since the inner query has to be executed once for every row of the parent query. IN a lot of cases, you may be better off re-writing as a conventional JOIN query with appropriate grouping.
Just guessing that you also might want the sum rather than the single forward records... Here is a query that aggregates EmployeeVacations per EmployeeId:
select
e.pin,
e.firstname,
e.lastname,
e.uniform,
e.al_cap,
ev.forward_sum as "10/1 balance",
ev.taken_sum as taken
from employee e
left join
(
select
employeeid,
sum(case when vacation_type = 'Forward'
and ishistory = 0 then value else 0 end) as forward_sum,
sum(case when vacation_type = 'Taken'
and creationdate >= '20161001' then value else 0 end) as taken_sum,
from employeevacations
where vacation_kind = 'AL'
group by employeeid
) ev on ev.employeeid = e.employeeid
order by e.lastname, e.firstname;
Please ...
use explicit joins instead of the pre-1992 comma-separated joins for readability and for being less prone to errors.
use double quotes for alias names; single quotes are for string literals.
use 'yyyymmdd' for dates; it is the supported date literal format in SQL Server.

How to query on another query result?

I have a problem as I am not so strong on queries.
I have a query with consists of a union of two select queries :
SELECT em.emp_code,
em.emp_name,
COALESCE(SUM(pe.hours_allotted),0) AS hours,
pe.dated
FROM employee_master em
LEFT JOIN project_employee pe ON (pe.Emp_code = em.emp_code)
WHERE (dated >= '2011-03-14'
AND dated < '2011-03-20' )
OR dated IS NULL
GROUP BY em.emp_code
UNION
(SELECT em.emp_code,
em.emp_name,
'0' AS hours,
pe.dated
FROM employee_master em
LEFT JOIN project_employee pe ON (pe.Emp_code = em.emp_code)
WHERE (dated >= '2011-03-14'
AND dated < '2011-03-20' )
OR dated IS NOT NULL
GROUP BY em.Emp_code)
ORDER BY emp_name;
Now the result sets are returning for example as:
ecode ename hours
----------------------
201 Alak basu 10
201 alak basu 0
The first result is from first select statement of the union where hours = 10
and hours = 0 is from second select statement of union.
What I want is:
ecode ename hours
----------------------------
201 alak basu 10
Like in the case there should be only one result per ecode. How to group it like summing up the hours on as group by ecode so that it gives me only one result as above?
You can always do something like:
select emp_code, min(emp_name) as emp_name, sum(hours)
from (
<your original query here>
) as e
group by emp_code
order by emp_name;
If the desired result is to sum all hours for a single employee code into a single row, and the second query after the UNION will only ever return zero hours, it seems like the best solution here is to get rid of the UNION.
EDIT: After further clarification, here's what I think the SQL should probably look like:
SELECT em.emp_code,
em.emp_name,
COALESCE(pe.hours, 0) AS hours
FROM employee_master em
LEFT JOIN (
SELECT emp_code,
SUM(hours_allotted) AS hours
FROM project_employee
WHERE dated >= '2011-03-14' AND
dated < '2011-03-20'
GROUP BY emp_code
) pe ON (pe.emp_code = em.emp_code)
ORDER BY em.emp_name;
What it's doing:
Perform a subquery to filter all project_employee entries to the ones within the specified date range. (Note that there is no need for NULL or NOT NULL checks at all here. Either the date is in range, or it is not.)
Sum the hours for each employee code generated in the subquery.
Take all employees in the employee_master table, and search for matching entries in the filtered, summed project_employee subquery result set. (Since it is a LEFT JOIN, every employee in the master table will have an entry, even if none of the filtered project_employee entries matched.)
In the case that there is no match, the pe.hours column will be NULL, causing the COALESCE to revert to its second value of zero.
Order results by emp_name.

SQL question: How can I extract this information from these tables?

I have these 3 tables:
EMPLOYEES
Emp_id PK || First_Name || Last_Name || Hiring_Date
Department
Dep_Name || emp_id
SALARIES
salary || emp_id
Two months ago, the company hired new employees.
I need to write a SQL statement, that counts how many employees were hired. In the SAME statement, I need to find out, what are the financial increases by each department, after the new hirings.
For the first thing, I think this is the query:
SELECT COUNT(emp_id) FROM employees
WHERE YEAR(NOW()) - YEAR(hiring_date) = 0 AND (MONTH(NOW()) - MONTH(hiring_date) = 2 OR MONTH(NOW()) - MONTH(hiring_date) = - 2);
but, I don't know how can I extract the information for the 2nd thing. (I know I need to make a join, but I don't know how to extract the increases by each department)
Once again, the 1st and 2nd must be IN THE SAME SQL STATEMENT.
This variant needs all three tables. It uses Standard SQL interval notations; not many DBMS actually support it, but this works when the current date is in January and the question's version does not:
SELECT Dep_Name, COUNT(*), SUM(SALARY)
FROM Employees AS E NATURAL JOIN Salaries AS S ON E.Emp_ID = S.Emp_ID
NATURAL JOIN Department AS D ON E.Emp_ID = D.Emp_ID
WHERE CURRENT_DATE - Hiring_Date <= INTERVAL(2) MONTH
GROUP BY Dep_Name;
I note that the Department table is a little unusual - more normally, it would be called something like Department_Emps; as it stands, its primary key is the Emp_ID column, not the Dep_Name column.
[For the record, the query below is what I used with IBM Informix Dynamic Server.]
SELECT Dep_Name, COUNT(*), SUM(SALARY)
FROM employees AS E JOIN salaries AS S ON E.Emp_ID = S.Emp_ID
JOIN department AS D ON E.Emp_ID = D.Emp_ID
WHERE CURRENT YEAR TO DAY <= INTERVAL(2) MONTH TO MONTH + Hiring_Date
GROUP BY Dep_Name;
SELECT COUNT(emp_id), SUM(salary)
FROM employees e JOIN salaries s ON (s.emd_id = e.emp_id)
WHERE YEAR(NOW()) - YEAR(hiring_date) = 0
AND (MONTH(NOW()) - MONTH(hiring_date) = 2
OR MONTH(NOW()) - MONTH(hiring_date) = - 2)