Pivot and left join - sql

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

Related

Perform ranking depend on category

I Have a table looks like this:
RowNum category Rank4A Rank4B
-------------------------------------------
1 A
2 A
3 B
5 A
6 B
9 B
My requirement is based on the RowNum order, Make two new ranking columns depend on category. Rank4A works like the DENSERANK() by category = A, but if the row is for category B, it derives the latest appeared rank for category A order by RowNum. Rank4B have similar logic, but it orders by RowNum in DESC order. So the result would like this (W means this cell I don't care its value):
RowNum category Rank4A Rank4B
-------------------------------------------
1 A 1 W
2 A 2 W
3 B 2 3
5 A 3 2
6 B W 2
9 B W 1
One more additional requirement is that CROSS APPLY or CURSOR is not allowed due to dataset being large. Any neat solutions?
Edit: Also no CTE (due to MAX 32767 limit)
You can use the following query:
SELECT RowNum, category,
SUM(CASE
WHEN category = 'A' THEN 1
ELSE 0
END) OVER (ORDER BY RowNum) AS Rank4A,
SUM(CASE
WHEN category = 'B' THEN 1
ELSE 0
END) OVER (ORDER BY RowNum DESC) AS Rank4B
FROM mytable
ORDER BY RowNum
Giorgos Betsos' answer is better, please read it first.
Try this out. I believe each CTE is clear enough to show the steps.
IF OBJECT_ID('tempdb..#Data') IS NOT NULL
DROP TABLE #Data
CREATE TABLE #Data (
RowNum INT,
Category CHAR(1))
INSERT INTO #Data (
RowNum,
Category)
VALUES
(1, 'A'),
(2, 'A'),
(3, 'B'),
(5, 'A'),
(6, 'B'),
(9, 'B')
;WITH AscendentDenseRanking AS
(
SELECT
D.RowNum,
D.Category,
AscendentDenseRanking = DENSE_RANK() OVER (ORDER BY D.Rownum ASC)
FROM
#Data AS D
WHERE
D.Category = 'A'
),
LaggedRankingA AS
(
SELECT
D.RowNum,
AscendentDenseRankingA = MAX(A.AscendentDenseRanking)
FROM
#Data AS D
INNER JOIN AscendentDenseRanking AS A ON D.RowNum > A.RowNum
WHERE
D.Category = 'B'
GROUP BY
D.RowNum
),
DescendantDenseRanking AS
(
SELECT
D.RowNum,
D.Category,
DescendantDenseRanking = DENSE_RANK() OVER (ORDER BY D.Rownum DESC)
FROM
#Data AS D
WHERE
D.Category = 'B'
),
LaggedRankingB AS
(
SELECT
D.RowNum,
AscendentDenseRankingB = MAX(A.DescendantDenseRanking)
FROM
#Data AS D
INNER JOIN DescendantDenseRanking AS A ON D.RowNum < A.RowNum
WHERE
D.Category = 'A'
GROUP BY
D.RowNum
)
SELECT
D.RowNum,
D.Category,
Rank4A = ISNULL(RA.AscendentDenseRanking, LA.AscendentDenseRankingA),
Rank4B = ISNULL(RB.DescendantDenseRanking, LB.AscendentDenseRankingB)
FROM
#Data AS D
LEFT JOIN AscendentDenseRanking AS RA ON D.RowNum = RA.RowNum
LEFT JOIN LaggedRankingA AS LA ON D.RowNum = LA.RowNum
LEFT JOIN DescendantDenseRanking AS RB ON D.RowNum = RB.RowNum
LEFT JOIN LaggedRankingB AS LB ON D.RowNum = LB.RowNum
/*
Results:
RowNum Category Rank4A Rank4B
----------- -------- -------------------- --------------------
1 A 1 3
2 A 2 3
3 B 2 3
5 A 3 2
6 B 3 2
9 B 3 1
*/
This isn't a recursive CTE, so the limit 32k doesn't apply.

Filter data based on condition

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')

Left Join between 3 tables

