Sql Query statement under microsoft access - sql

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.

Related

Need to join 3 tables and return results where everyone is over age 75

I have table employee with
id, name, dob, emplid
table documentation has
cdid, emplid, status, record
table appointment has
cvid, emplid, slotid
Everyone has a record in table Employee. Table Appointment stores everyone who schedules an appointment and table Documentation is where the record gets inserted when they complete their appointment. The problem is, they take walk-ins and will have a record in table Documentation, but no record in table Appointment. They want me to find everyone in table Employee who is over age 75, but does not currently have an appointment or has never come in as a walk-in.
I started with the below, but I am stuck on how to accurately get everyone counted.
SELECT COUNT(AgeYearsIntTrunc)
FROM (
SELECT DATEDIFF(hour,e.DOB,GETDATE())/8766.0 AS AgeYearsDecimal
,CONVERT(int,ROUND(DATEDIFF(hour,e.DOB,GETDATE())/8766.0,0)) AS AgeYearsIntRound
,DATEDIFF(hour,e.DOB,GETDATE())/8766 AS AgeYearsIntTrunc
FROM [dbo].Employee e
LEFT JOIN [dbo].Documentation d ON e.EmplID = d.EMPLID
WHERE d.Status IS NULL
) dt WHERE AgeYearsIntTrunc >='75'
Sample Data
It's normally best to not expect the compiler to do algebra for you, in other words: write predicates like x > y + 5 rather than x - y > 5.
Generally, the most efficient method for comparing dates is to keep the column you are checking without any function, do any necessary calculations on the GETDATE side.
NOT EXISTS is much easier for the compiler to reason about than LEFT JOIN / IS NULL
SELECT e.* --COUNT(*)
FROM [dbo].Employee e
WHERE e.DOB < DATEADD(year, -75, GETDATE())
AND (
NOT EXISTS (SELECT 1
FROM [dbo].Documentation d
WHERE e.EmplID = d.EMPLID
)
AND -- do you want OR maybe?
NOT EXISTS (SELECT 1
FROM dbo.Appointment a
WHERE a.EmplId = e.EmplId
)
)

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.

SQL how to handle multiple value join

I need to join assignments and expatriates tables by a combination of ID, effective_start_date and effective_end_date.
I need to get data about employees who have gone to another country during their assignment effective_start_date and effective_end_date. But there is a need to handle cases when during one assignment there have been entered data about employees going to two or more countries - I need to show only one - the last one or the active one (if there is).
In the results I'm getting multiple values for 123 person ID and it's because there are incorrect values entered in assignments table - I need to only show only one of this person 123 date - the information about him going to china (the active one).
So basically, if during one assignment (between effective_start_date and effective_end_date) there is information about him going to 2 different countries, I need to only show one case. I need to correct my select statement so it handles this case somehow.
Edit : This also needs to work when the 2 cases about employee going to another country are historical so I dont think this can be done with sysdate.
Edit nr.2 - updated sql fiddle. I need to show BOTH expatriations for person 321 and ony one for person 123 - this is basically my main goal.
Edit nr.3 - still havent found the solution.
LINK TO SQLFIDDLE
select
ass.person_id,
ass.effective_start_date,
ass.effective_end_date,
exp.date_from,
exp.date_to,
exp.home_country,
exp.host_country
from expatriates exp, assignments ass
where
exp.person_id=ass.person_id
and exp.date_to >= ass.effective_start_date
and exp.date_to <= ass.effective_end_date
As #PuneetPandey already wrote your logic will not catch all overlapping periods.
To get only one row you can use ROW_NUMBER, e.g.
select *
from
(
select
ass.person_id,
ass.effective_start_date,
ass.effective_end_date,
exp.date_from,
exp.date_to,
exp.home_country,
exp.host_country,
row_number()
over (partition by ass.person_id, ass.effective_start_date
order by exp.date_from) as rn
from expatriates exp, assignments ass
where
exp.person_id=ass.person_id
and exp.date_to >= ass.effective_start_date
and exp.date_to <= ass.effective_end_date
) dt
where rn = 1
First of all, I think the query needs to be changed -
and exp.date_to <= ass.effective_end_date to and exp.date_from <= ass.effective_end_date.
Now, if you want any of the visited country, you can select distinct record by personid as below -
select
distinct ass.person_id,
ass.effective_start_date,
ass.effective_end_date,
exp.date_from,
exp.date_to,
exp.home_country,
exp.host_country
from expatriates exp, assignments ass
where
exp.person_id=ass.person_id
and exp.date_to >= ass.effective_start_date
and exp.date_from <= ass.effective_end_date
or, if you want a particular row, you can probably maintain another column for status and have that as '1' if the visit is active else keep that as '0' and use below query -
select
ass.person_id,
ass.effective_start_date,
ass.effective_end_date,
exp.date_from,
exp.date_to,
exp.home_country,
exp.host_country
from expatriates exp, assignments ass
where
exp.person_id=ass.person_id
and exp.date_to >= ass.effective_start_date
and exp.date_from <= ass.effective_end_date
and exp.status = 1
I think you need to join a third table which will a derived table like "X" below:
select
ass.person_id,
ass.effective_start_date,
ass.effective_end_date,
exp.date_from,
exp.date_to,
exp.home_country,
exp.host_country
from expatriates exp, assignments ass, (
SELECT e.person_id, MAX(e.date_from) md
FROM expatriates e
INNER JOIN assignments a ON e.person_id=a.person_id
and e.date_to >= a.effective_start_date
and e.date_to <= a.effective_end_date GROUP BY e.person_id) X
where exp.person_id= X.person_id
and exp.date_from= X.md
Im assuming if a person get fired effective_end_date will be updated and no more expatriates record will appear. So I just select the last date_to in expatriates. That is why I dont see why you need compare date ranges and remove that part from my where.
SQL FIDDLE DEMO
active_or_last_ass AS (
SELECT exp.person_id, date_from, max(exp.date_to) max_date
FROM expatriates exp
WHERE exp.date_from < sysdate
GROUP BY exp.person_id, date_from
)
select
ass.person_id,
ass.effective_start_date,
ass.effective_end_date,
exp.date_from,
exp.date_to,
exp.home_country,
exp.host_country
from
active_or_last_ass ala
inner join expatriates exp
on exp.person_id = ala.person_id
and exp.date_to = ala.max_date
inner join assignments ass
on exp.person_id = ass.person_id

