SQL query to find employee with 3 year over year salary raises? - sql

Here is the table. My initial observation would be to query the salary to which employee has an incremental salary per year, but am confused on how to do that. Employee 1 is the only employee that has a three year increase, but not sure how to single them out. Thanks!

You can do this using lead()/lag() and aggregation:
select employee_id
from (select t.*,
lag(salary) over (partition by employee_id order by year) as prev_salary
from t
) t
group by employee_id
having min(salary - prev_salary) > 0 and
count(*) = 3;
This compares salaries in adjacent years and returns employees where the value is always increasing. It assumes that there are no gaps in the years -- as in your sample data.
The advantage of this approach is that you don't need to know the years in advance.

One option uses aggregation:
select employee_id
from mytable t
group by employee_id
having max(salary) filter(where year = 2020) > max(salary) filter(where year = 2019)
and max(salary) filter(where year = 2019) > max(salary) filter(where year = 2018)
This brings employee whose 2020 salary is greather than their 2019 salary, and whose 2019 salary is greater than their 2018 salary - which is how I understood your question.

I guess you don't want to hardcode the years in your query.
The best choice is the use of the window function LAG() to get the salary of the previous 2 years, but you should also check that the 3 years that you check are consecutive:
SELECT DISTINCT employee_id
FROM (
SELECT *,
LAG(year, 1) OVER (PARTITION BY employee_id ORDER BY year) year1,
LAG(salary, 1) OVER (PARTITION BY employee_id ORDER BY year) salary1,
LAG(year, 2) OVER (PARTITION BY employee_id ORDER BY year) year2,
LAG(salary, 2) OVER (PARTITION BY employee_id ORDER BY year) salary2
FROM tablename t
)
WHERE year1 = year - 1 AND year2 = year - 2 AND salary > salary1 AND salary1 > salary2
If you want to check only for the current year and the 2 previous years then add 1 more condition in the WHERE clause:
...AND year = strftime('%Y', CURRENT_DATE)
so you don't need to hardcode the current year,

Related

Compare current month sales with previous month sales

