For a University course I've been given the following task
Suppose that we have an employee table like (first_name, last_name, salary, hire_date, ...). There is regulation which states that every employee each year after he/she is hired, must renew his contract with company. Show how many months remain before the next renewal for each employee.
This is my attempt:
select (abs(months_between(sysdate,hire_date)/12 -
round((months_between(sysdate,e.hire_date))/12)))
from employees e
is it correct?or what will be correct implementation
You are part of the way there, but this isn't it - what you have so far will show the modulus of months since hire date divided by 12, as a fraction.
What you need is to subtract the the modulus of months since hire date divided by 12, as a whole number, from 12.
So to take an example, an employee who was hired 13 months ago will return 0.0833333 in your supplied query - you want to see a result of (12 - (the remainder of 13/12 as a whole number - ie. 1)) = 11 months to go.
Most flavours of SQL have a modulus function, although the name and syntax may vary between SQLs.
Also, I would amend your query to include e.* as well as your calculation in your select statement.
If you start with a table like this:
create table my_table (
first_name varchar(15),
last_name varchar(15),
salary integer,
hire_date date
);
insert into my_table values ('A', 'Beemer', 30000, '2011-01-15');
insert into my_table values ('B', 'Catalog', 31000, '2011-01-27');
insert into my_table values ('C', 'Doir', 45000, '2011-03-11');
you can use a SELECT statement to return a result like this one.
first_name last_name salary hire_date renewal_date days_to_renewal
--
A Beemer 30000 2011-01-15 2012-01-15 57
B Catalog 31000 2011-01-27 2012-01-27 69
C Doir 45000 2011-03-11 2012-03-11 113
You should keep in mind that "month" is a fuzzy word that most database people hate with a white-hot, burning passion. It can mean 28, 29, 30, or 31 days on a real calendar. And you will always get blamed when expectations don't match common sense.
Running your query against my data returns these values. Clearly wrong.
LAST_NAME MONTHS_TO_RENEWAL
Beemer .154442577658303464755077658303464755078
Catalog .186700642174432497013142174432497013143
Doir .310356556152927120669056152927120669056
Oracle has a months_between() function that's documented to return "the number of months between two dates". You need to know exactly what "number of months" means to this function. (Again, given that a calendar month might have 28, 29, 30 or 31 days in it.) I'd be really surprised if you're expected to do the arithmetic any other way.
I'd suggest you start with this, and fill in the three missing pieces.
select first_name, last_name, salary, hire_date,
('date arithmetic to calculate the renewal date') renewal_date,
months_between('one date', 'another date') months_to_renewal
from my_table;
Related
If anyone could help me with the following, I'd be grateful:
I'm trying to write a code (create an alert in PowerSchool) that will indicate if a student is younger or older than average for their current grade level. (For example, as student born before 6/30/2002 is older than average for 9th grade) I can't seem to make DECODE work in conjunction with >= TO_DATE. Here's my statement:
select lastfirst, decode (dob >= to_date ('2002-06-30', 'yyyy-mm-dd'), 'old') DOB
from students
where grade_level = 9
order by lastfirst
You probably could get away with using a PowerSchool decode in your sql, but I find code easier to write when fewer languages/systems are included, so I would leave it out. I would also let SQL do the average age calculation so you don't have to supply or calculate average dates yourself (see Averaging dates in oracle sql for an explanation of the TO_DATE line).
SELECT
LastFirst AS "Student Name"
,CASE WHEN dob >= (
SELECT
TO_DATE(ROUND(AVG(TO_NUMBER(TO_CHAR(dob, 'J')))),'J')
FROM Students
WHERE Grade_Level = 9
AND Enroll_Status = 0
) THEN 'Younger' ELSE 'Older' END AS "Older or Younger?"
FROM Students
WHERE Grade_Level = 9
ORDER BY LastFirst
The subquery calculates the average birthday, and the CASE statement compares each of your records against that, reporting if it's older or younger.
In the subquery that calculates the average, I've taken the liberty of assuming you only want to compare against currently enrolled students, since withdrawn students retain the same grade level. You don't really want those students who left 10 years ago and are still listed as a 9th grader to mess with your average numbers.
I have data submitted by several departments that I need to summarise to output on a report.
Most days, every department submits data. Some days, a department might miss submitting data.
I need to reflect a zero value entry for that department for the day, rather than skipping it.
I don't know why, but this is striking me as a difficult challenge.
If my data looks like this:
Date, Department, Employee
1 May 2016, First, Fred
1 May 2016, First, Wilma
1 May 2016, Second, Betty
1 May 2016, Second, Barney
2 May 2016, Second, Betty
3 May 2016, First, Wilma
3 May 2016, Second, Betty
3 May 2016, Second, Barney
If I do a count(*) on this data, the output I am hoping for is:
1 May 2016, First, 2
1 May 2016, Second, 2
2 May 2016, First, 0
2 May 2016, Second, 1
3 May 2016, First, 1
3 May 2016, Second, 2
It's the 3rd line, "2 May 2016, First, 0", that I can't get my output to include.
My underlying data is more complex than above, but above is a reasonable simplex representation of the problem.
I'm at the point where I'm messing around with cursors trying to 'build' this recordset, so I think that's a clue that I need to ask for help.
Assuming that your main table is:
create table mydata
(ReportDate date,
department varchar2(20),
Employee varchar2(20));
We can use the below query:
with dates (reportDate) as
(select to_date('01-05-2016','dd-mm-yyyy') + rownum -1
from all_objects
where rownum <=
to_date('03-05-2016','dd-mm-yyyy')-to_date('01-05-2016','dd-mm-yyyy')+1 ),
departments( department) as
( select 'First' from dual
union all
select 'Second' from dual) ,
AllReports ( reportDate, Department) as
(select dt.reportDate,
dp.department
from dates dt
cross join
departments dp )
select ar.reportDate, ar.department, count(md.employee)
from AllReports ar
left join myData md
on ar.ReportDate = md.reportDate and
ar.department = md.department
group by ar.reportDate, ar.department
order by 1, 2
First we generate dates that we are interested in. In our sample between 01-05-2016 and 03-05-2016. It's in dates WITH.
Next we generate list of departments - Departments WITH.
We cross join them to generate all possible reports - AllReports WITH.
And we use LEFT JOIN to your main table to figure out which data exists and which are missing.
I am in Database Fundamentals at ETSU. Our instructor is from Bangladesh, so it is hard to understand his lectures at times. Well SYSDATE usage was one of those days. Then in our lab had this question,
Display the average duration of work (in number of days) for employees grouped by job_id. Assume that nobody has resigned or been fired. Use Oracle date function SYSDATE.
After hours of google and stack overflow searching, and questions unanswered by the professor it still stumped me.
I have tried several (SELECT, FROM, GROUP_BY, HAVING, & ORDER_BY) and have finally come here in search of help.
This is my script:
SELECT job_id, hire_date
FROM hr.employees
GROUP BY job_id
HAVING AVG(SYSDATE) - hire_date;
This is my error:
Error starting at line: 1 in command -
SELECT job_id, hire_date
FROM hr.employees
GROUP BY job_id
HAVING AVG(SYSDATE) - hire_date;
Error at Command Line : 4 Column : 31
Error report -
SQL Error : ORA-00920: Invalid relational operator
00920. 00000 - "invalid relational operator"
WHAT am I doing wrong?
Display the average duration of work (in number of days) for employees grouped by job_id. Assume that nobody has resigned or been fired. Use Oracle date function SYSDATE.
So assuming no employees have been fired since their hiredate, let's see the below example which finds the average duration of work of employees in each department. Also, this assumes all the days of the week(including weekend).
SQL> SELECT deptno,
2 ROUND(AVG(SYSDATE - hiredate), 2) avg_duration
3 FROM emp
4 GROUP BY deptno
5 /
DEPTNO AVG_DURATION
---------- ------------
30 12327.79
20 12135.45
10 12218.79
SQL>
If you do not want to include the weekends, then you need to filter out those dates which are fall in either weekends or holidays.
Hint, where to_char(that_date, 'Dy') not in ('Sat', 'Sun')
But, I think the question is too simple and doesn't need to be made so complicated. So, above solution should suffice.
In the sql query, I am getting problem in the where clause.
I have a dob and ssn of people in a table. I need to find the youngest snr. customer. The age of youngest senior customer starts from 55. The data of DOB contains all the dob's of children,parent, senior customers. In the line "where" I have to write a condition that checks if the age is > 55 and has to be smaller amongst the senior customers.Please suggest me some ways .I posted a similar question before, but did nt get any reply that can help me in solving it.
I dont have the age parameter in my table.
SSn DOB
22 1950-2-2
21 1987-3-3
54 1954-4-7
I need to find the ssn corresponding to the age whcih has to be greater than 55 and smaller among above values .
In the script below, the sub-select finds the DOB of the youngest person 55 years of age or over; this is then used to find the corresponding SSN record(s). [This uses SQL Server syntax.]
SELECT yt.*
FROM *yourtable* yt
INNER JOIN
(
SELECT MAX(DOB) AS DOB
FROM *yourtable*
WHERE DATEADD(year, 55, DOB) < getdate()
) maxdob
ON maxdob.DOB = yt.DOB
n.b. you may find more than a single record if there is more than 1 person with the same DOB.
If you want to force this single restriction, add a TOP 1 clause in your SELECT statement.
hth
If you have the DOB then you can easily calculate the age based on the current date:
WHERE DATE_ADD(DOB, INTERVAL 55 YEAR) < NOW()
This will add 55 years to the DOB and if it is greater than the current time, it's true. This would indicate they are at least 55 years of age.
I am creating a database that will help keep track of which employees have been on a certain training course. I would like to get some guidance on the best way to design the database.
Specifically, each employee must attend the training course each year and my database needs to keep a history of all the dates on which they have attend the course in the past.
The end user will use the software as a planning tool to help them book future course dates for employees. When they select a given employee they will see:
(a) Last attendance date
(b) Projected future attendance date(i.e. last attendance date + 1 calendar year)
In terms of my database, any given employee may have multiple past course attendance dates:
EmpName AttandanceDate
Joe Bloggs 1st Jan 2007
Joe Bloggs 4th Jan 2008
Joe Bloggs 3rd Jan 2009
Joe Bloggs 8th Jan 2010
My question is what is the best way to set up the database to make it easy to retrieve the most recent course attendance date? In the example above, the most recent would be 8th Jan 2010.
Is there a good way to use SQL to sort by date and pick the MAX date?
My other idea was to add a column called ‘MostRecent’ and just set this to TRUE.
EmpName AttandanceDate MostRecent
Joe Bloggs 1st Jan 2007 False
Joe Bloggs 4th Jan 2008 False
Joe Bloggs 3rd Jan 2009 False
Joe Bloggs 8th Jan 2010 True
I wondered if this would simplify the SQL i.e.
SELECT Joe Bloggs WHERE MostRecent = ‘TRUE’
Also, when the user updates a given employee’s attendance record (i.e. with latest attendance date) I could use SQL to:
Search for the employee and set the
MostRecent value to FALSE
Add a new record with MostRecent set to TRUE?
Would anybody recommended either method over the other? Or do you have a completely different way of solving this problem?
To get the last attendance date use the group function called MAX, i.e.
SELECT MAX(AttandanceDate)
FROM course_data
WHERE employee_name = 'Joe Bloggs'
To get the max attendance date for all the employees:
SELECT employee_name, MAX(AttandanceDate)
FROM course_data
GROUP BY employee_name
ORDER BY employee_name
Query above will NOT return data for employees who haven't attended any courses. So you need to execute a different query.
SELECT A.employee_name, B.AttandanceDate
FROM employee AS A
LEFT JOIN (
SELECT employee_id, MAX(AttandanceDate) AS AttandanceDate
FROM course_data
GROUP BY employee_id
) AS B ON A.id = B.employee_id
ORDER BY A.employee_name
For employees who haven't attended any course, the query will return a NULL AttendanceDate.
The flag is redundant. The other way how to get last attend day by employee:
select top 1 AttandanceDate
from course_data
WHERE employee_name = 'Joe Bloggs'
order by AttandanceDate desc
This may already be the case, but the output from the AttandanceDate columns makes me suspicious that that column may not be a datetime column. Most RDBMS's have some sort of date, time, and/or date time data types to use for storing this information. In which KandadaBoggu's AND OMG Ponies responses are perfect. But if you are storing your dates as strings you WILL have issues trying to do any of their suggestions.
Using a date time data type usually also opens you to the possibilites of obtaining date details like:
e.g. SELECT YEAR(2008-01-01) will return 2008 as an integer.
If you are running SQL Server 2005 or 2008 or later, you can use row_number() do something like the following. This will list everyone, with their most recent attendance.
with temp1 as
(select *
, (row_number() over (partition by EmpName order by AttandanceDate descending))
as [CourseAttendanceOrder]
from AttendanceHistory)
select *
from temp
where CourseAttendanceOrder = 1
order by EmpName
This could be put into a view so you can use it as needed.
However, if you always will be focused on one individual at a time, it may be more efficient to make a stored procedure that can use statements like select max(AttandanceDate) for just the person you are working on.