I have 3 tables as follows:
Table 1 contains the following
Id Name Section Salary
---------------------------------
1 Mark It 1000
2 Dad Hr 2000
Table 2 contains this sample data:
Id Item Salary
--------------------------
1 Holday 50
1 Food 30
1 Rent 100
2 Food 200
2 Rent 200
Table 3 contains this data:
Id Descriptions Cost
---------------------------
1 Bonce 150
1 Rate 300
2 Car 100
2 Bonce 15
2 Rate 30
I need to have the data like the attached screenshot:
That is an interesting computing problem independent of your use. It does not a simple join the tables. That is the part of SQL Server enforce, in your report tool you need to do the adjusts.
Table preparation
DECLARE #table1 table (Id int, Name varchar(10), Section varchar(10), Salary decimal(18,2))
DECLARE #table2 table (Id int, Item varchar(10), Salary decimal(18,2))
DECLARE #table3 table (Id int, Descriptions varchar(10), Cost decimal(18,2))
INSERT #table1 values
(1, 'Mark', 'It', 1000)
,(2, 'Dad ', 'Hr', 2000)
INSERT #table2 Values
(1,'Holday', 50)
,(1,'Food ', 30)
,(1,'Rent ',100)
,(2,'Food ',200)
,(2,'Rent ',200)
INSERT #table3 values
(1, 'Bonce', 150)
,(1, 'Rate ', 300)
,(2, 'Car ', 100)
,(2, 'Bonce', 15)
,(2, 'Rate ', 30)
The Select
;WITH
lv0(n) AS (SELECT 0 FROM (VALUES (0), (0))G(n)), --2
lv1(n) AS (SELECT 0 FROM lv0 a CROSS JOIN lv0 b), -- 4
lv2(n) AS (SELECT 0 FROM lv1 a CROSS JOIN lv1 b), -- 16
lv3(n) AS (SELECT 0 FROM lv2 a CROSS JOIN lv2 b), -- 256
lv4(n) AS (SELECT 0 FROM lv3 a CROSS JOIN lv3 b), -- 65,536
--lv5(N) as (select 0 from lv4 a cross join lv4 b), -- 4,294,967,296
tally(n) AS (SELECT Row_number() OVER( ORDER BY (SELECT NULL)) FROM lv4),
t1 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table1),
t2 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table2),
t3 AS (SELECT Row_number() OVER( ORDER BY id ) N ,* FROM #table3)
SELECT a.NAME
,a.section
,a.section
,b.item
,b.salary
,c.descriptions
,c.cost
,a.id
,b.id
,c.id
FROM tally t
CROSS JOIN t1 a
LEFT JOIN t2 b
ON t.n = b.n
AND a.id = b.id
LEFT JOIN t3 c
ON t.n = C.n
AND a.id = c.id
WHERE a.id IS NOT NULL
AND ( b.id IS NOT NULL
OR c.id IS NOT NULL )
ORDER BY a.n
,b.n
,c.n
The result
NAME section section item salary descriptions cost id id id
---------- ---------- ---------- ---------- --------------------------------------- ------------ --------------------------------------- ----------- ----------- -----------
Mark It It Holday 50.00 Bonce 150.00 1 1 1
Mark It It Food 30.00 Rate 300.00 1 1 1
Mark It It Rent 100.00 NULL NULL 1 1 NULL
Dad Hr Hr NULL NULL Car 100.00 2 NULL 2
Dad Hr Hr Food 200.00 Bonce 15.00 2 2 2
Dad Hr Hr Rent 200.00 Rate 30.00 2 2 2

top 3 ranks corresponding to every partition in 3 different columns in SQL Server

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

I have a table "Student", now I would like to query the result as same as the second table. One group must have 2 students.

I have a table Student, now I would like to query the result the same as the second table. One group must have 2 students.
Student ID Name Gender Group
1 A M A1
2 B F A1
3 C M A2
4 D M A2
5 E F A3
6 F F A3
Name1 Gender1 Name2 Gender2 Group
A M B F A1
C M D M A2
E F F F A3
Something like:
select t1.name, t1.gender, t2.name, t2.gender, t1.group
from student t1, student t2
where t1.group = t2.group and t1.id < t2.id
Here is the complete example.
CREATE TABLE testing
(id char(10),
name char(10),
gender char(1),
grp char(10))
insert into testing values ('1','A','M','A1')
insert into testing values ('2','B','F','A1')
insert into testing values ('3','C','M','A2')
insert into testing values ('4','D','M','A2')
insert into testing values ('5','E','F','A3')
insert into testing values ('6','F','F','A3')
select name1, gender1, name2, gender2, a.grp from
(
SELECT name1 = CASE CONVERT(int,id)%2 WHEN 1 THEN name ELSE null END,
gender1 = CASE CONVERT(int,id)%2 WHEN 1 THEN gender ELSE null END,
grp FROM testing where CONVERT(int,id)%2 =1
) a
left join
(
SELECT name2 = CASE CONVERT(int,id)%2 WHEN 0 THEN name ELSE null END,
gender2 = CASE CONVERT(int,id)%2 WHEN 0 THEN gender ELSE null END,
grp FROM testing where CONVERT(int,id)%2 =0
) b on a.grp=b.grp
SELECT s1.name AS Name1, s1.gender AS Gender1,
s2.name AS Name2, s2.gender AS Gender2,
groups.group_s
FROM (
SELECT MIN(id) AS st_first, MAX(id) AS st_second, group_s
FROM students
GROUP BY group_s
) AS groups
LEFT JOIN students s1 ON groups.st_first=s1.id
LEFT JOIN students s2 ON groups.st_second=s2.id
students is the name of your table and group_s is the group column