Code to get the test data:
create table SalesCalls
(
EmpId INT NOT NULL,
EmpName nvarchar(20),
month INT,
Year INT,
CallsMade INT
)
GO
Insert into SalesCalls values
(1,'ABC',12,2018,10),
(1,'ABC',1,2019,15),
(1,'ABC',2,2019,20),
(2,'DEF',12,2018,12),
(2,'DEF',1,2019,14),
(2,'DEF',2,2019,26)
GO
The objective is to compare the current month sales of an Employee with the previous month sales of that Employee and find out the percentage change in it. Achieved that using the below query:
With SalesCTE as
(
select EmpId,EmpName,
Month As CurrentMonth,
Year as CurrentMonthYear,
Case When month = 1 then 12 Else (Month-1) End AS PrevMonth,
Case when month = 1 then (Year - 1) Else Year End As PrevMonthYear,
CallsMade
from SalesCalls
)
select
S1.EmpId, S1.EmpName, S1.CurrentMonth, S1.CurrentMonthYear, S1.CallsMade as CurrentMonthCalls,
S2.CurrentMonth as PrevMont,
S2.CurrentMonthYear as PrevMonthYear,
S2.CallsMade as PrevMonthCalls,
( CONVERT(numeric(5,2),S1.CallsMade) / S2.CallsMade) * 100 As PercentageChange
from SalesCTE S1
JOIN SalesCTE S2 ON S1.EmpId = S2.EmpId
AND S1.PrevMonth = S2.CurrentMonth
AND S1.PrevMonthYear = S2.CurrentMonthYear
ORDER BY S1.EmpId, S1.CurrentMonth, S1.CurrentMonthYear
The above query worked until the time there are no redundant records for an Employee for the same month.
But later data from multiple sources is coming in and an Employee table can have multiple records for the same month and it is still valid. Because the employee could be making calls in different ways. An as example the below record is inserted into the table:
Insert into SalesCalls values
(1,'ABC',1,2019,1)
Now the above query which worked fine above for the comparison of current month SalesCalls with the previous month is no longer working.
Phase 2 of the use case:
So to fix this I have build an intermediate temp table that contains aggregate data. The query used is:
Select EmpId, EmpName, month, Year, SUM(CallsMade) as CallsMade
into #SalesCalls
from SalesCalls
group by EmpId, EmpName, month, Year
Now the SalesCalls table inside the CTE is replaced with #SalesCalls and then the above query works fine.
But this #SalesCalls table needs to be dropped and recreated every time to see the latest comparison data.
The question is, is it possible to get the comparison data using a single query only and no intermediate temp tables or views.
Just use window functions:
select EmpId, EmpName, month, Year,
sum(CallsMade) as CallsMade,
(case when lag(year * 12 + month) over (partition by empId order by year, month) = year * 12 + month - 1
then lag(sum(callsMade)) over (partition by empId order by year, month)
end) as prevMonthCalls,
(case when lag(year * 12 + month) over (partition by empId order by year, month) = year * 12 + month - 1
then callsMade * 100.0 / lag(sum(callsMade) over (partition by empId order by year, month)
end) as as perentageChange
from SalesCalls
group by EmpId, EmpName, month, Year;
No joins, CTEs, subqueries, or temporary tables are needed at all.
One of the simplest solutions :
select EmpId, EmpName, month, Year,
sum(CallsMade) as CallsMade,
lag(sum(callsMade)) over (partition by empId order by year, month) AS prevMonthCalls,
sum(CallsMade) * 100.0 / lag(sum(CallsMade)) over (partition by empId order by year, month) as PercentageChange
from SalesCalls
group by EmpId, EmpName, month, Year
order by EmpId, Year,month;

T-SQL - Get last 30 Rows for eacht ID

how can i get the last 30 rows in a month for each employee? i have a table with evaluations for each employee.
SELECT
Date,
Month,
Team,
Employee_ID,
Evaluation_Score,
Evaluation_Case_Number
From X
Where month = #month
Order by date desc
This is what i got, but i only want to see the last 30 Evaluation Scores (or less, if they don't have that many) for the declared month.
Is there a way to do this? Thanks in Advance.
You can use row_number(). Something like this:
select x.*
from (select x.*,
row_number() over (partition by employee_id order by date desc) as seqnum
from x
where month = #month
) x
where seqnum <= 30;
Use the TOP statement:
SELECT TOP(30)
Date,
Month,
Team,
Employee_ID,
Evaluation_Score,
Evaluation_Case_Number
From X
Where month = #month
Order by date desc
This will help you to limit the number of returned rows.

How to write query for the cumulative salary for each employee within each year

This table has columns employee, month, year and salary.
I need to write sql query for this.
It should reset when we reach to December and restart calculating running sal for next year for the same employee.
As per now I am manage to write below Query but it is not enough:
SELECT Employee, Month, Year, Salary, SUM(Salary) OVER(ORDER BY rownum) AS "CUM SALARY"
FROM emp
group by Employee, Month, Year, Salary,rownum;
I don't know which database you are using, but this will work in Oracle database
SELECT Employee, Month, Year, Salary, SUM(Salary) OVER(partition by Employee, Year ORDER BY to_date(Year||Month, 'YYYYMon', 'nls_date_language = english')) AS "CUM SALARY"
FROM emp
;
You can use the sum analytical function as follows:
select t.*,
sum(salary) over (partition by empployee, year
order by case month when 'Jan' then 1
when 'Feb' then 2
.....
when 'Dec' then 12
end) as cum_sal
from employee t
Or you can use try_cast as follows:
select t.*,
sum(salary) over (partition by empployee, year
order by TRY_CAST (concat(month,' ', year)) as cum_sal
from employee t
Sample Query
WITH CTE
AS(
SELECT
EmpName,
Sal_Date,
Salary,
(ROW_NUMBER() over (PARTITION BY YEAR(Sal_Date) order by EmpName,Sal_Date asc) ) ROWNO
FROM tbl_EmpTB
GROUP BY EmpName,Sal_Date, Salary
)
SELECT EmpName,Datename(MONTH,sal_date) [Month],YEAR(sal_date) [YEAR],Salary,--CUMSAL,
SUM (Salary) OVER (PARTITION BY YEAR(Sal_Date) order by EmpName,Sal_Date asc) [CUM SALARY]
FROM CTE
ORDER BY Sal_Date

write a query to calculate age till date and find nth highest age from employee table

ID || birthdate
------------------------
1 || 25-10-1994
2 || 1-6-1990
3 || 4-12-2006
how to calculate age and how to get nth highest age.
Use months_between with row_number like this
select m.age from
(select s.age,
ROW_NUMBER() OVER (PARTITION BY s.age ORDER BY s.age desc) rownumber
from (select MONTHS_BETWEEN(sysdate, birthdate )/12 age from myTable) s) m
where m.rownumber = 1 -- or your nth highest age row, for example 11 ))
I'm unaware of Oracle syntax for SQL query but you can create the query as follows:
Sort the date column by ascending order.
Set results limit to 1 & offset to N i.e. Nth largest.
Use select to print that date column subtracted from current date to get present age.
The age can simply be calculated by subtracting the birtdate from sysdate. That will return the age in days. Once you have that, this is a typical greatest-n-per-group query. You can use e.g. a window function to filter out the n oldest:
select id, birthdate, "Age in days"
from (
select id,
birthdate,
sysdate - birthdate as "Age in days",
row_number() over (order by sysdate - birthdate desc) as rn
from emps
)
where rn <= 2 -- or whatever limit you want
order by 3;

ORACLE 12c - "not a single-group group function"

I have a table o employees that contains names, date of employment and some more information.
I want to check which year the most employees were employed.
I write a query which count employment for each year:
SELECT EXTRACT (YEAR FROM e1.empl_date) AS YEAR, COUNT(e1.id_empl) AS EMPL_NUMBER
FROM employees e1
GROUP BY EXTRACT (YEAR FROM e1.empl_date);
And result of this query are tuples:
YEAR | EMPL_NUMBER
1993 | 3
1997 | 2
and so on...
And now I want to get max of EMPL_NUMBER:
SELECT YEAR, MAX(EMPL_NUMBER)
FROM (SELECT EXTRACT (YEAR FROM e1.empl_date) AS YEAR, COUNT(e1.id_empl) AS EMPL_NUMBER
FROM employees e1
GROUP BY EXTRACT (YEAR FROM e1.empl_date));
And then I get an error:
ORA-00937: not a single-group group function
I don't understand why I get an error because subquery returns tuple with 2 columns.
You are using an aggregation function on the select result so If you need all the distinct YEAR you ust group by
SELECT T.YEAR, MAX(T.EMPL_NUMBER)
FROM (
SELECT EXTRACT (YEAR FROM e1.empl_date) AS YEAR, COUNT(e1.id_empl) AS EMPL_NUMBER
FROM employees e1
GROUP BY EXTRACT (YEAR FROM e1.empl_date)
) T
GROUP BY T.YEAR ;
Otherwise if you need the year of the MAX(EMPL_NUMBER) you could
SELECT T.YEAR, T.EMPL_NUMBER
FROM (
SELECT EXTRACT (YEAR FROM e1.empl_date) AS YEAR, COUNT(e1.id_empl) AS EMPL_NUMBER
FROM employees e1
GROUP BY EXTRACT (YEAR FROM e1.empl_date)
) T
WHERE (T.EMPL_NUMBER) IN (SELECT MAX(EMPL_NUMBER)
FROM (
SELECT EXTRACT (YEAR FROM e1.empl_date) AS YEAR, COUNT(e1.id_empl) AS EMPL_NUMBER
FROM employees e1
GROUP BY EXTRACT (YEAR FROM e1.empl_date)
) T1 )
In Oracle 12C, you can do:
SELECT EXTRACT(YEAR FROM e1.empl_date) AS YEAR, COUNT(e1.id_empl) AS EMPL_NUMBER
FROM employees e1
GROUP BY EXTRACT(YEAR FROM e1.empl_date)
ORDER BY COUNT(e1.id_empl) DESC
FETCH FIRST 1 ROW ONLY;
One way to do this is to use an aggregate query as you were doing already, and then to use aggregate functions to their full extent. For example, using the FIRST/LAST function (and using the SCOTT schema, EMP table for illustration):
select min(extract(year from hiredate)) keep (dense_rank last order by count(empno)) as yr,
max(count(empno)) as emp_count
from emp
group by extract(year from hiredate)
;
YR EMP_COUNT
---- ---------
1981 10
There are two problems with this solution. First, many developers (including many experienced ones) seem unaware of the FIRST/LAST function, or otherwise unwilling to use it. The other, more serious problem is that in this problem it is possible that there are several years with the same, highest number of hires. The problem requirement must be more detailed than in the Original Post. What is the desired output when there are ties for first place?
The query above returns the earliest of all the different years when the max hires were achieved. Change MIN in the SELECT clause to MAX and you will get the most recent year when the highest number of hires happened. However, often we want a query that, in the case of ties, will return all the years tied for most hires. That cannot be done with the FIRST/LAST function.
For that, a compact solution would add an analytic function to your original query, to rank the years by number of hires. Then in an outer query just filter for the rows where rank = 1.
select yr, emp_count
from (
select extract(year from hiredate) as yr, count(empno) as emp_count,
rank() over (order by count(empno) desc) as rnk
from emp
group by extract(year from hiredate)
)
where rnk = 1
;
Or, using the max() analytic function in the SELECT clause of the subquery (instead of a rank-type analytic function):
select yr, emp_count
from (
select extract(year from hiredate) as yr, count(empno) as emp_count,
max(count(empno)) over () as max_count
from emp
group by extract(year from hiredate)
)
where emp_count = max_count
;
I assume you want a single row showing the year most people where hired:
SELECT * FROM (
SELECT EXTRACT (YEAR FROM e1.empl_date) AS YEAR,
COUNT(e1.id_empl) AS EMPL_NUMBER
FROM employees e1
GROUP BY EXTRACT (YEAR FROM e1.empl_date)
ORDER BY COUNT(*))
WHERE ROWNUM=1;