how to get second highest salary department wise without using analytical functions? - sql

Suppose we have 3 employees in each department.we have total 3 departments . Below is the sample source table
Emp deptno salary
A 10 1000
B 10 2000
C 10 3000
D 20 7000
E 20 9000
F 20 8000
G 30 17000
H 30 15000
I 30 30000
Output
B 10 2000
F 20 8000
G 30 17000
With using analytic function dense_rank we can achive the second highest salary dept wise.
Can we achieve this without using ANY analytic function ???
Is Max() is also analytic function ??

It is a pain, but you can do it. The following query gets the second highest salary:
select t.deptno, max(t.salary) as maxs
from table t
where t.salary < (select max(salary)
from table t2
where t2.deptno = t.deptno
)
group by t.deptno;
You can then use this to get the employee:
select t.*
from table t join
(select t.deptno, max(t.salary) as maxs
from table t
where t.salary < (select max(salary)
from table t2
where t2.deptno = t.deptno
)
group by t.deptno
) tt
on t.deptno = tt.deptno and t.salary = tt.maxs;

Create table and insert dummy data
CREATE TABLE #Employee
(
Id Int,
Name NVARCHAR(10),
Sal int,
deptId int
)
INSERT INTO #Employee VALUES
(1, 'Ashish',1000,1),
(2,'Gayle',3000,1),
(3, 'Salman',2000,2),
(4,'Prem',44000,2)
Query to get result
;WITH cteRowNum AS (
SELECT *,
DENSE_RANK() OVER(PARTITION BY deptId ORDER BY Sal DESC) AS RowNum
FROM #Employee
)
SELECT *
FROM cteRowNum
WHERE RowNum = 2;

This will give you 2nd highest salary in each department:
SELECT a.Emp, a.deptno, a.salary
FROM Emp a
WHERE 1 = (SELECT COUNT(DISTINCT salary)
FROM Emp b
WHERE b.salary > a.salary AND a.deptno = b.deptno)
group by a.deptno

Solution using Correlated Subquery:
SELECT * FROM emp e1 WHERE 2 = (SELECT COUNT(DISTINCT sal)
FROM emp e2
WHERE e1.sal <= e2.sal
AND e1.deptno = e2.deptno
);

Quite straightforward and declarative, but slow
select
t1.*
from
#tmp t1
inner join #tmp h1 on h1.dept = t1.dept and h1.emp <> t1.emp
left outer join #tmp h2 on h2.dept = h1.dept and h2.salary > h1.salary
left outer join #tmp t2 on t1.dept = t2.dept and t2.salary > t1.salary and t2.emp <> h1.emp
where
t2.emp is null and h2.emp is null

You can find 2nd highest salary something like this:
select max(a.Salary),a.Deptno from Employee a join (select MAX(salary) salary
from Employee group by Deptno) b on a.Salary < b.salary group by a.Deptno
And no MAX() is not an analytic function.
Reference

On MySQL this how you can get second highest salary, given table name is salaries:
By Nested Queries: (where you can change offset 0/1/2 for first, second and third place respectively)
select
*
from
salaries as t1
where
t1.salary = (select
salary
from
salaries
where
salaries.deptno = t1.deptno ORDER by salary desc limit 1 offset 1);
or might be by creating rank: (where you can change rank= 1/2/3 for first, second and third place respectively)
SET #prev_value = NULL;
SET #rank_count = 0;
select * from
(SELECT
s.*,
CASE
WHEN #prev_value = deptno THEN #rank_count := #rank_count + 1
WHEN #prev_value := deptno THEN #rank_count := 1
ELSE #rank_count := 1
END as rank
FROM salaries s
ORDER BY deptno, salary desc) as t
having t.rank = 2;

SQL Query:
select TOP 2 max(salary),Emp from EMployee where deptno='your_detpno'

Very simple logic.
Please try:
SELECT dept as dd, ( SELECT ee.salary FROM `employees` as ee WHERE ee.dept=dd
ORDER BY ee.salary DESC LIMIT 1,1 ) as sndHigh
FROM `employees`
WHERE 1
GROUP BY dept

select min(salary),deptno from
(SELECT distinct top 2 salary,deptno from table ORDER BY salary DESC)
as a
group by deptno

CREATE TABLE Employee
([Name] varchar(1), [Dept] varchar(1), [Salary] int)
;
INSERT INTO Employee
([Name], [Dept], [Salary])
VALUES
('a', 'M', 20),
('b', 'M', 25),
('c', 'M', 30),
('d', 'C', 44),
('e', 'C', 45),
('f', 'C', 46),
('g', 'H', 20)

Related

How to compare column data with entire table data