Trying to select multiple columns on an inner join query with group and where clauses

I'm trying to run a query where it will give me one Sum Function, then select two columns from a joined table and then to group that data by the unique id i gave them. This is my original query and it works.
SELECT Sum (Commission_Paid)
FROM [INTERN_DB2].[dbo].[PaymentList]
INNER JOIN [INTERN_DB2]..[RealEstateAgentList]
ON RealEstateAgentList.AgentID = PaymentList.AgentID
WHERE Close_Date >= '1/1/2013' AND Close_Date <= '12/31/2013'
GROUP BY RealEstateAgentList.AgentID
I've tried the query below, but I keep getting an error and I don't know why. It says its a syntax error.
SELECT Sum (Commission_Paid)
FROM [INTERN_DB2].[dbo].[PaymentList]
INNERJOIN [INTERN_DB2]..[RealEstateAgentList](
Select First_Name, Last_Name
From [Intern_DB2]..[RealEstateAgentList]
Group By Last_name
)
ON RealEstateAgentList.AgentID = PaymentList.AgentID
WHERE Close_Date >= '1/1/2013' AND Close_Date <= '12/31/2013'
GROUP BY RealEstateAgentList.AgentID
Your query has multiple problems:
SELECT rl.AgentId, rl.first_name, rl.last_name, Sum(Commission_Paid)
FROM [INTERN_DB2].[dbo].[PaymentList] pl inner join
(Select agent_id, min(first_name) as first_name, min(last_name) as last_name
From [Intern_DB2]..[RealEstateAgentList]
GROUP BY agent_id
) rl
ON rl.AgentID = pl.AgentID
WHERE Close_Date >= '2013-01-01' AND Close_Date <= '2013-12-31'
GROUP BY rl.AgentID, rl.first_name, rl.last_name;
Here are some changes:
INNERJOIN --> inner join.
Fixed the syntax of the subquery next to the table name.
Removed columns for first and last name. They are not used.
Changed the subquery to include agent_id.
Added agent_id, first_name, and last_name to the outer aggregation, so you can tell where the values are coming from.
Changed the date formats to a less ambiguous standard form.
Added table alias for subquery.
I suspect the subquery on the agent list is not important. You can probably do:
SELECT rl.AgentId, rl.first_name, rl.last_name, Sum(pl.Commission_Paid)
FROM [INTERN_DB2].[dbo].[PaymentList] pl inner join
[Intern_DB2]..[RealEstateAgentList] rl
ON rl.AgentID = pl.AgentID
WHERE pl.Close_Date >= '2013-01-01' AND pl.Close_Date <= '2013-12-31'
GROUP BY rl.AgentID, rl.first_name, rl.last_name;
EDIT:
I'm glad this solution helped. As you continue to write queries, try to always do the following:
Use table aliases that are abbreviations of the table names.
Always use table aliases when referring to columns.
When using date constants, either use "YYYY-MM-DD" format or use convert() to convert a string using the specified format. (The latter is actually the safer method, but the former is more convenient and works in almost all databases.)
Pay attention to the error messages; they can be informative in SQL Server (unfortunately, other databases are not so clear).
Format your query so other people can understand it. This will help you understand and debug your queries as well. I have a very particular formatting style (which no one is going to change at this point); the important thing is not the particular style but being able to "see" what the query is doing. My style is documented in my book "Data Analysis Using SQL and Excel.
There are other rules, but these are a good way to get started.
SELECT Sum (Commission_Paid)
FROM [INTERN_DB2].[dbo].[PaymentList] pl
INNER JOIN (
Select First_Name, Last_Name
From [Intern_DB2]..[RealEstateAgentList]
Group By Last_name
) x ON x.AgentID = pl.AgentID
WHERE Close_Date >= '1/1/2013'
AND Close_Date <= '12/31/2013'
GROUP BY RealEstateAgentList.AgentID
This is how the query should look... however, if you subquery first and last name, you'll also have to include them in the group by. Assuming Close_Date is in the PaymentList table, this is how I would write the query:
SELECT
al.AgentID,
al.FirstName,
al.LastName,
Sum(pl.Commission_Paid) AS Commission_Paid
FROM [INTERN_DB2].[dbo].[PaymentList] pl
INNER JOIN [Intern_DB2].dbo.[RealEstateAgentList] al ON al.AgentID = pl.AgentID
WHERE YEAR(pl.Close_Date) = '2013'
GROUP BY al.AgentID, al.FirstName, al.LastName
Subqueries are evil, for the most part. There's no need for one here, because you can just get the columns from the join.

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.