I am working on a database query and I am facing issues creating the right query to get required output.
Below is my create table query and insert statements.
create table DETAILS (dep_name varchar(50), emp_name varchar(50), salary int);
insert into DETAILS values ('marketing', 'ravi', 50000);
insert into DETAILS values ('marketing', 'suresh', 25000);
insert into DETAILS values ('marketing', 'mahesh', 60000);
insert into DETAILS values ('legal', 'raja', 40000);
insert into DETAILS values ('legal', 'gavi', 50000);
insert into DETAILS values ('legal', 'rana', 90000);
insert into DETAILS values ('legal', 'jyoti', 60000);
My requirement is to display only top 2 salaried employees from each department. Please see sample output below.
dep_name emp_name salary
legal rana 90000
legal jyoti 60000
marketing mahesh 60000
marketing ravi 50000
I have tried several queries but not able to crack this. Can somebody get me to frame the right query?
If you are using Oracle then it can be done by dense_rank() over (partition by .. order by ..)
If your RDBMS is mysql you cannot use dense_rank() you have to emulate the concept. See this link.
Try:
select
dep_name,
emp_name,
salary
from (
SELECT
#row_number:=CASE
WHEN #dep_name = dep_name THEN #row_number + 1
ELSE 1
END AS num,
#dep_name := dep_name as dep_name,
emp_name,
salary
FROM DETAILS ,(SELECT #dep_name := '', #row_number := 0) as t
ORDER BY dep_name, salary desc
) x
where num < 3
ORDER BY dep_name, salary desc;
sql fiddle demo
This will be one option to do it in MySQL (not tested)
SELECT dep_name, emp_name, salary
FROM (
SELECT
#nr := IF(#old_val = c.col,(#nr +1),1) AS nr,
#old_val:=c.col AS tmp,
c.*
FROM details AS c , (SELECT #nr:=0, #old_val:='') AS TMP
ORDER BY salary DESC
) AS result WHERE nr < 3
Related
I am trying to Get employees who received a raise in 2 consecutive years, in this case employee 1000 is the right answer.
here is the data and the sql i have tried.
EID
SALARY
YEAR
1000
10,000
2016
1000
7,500
2015
1000
6,000
2014
1001
8,000
2016
1001
7,500
2015
1002
7,500
2016
1002
7,500
2015
1002
5,000
2014
1003
6,000
2016
1003
7,000
2015
1003
5,000
2014
i have used following code however it gets only row number by EID and not calcualtion of last year and present year, i need to find employee who got raise in 2 consecutive years.
output
select * ,
row_number() over(partition by eid order by salary and year desc)as rn
from gs;
You can do it using the LEAD window function that compute the two consecutive previous value of the salary. Then you can check how many employees have at least one row with salary1 < salary2 < salary3.
SELECT DISTINCT
eid
FROM (
SELECT
eid,
year,
salary,
(LEAD(salary, 1) OVER(PARTITION BY eid ORDER BY year DESC)) AS prev_salary1,
(LEAD(salary, 2) OVER(PARTITION BY eid ORDER BY year DESC)) AS prev_salary2
FROM
employees
) consecutive3
WHERE
salary > prev_salary1
AND
prev_salary1 > prev_salary2
The assumption is that there are no missing years for which a salary of a dependent was not recorded.
Here's the fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=8c0d8a1deec8e77bb32a173656c3e386.
EDIT: Detailed explanation
Let's do the example of Jennifer, who has worked for five years and got these salaries:
2018 -> 65000
2017 -> 55000
2016 -> 50000
She's a candidate for being selected as her salary was raised three times consecutively.
1. LEAD(salary, 1) OVER(PARTITION BY eid ORDER BY year DESC)
Allows you to get the salary for year "Y" and the salary for year "Y-1":
("year" -> "salary", "previous_salary")
2018 -> 65000 , 55000
2017 -> 55000 , 50000
2016 -> 50000 , NULL
2. LEAD(salary, 2) OVER(PARTITION BY eid ORDER BY year DESC)
Allows you to get the salary for year "Y" and the salary for year "Y-1":
("year" -> "salary", "previous_salary", "previous_salary_by_2_years")
2018 -> 65000 , 55000 , 50000
2017 -> 55000 , 50000 , NULL
2016 -> 50000 , NULL , NULL
3. WHERE salary > prev_salary1 AND prev_salary1 > prev_salary2
Some filtering on the employees who
have their year3 salary higher than their year2 salary (salary > prev_salary1)
have their year2 salary higher than their year1 salary (prev_salary1 > prev_salary2)
I know that this has already been answered but here is my take using the lag function to determine if there was an increase from the previous year and ran that twice.
SELECT *
FROM (
SELECT
t2.*,
LAG(increase) over (partition by eid order by year) AS increaseNextYear
FROM (
SELECT
t1.*,
COALESCE(salary - LAG(salary) over (partition by eid order by year), 0) > 0 AS increase
FROM tbl_test t1
) as t2
) t3 where increase AND increaseNextYear
with
dates as
(
select
a.*,
dense_rank() OVER (
partition by eid
order by year desc, salary
)
as rn,
case
when
lead(salary,2)over(partition by eid order by year, salary)
>
lead(salary,1)over(partition by eid order by year, salary)
and
lead(salary,1)over(partition by eid order by year, salary)
>
salary
then
1
else
0
end
as flag
from
employees a
)
select
eid
from
dates
where
rn = 3
and flag = 1
Not a beautiful query, but straight-forward: find employees who had a salary in a year where the salary in the previous year was lower and the salary in the year before that even lower. Using LAG is more elegant, but I thought I'd throw this in, just to show an alternative.
select *
from employee
where exists
(
select null
from gs
where gs.eid = employee.id
and exists
(
select null
from gs prev
where prev.eid = gs.eid
and prev.year = gs.year - 1
and prev.salary < gs.salary
and exists
(
select null
from gs prevprev
where prevprev.eid = prev.eid
and prevprev.year = prev.year - 1
and prevprev.salary < prev.salary
)
)
);
Same thing with a join:
select *
from employee
where exists
(
select null
from gs
join gs prev on prev.eid = gs.eid
and prev.year = gs.year - 1
and prev.salary < gs.salary
join gs prevprev on prevprev.eid = prev.eid
and prevprev.year = prev.year - 1
and prevprev.salary < prev.salary
where gs.eid = employee.id
);
For versions prior to 8.0 (mine is 5.7) which lack the cutting edge features of the newer one, I tried a procedure to accomplish the job. First and foremost, get all the eid which have no less than three years' salary record, which is the minimum requirement of the consecutive bonus. The rest is to fetch and compare using a cursor from the eid pool. The result is stored in a temporary table t .
delimiter //
drop procedure if exists lucky_emp//
create procedure lucky_emp()
begin
declare old_eid int default 0;
declare old_salary int;
declare new_eid int ;
declare new_salary int;
declare bonus_year int;
declare fin bool default false;
declare c cursor for select eid,salary from salary where eid in(select eid from salary group by eid having count(eid)>=3) order by eid,year;
declare continue handler for not found set fin=true;
drop temporary table if exists t ;
create temporary table t (t_eid int);
open c;
lp:loop
fetch c into new_eid ,new_salary;
if fin=true then
leave lp;
end if;
if new_eid !=old_eid then
set old_eid=new_eid,old_salary=0,bonus_year=0;
end if;
if new_salary> old_salary then
set bonus_year=bonus_year+1,old_salary=new_salary;
else
set bonus_year=0;
end if;
if bonus_year=3 and new_eid not in(select t_eid from t) then
insert t values(new_eid);
end if;
end loop lp;
end//
delimiter ;
select * from t ;
Select a.*, b.prev_sal1, b.prev_sal2
from employees a
join (
Select eid ,year,
lag(salary,1) over (partition by eid order by year) as prev_sal1,
lag(salary,2) over (partition by eid order by year) as prev_sal2
from employees ) b
on a.eid=b.eid
and a.year = b.year
where salary>prev_sal1 and prev_sal1>prev_sal2
fiddle: https://dbfiddle.uk/rfGv31zM
I have a student table which has student_id, score and subject
CREATE TABLE IF NOT EXISTS students
(student_id INT(3), subject ,VARCHAR(45), score INT(3) );
The data inserted is
insert into students values(1,'math',70);
insert into students values(1,'science',71);
insert into students values(1,'history',72);
insert into students values(1,'english',73);
insert into students values(1,'kannada',74);
insert into students values(3,'math',50);
insert into students values(3,'science',51);
insert into students values(3,'history',52);
insert into students values(3,'english',53);
insert into students values(3,'kannada',54);
insert into students values(2,'math',60);
insert into students values(2,'science',61);
insert into students values(2,'history',62);
insert into students values(2,'english',63);
insert into students values(2,'kannada',64);
I am getting the required output after using the query,
select student_id,score,subject
from
(select #prev := '', #n:=0) init
join
(select #n := if(subject != #prev , 1, #n+1) as n,
#prev := subject,
student_id,score,subject from students
order by
subject asc,
score desc
) x
where n<=2
order by subject, score desc;
I simply don't understand how this works, why there is a need for the join? Is this a sub-query? Will the statements in the from clause run on every row of data? Some one please explain it to me. I am learning SQL.
Note : I found this query similar to this online and I merely adapted that to suite my requirement ,this is not my work.
The join is just needed so that you can initialize the variables #prev and #n in the query. This needs to be done separately from the query that you're trying to filter. You could do it before the query instead, but this keeps everything together in one self-contained query.
SET #prev = '';
SET #n = 0;
SELECT student_id, score, subject
FROM
(select #n := if(subject != #prev , 1, #n+1) as n,
#prev := subject,
student_id,score,subject from students
order by
subject asc,
score desc
) x
where n<=2
order by subject, score desc;
In this case the subquery is used so that you can select the n <= 2 rows that you want.
I'm trying in MySql to count the number of users created each day and then get an accumulative figure on a row by row basis. I have followed other suggestions on here, but I cannot seem to get the accumulation to be correct.
The problem is that it keeps counting from the base number of 200 and not taking account of previous rows.
Where was I would expect it to return
My Sql is as follows;
SELECT day(created_at), count(*), (#something := #something+count(*)) as value
FROM myTable
CROSS JOIN (SELECT #something := 200) r
GROUP BY day(created_at);
To create the table and populate it you can use;
CREATE TABLE myTable (
id INT AUTO_INCREMENT,
created_at DATETIME,
PRIMARY KEY (id)
);
INSERT INTO myTable (created_at)
VALUES ('2018-04-01'),
('2018-04-01'),
('2018-04-01'),
('2018-04-01'),
('2018-04-02'),
('2018-04-02'),
('2018-04-02'),
('2018-04-03'),
('2018-04-03');
You can view this on SqlFiddle.
Use a subquery:
SELECT day, cnt, (#s := #s + cnt)
FROM (SELECT day(created_at) as day, count(*) as cnt
FROM myTable
GROUP BY day(created_at)
) d CROSS JOIN
(SELECT #s := 0) r;
GROUP BY and variables have not worked together for a long time. In more recent versions, ORDER BY also needs a subquery.
i've created a employee attendance table having 3 columns emp_name, emp_present & date,
there is no primary key in mytable
i'm using this query to get total presents of employees
SELECT `emp_name`, `emp_present`,(select sum(emp_present='present')
from empattendance) as 'total presents', `date` FROM `empattendance` where date=curdate()
but i want to get total presents and 'total absents' of each indiviual shown with them in total presents and 'total absents' column respectively. I'm a newbie in database and don't have much knowledge about how to sort this problem.....
You can use conditional aggregation, to get the count of rows where employee was present and the count of rows where employee was absent like this:
SELECT emp_name,
SUM(emp_present = 'present') AS numPresence,
SUM(emp_present = 'absent') AS numAbsence
FROM employees
GROUP BY emp_name;
EDIT:
If you want a running total of presence and absences, you'll need to use a variable:
SET #present := 0;
SET #absent := 0;
SELECT emp_name, dateColumn,
IF(emp_present = 'present', #present := #present + 1, #present) AS numPresence
IF(emp_present = 'absent', #absent := #absent+ 1, #absent) AS numAbsense
FROM myTable
ORDER BY dateColumn;
I'm trying to insert different numbers of rows into my table by adjusting #s:=1 and #s<1000 but it only inserts one at a time. What am I doing wrong?
INSERT INTO
rent
(
id
)
select #s:=#s+1 as seq
FROM (SELECT #s:=1) AS baseview, rent
WHERE #s<1000
ORDER by #s ASC;
Can you try this?:
INSERT INTO
rent
(
id
)
SELECT seq FROM (
select #s:=#s+1 as seq
FROM (SELECT #s:=1) AS baseview, rent
WHERE #s<1000
ORDER by #s ASC
) seqSource;