SQL query within SQL query on sum - sql

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.

Related

Oracle SQL WHEN condition in SELECT statement

I have the following select statement (example) which gives back a sum from a table.
Select SUM(Salary) from Users d, MoreData m where
d.Office = 'A' and
m.Active = 'Yes' and
d.id_user = m.id_of_user
This works perfectly fine and adds up column salery for all users which are located in Office A abd are marked as active in the table MoreData.
Now I would only like to get a result if the SUM(Salary) under these conditions is greater than 1.000.000 EUR. If not the result would be just NULL.
Thanks in Advance
I tried adding an CASE WHEN statement but this always gives ORA-00900 back.
It needs to be an SELECT statement.
That looks like a having clause:
Select SUM(Salary) from Users d, MoreData m where
d.Office = 'A' and
m.Active = 'Yes' and
d.id_user = m.id_of_user
having sum(salary) > 1000000 --> this
Though, I presume that you'd actually want to get some more info, such as which user has such a salary, so you'd have to add that column into the select column list and then group by it. Also, to follow good practice, switch to join:
SELECT d.id_user,
SUM (salary)
FROM users d JOIN moredata m ON m.id_of_user = d.id_user
WHERE d.office = 'A'
AND m.active = 'Yes'
GROUP BY d.id_user
HAVING SUM (salary) > 1000000

How to add the occurrences of a value in a SQL table specific to another value in the table

I have searched and think I have found part of my answer, but I still can't quite figure it out. I have a database with 4 tables and I'm trying to return for each employee their name, the number of total vacation days they have which is based on their job title and the number of vacation days they have taken wich is found by adding up all of instances where the ReasonID column of the Leave table equals 2 for that employee.
This is what I have, and if I take out the line where I'm trying to get VacationDaysTaken, I can return the correct EmployeeName and TotalVactionDays. If I just try to return VacationDaysTaken, then I get the number of vacation days used by all employees. If I try to run it as I have it listed below, I get "Column 'Employee.Last' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
SELECT
Employee.Last + ', ' + Employee.First AS EmployeeName,
Title.Vacation AS TotalVacationDays,
SUM(CASE WHEN Leave.ReasonID=2 THEN 1 ELSE 0 END) AS VacationDaysTaken
FROM Employee, Title, Leave, LeaveType
WHERE Employee.EmpID = Leave.EmpID
AND Leave.ReasonID = LeaveType.ReasonID
AND Employee.TitleID = Title.TitleID
ORDER BY EmployeeName
Never use commas in the FROM clause. Always use proper, explicit JOIN syntax.
You need a GROUP BY:
SELECT e.Last + ', ' + e.First AS EmployeeName,
t.Vacation AS TotalVacationDays,
SUM(CASE WHEN l.ReasonID = 2 THEN 1 ELSE 0 END) AS VacationDaysTaken
FROM Employee e JOIN
Title t
ON e.TitleID = t.TitleID JOIN
Leave l
ON e.EmpID = l.EmpID
GROUP BY e.Last, e.First, t.Vacation
ORDER BY EmployeeName;
Note: Because you are using the ReasonId for the comparison, there is no need to join to the leave types table.

Set default value to zero when count is null

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.

Sql Query statement under microsoft access

i have 2 tables:
Employee:
ID
SalaryPerDay
overTimeHoursPrice
.....
.....
Schedule:
ID
EmployeeID
Date
Attending (boolean)
loan
discount
overTimeHours
with many to one relationship
i want a query to return
[employee name] and
[sum(loan)] and
[sum(discount)] and
[sum(overTimeHours)] and
[count(attending)] where attending = true and
[count(attending) * SalaryPerDay] and
[sum(overTimeHours)* overTimeHoursPrice ] and
[(count(attending) * SalaryPerDay) + (sum(overTimeHours)* overTimeHoursPrice) - (sum(discount)) - (sum(loan))]
1- where Date >= [date1] And Date <= [date2]
2- where Date >= [date1] And Date <= [date2] And EmployeeName = [name]
(date1 and date2 and name are parameters)
Something like this should do the trick....
SELECT
emp.EmployeeName, sum_loan, sum_discount, sum_overTimeHours, count_attending,
(count_attending*SalaryPerDay) as totalDayPay,
(sum_overTimeHours*overTimeHoursPrice) as totalOverTimePay,
((count_attending*SalaryPerDay) + (sum_overTimeHours*overTimeHoursPrice) -
sum_discount - sum_loan) as grandTotal
FROM Employee emp
INNER JOIN (SELECT
EmployeeID,
sum(loan) as sum_loan,
sum(discount) as sum_discount,
sum(overTimeHours) as sum_overTimeHours,
sum(iif(Attending,1,0)) as count_attending
FROM Schedule
WHERE Date >= {date1} and Date <= {date2}
GROUP BY EmployeeID
) sch
ON emp.ID = sch.EmployeeID
WHERE emp.EmployeeName = {name}
Note the two WHERE clauses. You can adjust these as needed to achieve your two different parameterized restrictions.
Edit #1:
Due to some uncertainty about the actual numeric value of the "boolean" stored in the Schedule.Attending field, I've adjusted the query above to account for the boolean value explicitly. To accomplish this, I've made use of the MSAccess-specific expression function, IIF(). This is a much more robust solution than just assuming that the field will contain either a 1 or a 0.
Edit #2:
I should also note that the syntax varies slightly depending on where you're using it. The above is the "standard sql" syntax for the derived table (that's the subquery that's inside parenthesis following the INNER JOIN keywords). If you're running this query through an ODBC connection, then the syntax above is valid.
However, If you're trying to create a Query within Access itself, you'll need to use square brackets with a trailing period [ ]. instead of parenthesis ( ) around the subquery. So instead of:
SELECT ... FROM Employee emp INNER JOIN (SELECT ... ) sch ON ...
use this:
SELECT ... FROM Employee emp INNER JOIN [SELECT ... ]. sch ON ...
I think you want:
SELECT e.EmployeeName,
Sum(s.loan) AS SumOfloan,
Sum(s.discount) AS SumOfdiscount,
Sum(s.overTimeHours) AS SumOfoverTimeHours,
Sum(Abs([Attending])) AS Attnd,
Sum([SalaryPerDay]*Abs([Attending])) AS SalyAttnd,
Sum([overTimeHoursPrice]*[overtimehours]) AS OTCost,
Sum(([SalaryPerDay]*Abs([Attending])+[overTimeHoursPrice]*[overtimehours])-([loan]-[discount])) AS Due
FROM Employee e
INNER JOIN Schedule s ON e.ID = s.EmployeeID
WHERE s.Date Between [date1] And [Date2]
AND EmployeeName = [Name]
GROUP BY e.ID, e.EmployeeName
Note that a Boolean is either 0 or -1, so [SalaryPerDay]*Abs([Attending] = Salary * 1, if attending or 0, if not attending.

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.