I have a table like below. I need to find out the employes who have rank R1 but never have rank C1 and C2.
Id ECode Name Rank
1 EMP1 AA R1
2 EMP2 BB R1
3 EMP1 AA R2
4 EMP1 AA C1
5 EMP1 AA C2
6 EMP1 AA C3
7 EMP2 BB C4
8 EMP2 BB C5
9 EMP3 CC R1
10 EMP3 CC C1
11 EMP3 CC C2
12 EMP3 CC C4
13 EMP4 DD R1
14 EMP4 DD C3
One approach uses aggregation by employee:
SELECT ECode, Name
FROM yourTable
GROUP BY ECode, Name
HAVING
SUM(CASE WHEN Rank = 'R1' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN Rank IN ('C1', 'C2') THEN 1 ELSE 0 END) = 0
Try this:
SELECT *
FROM EMPLOYES A
WHERE RANK = 'R1'
AND NOT EXISTS(SELECT 1
FROM EMPLOYES B
WHERE B.ECODE = A.ECODE
AND RANK IN ('C1','C2')
AND ROWNUM = 1)
I would do:
SELECT ecode, name
FROM t
WHERE rank IN ('R1', 'C1', 'C2')
GROUP BY ecode, name
HAVING MIN(rank) = MAX(rank) AND MAX(rank) = 'R1';
Evidently use NOT EXISTS :
select *
from mytable t
where t.rank = 'R1'
and not exists ( select ECode from mytable where ECode = t.ecode and rank in ('C1','C2') );
You can use combination of exists and not exists:
select *
from table t
where exists (select 1 from table where ECode = t.ECode and Rank = 'R1') AND
not exists (select 1 from table where ECode = t.ECode and Rank IN ('C1', 'C2'))
Select * from (tablename)
where Rank = 'R1'
and Rank not in (Select Rank from (tablename)
where Rank = 'C1'
or Rank = 'C2')
Related
I have two tables:
Project:
Id
Name
1
ABC
2
DEF
3
GHI
4
JKL
5
MNO
6
PQR
Attachment:
Id
Status
ProjectId
1
a1
1
2
a1
1
3
a2
2
4
a2
2
5
a1
3
6
a2
3
7
a1
4
8
a2
4
9
a3
4
10
a1
5
11
a2
5
12
a4
5
I'd like to get projectnames which has assignments only with statuses a1 and a2 (must have both of them) and doesn't havve assignments in statuses a3 and a4.
So the result should be:
GHI
What I've tried wass:
select distinct
p.Name
from
Attachment a
inner join Project p on p.Id = a.ProjectId
group by
p.Name, a.status
having (a.Status = 'a1' or a.Status = 'a2'
You can use exist|not exists keywords to achieve this.
select [Name] from Project t1
where
exists (select 1 from Attachment where [Status] = 'a1' and ProjectId = t1.Id)
and
exists (select 1 from Attachment where [Status] = 'a2' and ProjectId = t1.Id)
and
not exists (select 1 from Attachment where [Status] = 'a3' and ProjectId = t1.Id)
and
not exists (select 1 from Attachment where [Status] = 'a4' and ProjectId = t1.Id)
use String_agg,Subquery and join to get your desired result
SELECT name
FROM (SELECT P.name,
String_agg(status, ',')
within GROUP (ORDER BY status) Status
FROM project P
join attachment A
ON A.projectid = P.id
GROUP BY P.name) t
WHERE status = 'a1,a2'
dbfiddle
You can use count distinct in combination with NOT IN:
select P.name
from Project P
left join Attachment A on P.id = A.ProjectId
where p.id not in (select A2.ProjectId
from Attachment A2
where A2.status in ('a3', 'a4'))
and A.status in ('a1', 'a2')
group by P.name
having count(distinct A.status) = 2
Here is a demo
Can someone help here.
I have table employee. I want to find out list of employees having 2 products(P1,P2).
Emp_id Prd_id
E1 P1
E1 P2
E2 P1
E2 P2
E2 P3
E3 P1
E3 P3
E4 P1
E4 P2
So, I want output as
Emp_id
E1
E4
SELECT emp_id
FROM employee
GROUP BY emp_id
HAVING COUNT(CASE WHEN prd_id 'p1' THEN 1 END) > 0 -- p1 exists
AND COUNT(CASE WHEN prd_id 'p2' THEN 1 END) > 0 -- p2 exists
AND COUNT(CASE WHEN prd_id NOT IN ('p1', 'p2') THEN 1 END) = 0 -- but not other
Easier to extend to a larger number of products:
SELECT emp_id
FROM
( -- get a unique list first
SELECT DISTINCT emp_id, prd_id
FROM employee
) AS dt
GROUP BY emp_id
HAVING SUM(CASE WHEN prd_id IN ('p1', 'p2') THEN 1 ELSE -1 END) = 2
If the requirement is to find employees with any two products, you could use a having condition to count them:
SELECT emp_id
FROM employees
GROUP BY emp_id
HAVING COUNT(*) = 2
If those products must be p1 and p2, you could add another condition on that:
SELECT emp_id
FROM employees
GROUP BY emp_id
HAVING COUNT(*) = 2 AND
COUNT(CASE WHEN prd_id IN ('p1', 'p2') THEN 1 END) = 2
T-SQL, need help with combining 2 values in an column when Pivoting.
I have a Employee table with the below data -
EmpId Status L#
E1 A 1
E1 B 1
E2 A 2
E2 B 2
E3 B 3
E3 C 3
E3 D 3
and a Supervisor Table -
EmpId Sup
E1 S1
E2 S2
E3 S3
I would like to combine the values for L# when the Status is B or C
EmpId Sup A B,C D
E1 S1 1 1 0
E2 S2 1 2 0
E3 S3 0 2 1
Just use conditional aggregation. I am a little unclear what the results are. The following counts the statuses:
select s.empid, s.sup,
sum(case when status = 'A' then 1 else 0 end) as a,
sum(case when status in ('B', 'C') then 1 else 0 end) as bc,
sum(case when status = 'D' then 1 else 0 end) as d
from employee e join
supervisor s
on e.empid = s.empid
group by s.empid, s.sup;
This may help
DECLARE #TableEmp TABLE (EmpId VARCHAR(10), Status VARCHAR(10), L# INT)
INSERT INTO #TableEmp VALUES
('E1', 'A', 1 ),
('E1', 'B', 1 ),
('E2', 'A', 2 ),
('E2', 'B', 2 ),
('E3', 'B', 3 ),
('E3', 'C', 3 ),
('E3', 'D', 3 )
DECLARE #TableSup TABLE(EmpId VARCHAR(10), Sup VARCHAR(10))
INSERT INTO #TableSup VALUES
('E1', 'S1'),
('E2', 'S2'),
('E3', 'S3')
SELECT EmpId, Sup ,ISNULL(A,0) A, ISNULL(B,0) B,ISNULL(C,0) C,ISNULL(D,0) D
FROM(
SELECT e.EmpId, Sup,Status,ISNULL(L#,0) L#
FROM #TableEmp e
LEFT JOIN #TableSup s ON e.EmpId = s.EmpId
) p
PIVOT(
SUM(L#)
FOR Status IN (A, B, C,D)
) piv
The following query should do what you want:
CREATE TABLE #emp (EmpID VARCHAR(10), [Status] VARCHAR(10), L# INT)
CREATE TABLE #sup (EmpID VARCHAR(10), [Sup] VARCHAR(10))
INSERT INTO #emp VALUES
('E1','A',0),
('E1','B',1),
('E2','A',2),
('E2','B',2),
('E3','B',3),
('E3','C',5),
('E3','D',5)
INSERT INTO #sup VALUES
('E1','S1'),
('E2','S2'),
('E3','S3')
SELECT * FROM (
SELECT e.EmpID, s.Sup, t.[Status], COUNT(L#) AS [Cnt]
FROM #emp e
INNER JOIN #sup s ON e.EmpID = s.EmpID
CROSS APPLY (VALUES(CASE WHEN e.[Status] IN ('B','C') THEN 'B,C' ELSE e.[Status] END)) AS T([Status])
GROUP BY e.EmpID, s.Sup, t.[Status] ) A
PIVOT (MAX([Cnt]) FOR [Status] IN ([A],[B,C],[D])) pvt
The result is as below,
EmpID Sup A B,C D
E1 S1 1 1 NULL
E2 S2 1 1 NULL
E3 S3 NULL 2 1
Following is my table in SQL Server
ID NAME SALARY
10 A 10
10 B 5
10 C 20
10 D 20
11 E 40
11 F 40
11 G 30
11 H 50
12 I 50
12 J 35
My objective is to add six other columns first_value,second_value,third_value, first_rank, second_rank, third_rank corresponding to each ID.
The output should look like as following:
ID NAME SALARY R1 R2 R3 R1_name R2_name R3_name
10 A 10 5 10 20 B A C
10 B 5 5 10 20 B A C
10 C 20 5 10 20 B A C
10 D 20 5 10 20 B A C
11 E 40 30 40 40 G E F
11 F 40 30 40 40 G E F
11 G 30 30 40 40 G E F
11 H 50 30 40 40 G E F
12 I 50 35 50 NULL J I NULL
12 J 35 35 50 NULL J I NULL
Following is the insert query:
CREATE TABLE EMP(ID NVARCHAR(10), NAME NVARCHAR(20), SALARY MONEY)
INSERT INTO EMP
VALUES
(10, 'A', 10),(11, 'E',40 ),(10,'B',5),(11,'F',40),(12,'I',50)
,(10,'C',20),(11,'G',30),(12,'J',35),(10,'D',20),(11,'H',50)
Thanks in advance.
;WITH TOUpdate AS
(
SELECT ID,
MAX(case when RN=1 THEN SALARY ELSE 0 END) AS R1,
MAX(case when RN=2 THEN SALARY ELSE 0 END) AS R2,
MAX(case when RN=3 THEN SALARY ELSE 0 END) AS R3,
MAX(case when RN=1 THEN Name ELSE NULL END) AS R1_Name,
MAX(case when RN=2 THEN Name ELSE NULL END) AS R2_Name,
MAX(case when RN=3 THEN Name ELSE NULL END) AS R3_Name
FROM(
SELECT *,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY SALARY) AS RN
FROM #EMP
) X
WHERE X.RN<4
GROUP BY ID
)
SELECT *
FROM #EMP E
INNER JOIN TOUpdate U
ON E.ID=U.ID
Quite ugly but it works, you could try
DECLARE #EMP AS TABLE(ID NVARCHAR(10), NAME NVARCHAR(20), SALARY MONEY)
INSERT INTO #EMP
VALUES
(10, 'A', 10),(11, 'E',40 ),(10,'B',5),(11,'F',40),(12,'I',50)
,(10,'C',20),(11,'G',30),(12,'J',35),(10,'D',20),(11,'H',50)
;WITH temp AS
(
SELECT e.* , row_number() over(partition by e.ID ORDER BY e.SALARY ASC) AS Rn
FROM #EMP e
)
SELECT e.*, t1.SALARY AS R1, t1.Name AS R1_Name, t2.SALARY AS R2, t2.Name AS R2_Name, t3.SALARY AS R3, t3.Name AS R3_Name
FROM #EMP e
LEFT JOIN temp t1 ON e.ID = t1.ID AND t1.Rn = 1
LEFT JOIN temp t2 ON e.ID = t2.ID AND t2.Rn = 2
LEFT JOIN temp t3 ON e.ID = t3.ID AND t3.Rn = 3
ORDER BY e.ID ASC
Demo link: Rextester
We can actually achieve your desired output by doing a single join to a CTE which ranks the salaries for each ID.
WITH cte1 AS (
SELECT ID, NAME, SALARY,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY SALARY) rn
FROM EMP
),
cte2 AS (
SELECT
ID,
MAX(CASE WHEN rn = 1 THEN SALARY END) AS R1,
MAX(CASE WHEN rn = 2 THEN SALARY END) AS R2,
MAX(CASE WHEN rn = 3 THEN SALARY END) AS R3,
MAX(CASE WHEN rn = 1 THEN NAME END) AS R1_name,
MAX(CASE WHEN rn = 2 THEN NAME END) AS R2_name,
MAX(CASE WHEN rn = 3 THEN NAME END) AS R3_name
FROM cte1
GROUP BY ID
)
SELECT
t1.ID,
t1.NAME,
t1.SALARY,
t2.*
FROM EMP t1
INNER JOIN cte2 t2
ON t1.ID = t2.ID
Output:
Demo here:
Rextester
This is what I have
ID Name DateTime Value Group
1 Mark 1/1/2010 0 1
2 Mark 1/2/2010 1 1
3 Mark 1/3/2010 0 1
4 Mark 1/4/2010 0 2
40 Mark 1/5/2010 1 2
5 Mark 1/9/2010 1 2
6 Mark 1/6/2010 1 2
7 Kelly 1/1/2010 0 3
8 Kelly 1/2/2010 1 3
9 Kelly 1/3/2010 1 3
10 Nancy 1/4/2010 0 4
11 Nancy 1/5/2010 0 4
12 Nancy 1/6/2010 1 5
13 Nancy 1/7/2010 0 5
What I want is to get the rows per "name" per "group" with minimum datetime after the value becomes 1. From the above example, I would need to get
3 Mark 1/3/2010 0 1
6 Mark 1/6/2010 1 2
9 Kelly 1/3/2010 1 3
13 Nancy 1/7/2010 0 5
Based on the description of your rules, I believe the output will actually be a bit different since 2010-01-05 was the first DateTime where the Value = 1 for Group 2 for Mark.
ID Name DateTime Value Group
3 Mark 2010-01-03 0 1
6 Mark 2010-01-06 1 2
9 Kelly 2010-01-03 1 3
13 Nancy 2010-01-07 0 5
The below code will work as demonstrated in this SQLFiddle.
SELECT sub.ID
, sub.Name
, sub.[DateTime]
, sub.Value
, sub.[Group]
FROM
(SELECT t.ID
, t.Name
, t.[DateTime]
, t.Value
, t.[Group]
, SequentialOrder = ROW_NUMBER() OVER
(PARTITION BY t.Name, t.[Group]
ORDER BY t.[DateTime])
FROM Test t
JOIN
(SELECT Name
, [Group]
, MinimumDateTime = MIN([DateTime])
FROM Test
WHERE Value = 1
GROUP BY Name
, [Group]) mint
ON t.Name = mint.Name
AND t.[Group] = mint.[Group]
WHERE t.[DateTime] > mint.MinimumDateTime) sub
WHERE sub.SequentialOrder = 1
ORDER BY ID;
Below is my query and it goes on assumption that records are received in order of their dates
WITH TBL_1 AS
(
SELECT A.*, ROW_NUMBER() OVER(PARTITION BY NAME, GROUP ORDER BY DATE) AS RN
FROM TABLE
WHERE (NAME, GROUP) IN
(SELECT NAME, GROUP FROM TABLE WHERE VALUE = 1)
),
TBL_2 AS
(
SELECT * FROM TBL_1 WHERE VALUE = 1
),
TBL_3 AS
(
SELECT A.*
FROM TBL_1 AS A
INNER JOIN TBL_2 AS B
ON B.NAME = A.NAME
AND B.GROUP = A.GROUP
AND A.RN > B.RN
)
SELECT *
FROM TBL_3
WHERE (NAME, GROUP, DATE) IN
(SELECT NAME, GROUP, MIN(DATE) FROM TBL_3 GROUP BY NAME, GROUP)
In SQL Server 2012 you can do this:
SELECT * FROM (
SELECT DISTINCT
ID,
Name,
DateTime,
Value,
Gr,
LAG(ID) OVER (PARTITION BY Name, Gr ORDER BY DateTime) F
FROM (
SELECT
ID,
Name,
DateTime,
Value,
Gr,
CASE WHEN LAG(Value) OVER (PARTITION BY Name, Gr ORDER BY DateTime) = 1 THEN 1 ELSE 0 END F
FROM
T
) TT
WHERE F = 1
) TT WHERE F IS NULL
ORDER BY Gr, Name, DateTime
Fiddle: http://www.sqlfiddle.com/#!6/5a0fa2/19
using window functions:
with cte as (
select
*,
row_number() over(partition by [Group], Name order by [DateTime]) as rn,
dense_rank() over(order by [Group], Name) as rnk
from Table1
)
select c1.*
from cte as c1
inner join cte as c2 on c2.rn = c1.rn - 1 and c2.rnk = c1.rnk and c2.Value = 1
where
not exists (select * from cte as c3 where c3.rn <= c1.rn - 2 and c3.rnk = c1.rnk and c3.Value = 1)
or apply:
select t1.*
from Table1 as t1
cross apply (
select top 1 t2.Value, t2.DateTime
from Table1 as t2
where
t2.[Group] = t1.[Group] and t2.Name = t1.Name and
t2.[DateTime] < t1.[DateTime]
order by t2.[Datetime] desc
) as t2
where
t2.Value = 1 and
not exists (
select *
from Table1 as t3
where
t3.[Group] = t1.[Group] and t3.Name = t1.Name and
t3.[DateTime] < t2.[DateTime] and t3.Value = 1
)
sql fiddle demo
update forgot to mention that your output seems to be incorrect - there should id = 6 instead of 5 in second row (see sql fiddle).