I have sample result set :
declare #Emp Table (Emp_Name VARCHAR(10),Dept_name VARCHAR(10),Subjects VARCHAR(10),Score VARCHAR(10))
Insert into #EMP(Emp_Name,Dept_name,Subjects,Score)VALUES ('MOHAN','LANGUAGES','English',35)
Insert into #EMP(Emp_Name,Dept_name,Subjects,Score)VALUES ('MOHAN','LANGUAGES','TELUGU',35)
Insert into #EMP(Emp_Name,Dept_name,Subjects,Score)VALUES ('MOHAN','SCIENCE','BIOLOGY',35)
Insert into #EMP(Emp_Name,Dept_name,Subjects,Score)VALUES ('MOHAN','SCIENCE','PHYSICS',35)
Insert into #EMP(Emp_Name,Dept_name,Subjects,Score)VALUES ('MOHAN','SOCIAL','ECONOMICS',35)
Insert into #EMP(Emp_Name,Dept_name,Subjects,Score)VALUES ('MOHAN','SOCIAL','CIVICS',35)
Insert into #EMP(Emp_Name,Dept_name,Subjects,Score)VALUES ('MOHAN','SOCIAL','ECONOMICS',35)
Sample Data :
Emp_Name Dept_name Subjects Score
MOHAN LANGUAGES English 35
MOHAN LANGUAGES TELUGU 35
MOHAN SCIENCE BIOLOGY 35
MOHAN SCIENCE PHYSICS 35
MOHAN SOCIAL ECONOMICS 35
MOHAN SOCIAL CIVICS 35
MOHAN SOCIAL ECONOMICS 35
Need to compare entire table data with each column data :
Select COUNT(*) ALL_COl FROM (
select Emp_Name,Dept_name,Subjects,Score from #Emp )T
Select Count(*) Without_Subject_Col FROM (
select DISTINCT Emp_Name,Dept_name,Score from #Emp)TT
Select count(*) Without_Dept_Col from (
select DISTINCT Emp_Name,Subjects,Score from #Emp )TTT
Select COUNT(*) Without_Dept_Subject_Col from (
select DISTINCT Emp_Name,Score from #Emp )TTTT
How can I get the output like this :
ALL_COL Without_Subject_Col Without_Dept_Col Without_Dept_Subject_Col
7 3 6 1
Suggest me the way how to achieve it
You could just OUTER APPLY the counts?
select top 1 a.ALL_COl,
aa.Without_Subject_Col,
aaa.Without_Dept_Col,
aaaa.Without_Dept_Subject_Col
from #EMP e
outer apply
(Select COUNT(*) ALL_COl FROM (
select Emp_Name,Dept_name,Subjects,Score from #Emp )T ) A
outer apply
(Select Count(*) Without_Subject_Col FROM (
select DISTINCT Emp_Name,Dept_name,Score from #Emp)TT ) AA
outer apply
(Select count(*) Without_Dept_Col from (
select DISTINCT Emp_Name,Subjects,Score from #Emp )TTT)AAA
outer apply
(Select COUNT(*) Without_Dept_Subject_Col from (
select DISTINCT Emp_Name,Score from #Emp )TTTT)AAAA
Just select the counts as sub-queries
SELECT
(
SELECT COUNT(*) FROM #Emp
) AS ALL_Col,
(
SELECT COUNT(*)
FROM (SELECT DISTINCT Emp_Name, Dept_name, Score FROM #Emp) q
) AS Without_Subject_Col,
(
SELECT COUNT(*)
FROM (SELECT DISTINCT Emp_Name, Subjects, Score FROM #Emp) q
) AS Without_Dept_Col,
(
SELECT COUNT(*)
FROM (SELECT DISTINCT Emp_Name, Score FROM #Emp) q
) AS Without_Dept_Subject_Col;
A test on rextester here
No idea why you want this but using count with distinct makes this pretty simple.
Select ALL_COL = COUNT(*)
, Without_Subject_Col = Count(DISTINCT Emp_Name + Dept_name + convert(varchar(5), Score))
, Without_Dept_Col = count(distinct Emp_Name + Subjects + convert(varchar(5), Score))
, Without_Dept_Subject_Col = count(distinct Emp_Name + convert(varchar(5), Score))
from #Emp

Order by clause with union in SQL select query

Is there a way to order the union of two select all statements based on table column values.
My sample code is:
SELECT * FROM emp WHERE mgr='7839'
UNION
SELECT * FROM emp WHERE NOT EXISTS (SELECT * FROM emp WHERE mgr='7839')
AND empno='7839'
order by ename;
This code is showing error as:invalid identifier 'ENAME'.
I am not using specific columns in select statement instead of * since there are more than 10 columns in the table and the code looks so big.
But ename is a column in emp.
will you consider small change in your query.
SELECT * FROM
(SELECT * FROM emp WHERE mgr='7839'
UNION
SELECT * FROM emp WHERE NOT EXISTS (SELECT * FROM emp WHERE mgr='7839')
AND empno='7839') AA
order by AA.ename;
Why would use use union for this? Just do:
SELECT e.*
FROM emp e
WHERE e.mgr = '7839' OR
(NOT EXISTS (SELECT 1 FROM emp e2 WHERE e2.mgr = '7839') AND
empno = '7839'
)
ORDER BY ename;

SELECT data from another table with max date

I need help with a SELECT query in which I need to join to another table and get record with max date. I have created sample to demonstrate. My last SELECT is incorrect and I need guidance to fix it or if there is a better way in Sql server 2014
CREATE TABLE #EmpTable
(
EmpNum INT,
colA VARCHAR(5) NULL,
colB VARCHAR(5) NULL
)
CREATE TABLE #EmpDetailTbl
(
EmpNum INT,
Name VARCHAR(10),
Department VARCHAR(10) NULL,
ReportDate DATETIME NOT NULL
)
INSERT INTO #EmpTable
VALUES (101, 'val11', 'Val21'), (102, 'val12', 'Val21'), (103, 'val13', 'Val23');
INSERT INTO #EmpDetailTbl
VALUES (101, 'emp101', 'Dept1', '05/01/2018'), (101, 'emp101', 'Dept2', '06/01/2018'),
(101, 'emp101', 'Dept1', '05/01/2017'), (102, 'emp102', 'Dept3', '04/01/2018'),
(102, 'emp102', 'Dept1', '05/01/2018')
--select * from #EmpDetailTbl
--select * from #EmpTable
SELECT
a.EmpNum, Name, ColA, ColB, Department
FROM
#EmpTable a
LEFT OUTER JOIN
#EmpDetailTbl b ON a.EmpNum = b.EmpNum
AND ReportDate = (SELECT MAX(ReportDate)
FROM #EmpDetailTbl
a.EmpNum = b.EmpNum)
Use rank() analytic function to enumerate rows based on their report dates and then pick only the first for each employee:
SELECT EmpNum, Name, ColA, ColB, Department
FROM (
SELECT
a.EmpNum, b.Name, a.ColA, a.ColB, b.Department,
rank() over (partition by a.EmpNum order by b.ReportDate desc) as rn
FROM #EmpTable a
LEFT JOIN #EmpDetailTbl b ON
a.EmpNum = b.EmpNum
) t
WHERE rn = 1;
This query will take care of ties: in case there is more than 1 record for employee with the same date which also happens to be maximum it will show them all.
If I understand correctly you can try this to get MaxDate row.
using a exists to get MAX(b1.ReportDate) by EmpNum
select a.EmpNum, Name, ColA, ColB, Department
FROM #EmpTable a
LEFT JOIN #EmpDetailTbl b on a.EmpNum = b.EmpNum
WHERE exists (
SELECT 1
FROM #EmpDetailTbl b1
WHERE b1.EmpNum = b.EmpNum
GROUP BY b1.EmpNum
HAVING MAX(b1.ReportDate) = b.ReportDate
)
sqlfiddle: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=a3deade95dbd25d9cadadee37e16d9c6
Result
EmpNum Name ColA ColB Department
101 emp101 val11 Val21 Dept2
102 emp102 val12 Val21 Dept1
103 emp103 val13 Val23 Dept1
ReportDate SHOULD BE a.ReportDate

Max sal to print base on manager_id

I have this table:
id mgr_id sal
1 5 5000
2 5 6000
3 6 7000
4 6 8000
I expect this output:
id mgr_id sal MaX_sal
1 5 5000 6000
2 5 6000 6000
3 6 7000 8000
4 6 8000 8000
Based on mgr_id select max sal and print in front of id.
Thanks in advance.
Possible would be a subselect inside the select (although a bit slow as all subselects are):
select id, mgr_id, sal, (select max(sal) from mytable n2
where n2.mgr_id = mytable.mgr_id) as max_sal from mytable
Create a subtable grouping by mrg_id to get the max Sal per mrg_id and then left join it with the principal table on mrg_id -- Try this :
declare #table table (id int,mrg_id int,sal decimal(10,0))
insert into #table
select 1, 5, 5000 union all
select 2, 5, 6000 union all
select 3, 6, 7000 union all
select 4, 6, 8000
/***** change #table with your table name *****/
SELECT
t.id,
t.mrg_id,
t.sal,
t1.max_sal
FROM #table t
LEFT JOIN
(SELECT
mrg_id,
MAX(sal) max_sal
FROM #table
group by
mrg_id) t1
on t.mrg_id=t1.mrg_id
This would work even in SQL Server 6.5
SELECT id, mgr_id, sal,
(
SELECT MAX(sal)
FROM employees mgr
WHERE mgr.mgr_id = emp.mgr_id
) AS max_sal
FROM
employees emp
This works on SQL Server 2005:
WITH max_sals AS
(
SELECT mgr_id, MAX(sal) AS max_sal
FROM employees
GROUP BY mgr_id
)
SELECT emp.*, max_sals.max_sal
FROM
employees emp
LEFT JOIN max_sals
ON emp.mgr_id = max_sals.mgr_id
and this using partiton:
SELECT emp.*, MAX(emp.sal) OVER (PARTITION BY emp.mgr_id) AS max_sal
FROM employees emp
Sample data and table:
CREATE TABLE employees
(
id int,
mgr_id int,
sal int
)
INSERT INTO employees VALUES
(1,5,5000),
(2,5,6000),
(3,6,7000),
(4,6,8000)
With SQL Server 2012 simply use this query:
SELECT d.id, d.mgr_id, d.sal, MAX(sal) over(partition by mgr_id)
FROM #data d
You can use this query with SQL Server <2012:
SELECT d.id, d.mgr_id, d.sal, m.mx
FROM #data d
INNER JOIN (
SELECT mgr_id, mx = MAX(sal) FROM #data
GROUP BY mgr_id
) m
ON m.mgr_id = d.mgr_id;
Another option with SQL Server >= 2005:
SELECT d.id, d.mgr_id, d.sal, m.mx
FROM #data d
CROSS APPLY (SELECT mx = MAX(sal) FROM #data m WHERE m.mgr_id = d.mgr_id) m(mx)
Sample data:
Declare #data table([id] int, [mgr_id] int, [sal] int);
INSERT INTO #data([id], [mgr_id], [sal])
VALUES
(1, 5, 5000),
(2, 5, 6000),
(3, 6, 7000),
(4, 6, 8000)
;
Try Cross Apply,
SELECT id,
mgr_id,
sal,
Max_sal
FROM #your_table A
CROSS APPLY (SELECT Max(SAL) AS Max_sal
FROM #your_table B
WHERE B.mgr_id = A.mgr_id)cs

I want to make an insert using union all which has a column getting values from a sequence

I tried
INSERT INTO my_test_one (rollno,name, sirname, Dept)
(select rollno_seq.nextval,'name1','sirname1', Dept
FROM my_test_one_backup
WHERE dept = 500
UNION ALL
select rollno_seq.nextval,'name1','sirname1', Dept
FROM my_test_one_backup
WHERE dept = 501 );
While doing this I am getting the error
Error report:
SQL Error: ORA-02287: sequence number not allowed here
02287. 00000 - "sequence number not allowed here"
Don't use a UNION but a single SELECT and OR in this case:
SELECT rollno_seq.nextval,'name1','sirname1', Dept
FROM my_test_one_backup
WHERE dept = 500 OR dept = 501
Try:
INSERT INTO my_test_one
(rollno, name, sirname, Dept)
SELECT rollno_seq.nextval,
name1,
sirname1,
dept
FROM (select 'name1' as name1,'sirname1' as sirname1, Dept
FROM my_test_one_backup
WHERE dept = 500
UNION ALL
select 'name1','sirname1', Dept
FROM my_test_one_backup
WHERE dept = 501 );
Edit: Better still, use an OR like CodeBrickie says or and IN statement.
WHERE dept IN (500, 501);
Edit2:
Currently you are selecting 'name1', 'sirname1' as literals so each row returned will insert the next sequence number, 'name1', 'sirname1' and whatever the value of DEPT column is.
If your table has columns called name1 and sirname1 then you'll need to remove the single quotes (and you wouldn't need the column alias either) e.g.:
INSERT INTO my_test_one
(rollno, name, sirname, Dept)
SELECT rollno_seq.nextval,
name1,
sirname1,
dept
FROM (select name1, sirname1, Dept
FROM my_test_one_backup
WHERE dept = 500
UNION ALL
select name1, sirname1, Dept
FROM my_test_one_backup
WHERE dept = 501 );
Or
INSERT INTO my_test_one
(rollno, name, sirname, Dept)
SELECT rollno_seq.nextval,
name1,
sirname1,
dept
FROM my_test_one_backup
WHERE dept IN (500, 501);
You can't use a sequence in unioned selects, so you'll need to put the union in a sub-query and the sequence in the outer query:
INSERT INTO my_test_one (rollno,name, sirname, Dept)
select rollno_seq.nextval, name1, sirname1, dept
from (SELECT 'name1' as name1,'sirname1' as sirname1, Dept
FROM my_test_one_backup
WHERE dept = 500
UNION ALL
SELECT 'name1','sirname1', Dept
FROM my_test_one_backup
WHERE dept = 501 );
You should also note that, in SQL, double quotes indicate an object name and single quotes denote a string, so 'name1' and 'sirname1' will be static strings, not column references.