How to write a query to allow null in minimum function - sql

I need to write a query to get minimum values for a column from a table and if the value is null then I want to include that row. I wrote following query but it ignores the null values. How I can modify this query to include null values in the result?
select * from TABLE where COLUMN = (select min(COLUMN) from TABLE );
If the table is like below
|ID | VALUE | NAME
101 1 John
101 null John
102 1 Bill
103 1 Tina
103 null Tina
104 null James
Result Should be
|ID | VALUE | NAME
101 1 John
102 1 Bill
103 1 Tina
104 null James

You need distinct on:
with my_table(id, value, name) as (
values
(101, 1, 'John'),
(101, null, 'John'),
(102, 1, 'Bill'),
(103, 1, 'Tina'),
(103, null, 'Tina'),
(104, null, 'James')
)
select distinct on (id) *
from my_table
order by id, value
id | value | name
-----+-------+-------
101 | 1 | John
102 | 1 | Bill
103 | 1 | Tina
104 | | James
(4 rows)
Distinct on is a fantastic feature specific for Postgres. An alternative in other RDBMS may be:
select t.id, t.value, t.name
from my_table t
join (
select id, min(value) as value
from my_table
group by id
) u on u.id = t.id and u.value is not distinct from t.value;
Note, you should use is not distinct from because value may be null.

SQL SERVER
select DISTINCT j.ID,j.VALUE,j.NAME from Table1 j
join (
select id, MIN(VALUE) VALUE from Table1
group by id
) as t
on t.ID = j.ID and (t.VALUE = j.VALUE or t.VALUE is null)

You cannot do an equals (=) for a null value, you have to check is null or so. So one simple solution is to default the null value to a number that would not otherwise be used:
select * from TABLE where coalesce(COLUMN, -9999) = (select min(coalesce(COLUMN,-9999)) from TABLE );
The coalesce function returns the first non-null value passed to it.

with c as (
select column as c
from table
order by column nulls first
limit 1
)
select *
from table cross join c
where column = c or column is null

If you want to user order by:
select t.*
from t
order by t.column asc nulls first
limit 1;
Alternatively, use rank():
select t.*
from (select t.*,
rank() over (order by col asc nulls first) as seqnum
from t
) t
where seqnum = 1;

I hope this solve your problem.
SELECT id,
CASE WHEN MIN(
CASE WHEN value IS NULL THEN 0 ELSE 1 END) = 0 THEN null
ELSE MIN(value) END
FROM tableName
GROUP BY id
or using COALESCE.
SELECT id,
CASE WHEN MIN(COALESCE(value, 0)) = 0 THEN null
ELSE MIN(value) END
FROM tableName
GROUP BY id
I am on mobile phone now, so I cannot test.

Related

Get top 5 records for each group and Concate them in a Row per group

I have a table Contacts that basically looks like following:
Id | Name | ContactId | Contact | Amount
---------------------------------------------
1 | A | 1 | 12323432 | 555
---------------------------------------------
1 | A | 2 | 23432434 | 349
---------------------------------------------
2 | B | 3 | 98867665 | 297
--------------------------------------------
2 | B | 4 | 88867662 | 142
--------------------------------------------
2 | B | 5 | null | 698
--------------------------------------------
Here, ContactId is unique throughout the table. Contact can be NULL & I would like to exclude those.
Now, I want to select top 5 contacts for each Id based on their Amount. I am accomplished that by following query:
WITH cte AS (
SELECT id, Contact, amount, ROW_NUMBER()
over (
PARTITION BY id
order by amount desc
) AS RowNo
FROM contacts
where contact is not null
)
select *from cte where RowNo <= 5
It's working fine upto this point. Now I want to concate these (<=5) record for each group & show them in a single row by concatenating them.
Expected Result :
Id | Name | Contact
-------------------------------
1 | A | 12323432;23432434
-------------------------------
2 | B | 98867665;88867662
I am using following query to achieve this but it still gives all records in separate rows and also including Null values too:
WITH cte AS (
SELECT id, Contact, amount,contactid, ROW_NUMBER()
over (
PARTITION BY id
order by amount desc
) AS RowNo
FROM contacts
where contact is not null
)
select *from id, name,
STUFF ((
SELECT distinct '; ' + isnull(contact,'') FROM cte
WHERE co.id= cte.id and co.contactid= cte.contactid
and RowNo <= 5
FOR XML PATH('')),1, 1, '')as contact
from contacts co inner join cte where cte.id = co.id and co.contactid= cte.contactid
Above query still gives me all top 5 contacts in diff rows & including null too.
Is it a good idea to use CTE and STUFF togather? Please suggest if there is any better approach than this.
I got the problem with my final query:
I don't need original Contact table in my final Select, since I already have everything I needed in CTE. Also, Inside STUFF(), I'm using contactid to join which is what actually I'm trying to concat here. Since I'm using that condition for join, I am getting records in diff rows. I've removed these 2 condition and it worked.
WITH cte AS (
SELECT id, Contact, amount,contactid, ROW_NUMBER()
over (
PARTITION BY id
order by amount desc
) AS RowNo
FROM contacts
where contact is not null
)
select *from id, name,
STUFF ((
SELECT distinct '; ' + isnull(contact,'') FROM cte
WHERE co.id= cte.id
and RowNo <= 5
FOR XML PATH('')),1, 1, '')as contact
from cte where rowno <= 5
You can use conditional aggregation:
id, name, contact,
select id, name,
concat(max(case when seqnum = 1 then contact + ';' end),
max(case when seqnum = 2 then contact + ';' end),
max(case when seqnum = 3 then contact + ';' end),
max(case when seqnum = 4 then contact + ';' end),
max(case when seqnum = 5 then contact + ';' end)
) as contacts
from (select c.*
row_number() over (partition by id order by amount desc) as seqnum
from contacts c
where contact is not null
) c
group by id, name;
If you are running SQL Server 2017 or higher, you can use string_agg(): as most other aggregate functions, it ignores null values by design.
select id, name, string_agg(contact, ',') within group (order by rn) all_contacts
from (
select id, name, contact
row_number() over (partition by id order by amount desc) as rn
from contacts
where contact is not null
) t
where rn <= 5
group by id, name
Note that you don't strictly need a CTE here; you can return the columns you need from the subquery, and use them directly in the outer query.
In earlier versions, one approach using stuff() and for xml path is:
with cte as (
select id, name, contact,
row_number() over (partition by id order by amount desc) as rn
from contacts
where contact is not null
)
select id, name,
stuff(
(
select ', ' + c1.concat
from cte c1
where c1.id = c.id and c1.rn <= 5
order by c1.rn
for xml path (''), type
).value('.', 'varchar(max)'), 1, 2, ''
) all_contacts
from cte
group by id, name
I agree with #GMB. STRING_AGG() is what you need ...
WITH
contacts(Id,nm,ContactId,Contact,Amount) AS (
SELECT 1,'A',1,12323432,555
UNION ALL SELECT 1,'A',2,23432434,349
UNION ALL SELECT 2,'B',3,98867665,297
UNION ALL SELECT 2,'B',4,88867662,142
UNION ALL SELECT 2,'B',5,NULL ,698
)
,
with_filter_val AS (
SELECT
*
, ROW_NUMBER() OVER(PARTITION BY id ORDER BY amount DESC) AS rn
FROM contacts
)
SELECT
id
, nm
, STRING_AGG(CAST(contact AS CHAR(8)),',') AS contact_list
FROM with_filter_val
WHERE rn <=5
GROUP BY
id
, nm
-- out id | nm | contact_list
-- out ----+----+-------------------
-- out 1 | A | 12323432,23432434
-- out 2 | B | 98867665,88867662

Making a conditional aggregate

I have tricky grouping problem for our business reasons, I have a table which has values like this
----------------------------
| NAME | TYPE | VALUE |
----------------------------
| N1 | T1 | V1 |
| N1 | T2 | V2 |
| N1 | NULL | V3 |
| N2 | T2 | V4 |
| N2 | NULL | V5 |
| N3 | NULL | V6 |
-----------------------------
I need to group it in a way that,
The first level grouping will be by name.
At the second level,
When the available types are T1,T2 and NULL, group T1 and NULL together and have T2 grouped seperately.
When the available types are T2 and NULL, group NULL with T2.
When NULL is the only available type, just have it as it is.
The expected O/P for the above table is,
----------------------------
| N1 | T1 | V1+V3 |
| N1 | T2 | V2 |
| N2 | T2 | V4+V5 |
| N3 | NULL | V6 |
-----------------------------
How to achieve this in snowflake sql. Or any other server, so that I can find an equivalent in Snowflake.
The following query should work:
SELECT t1.NAME, COALESCE(TYPE, MIN_TYPE), SUM(VALUE)
FROM mytable AS t1
JOIN (
SELECT NAME, MIN(TYPE) AS MIN_TYPE
FROM mytable
GROUP BY NAME
) AS t2 ON t1.NAME = t2.NAME
GROUP BY t1.NAME, COALESCE(TYPE, MIN_TYPE)
The query uses a derived table in order to extract the MIN(TYPE) value per NAME. Using COALESCE we can then convert NULL to either T1 or T2.
Edit:
You can create a pivoted version of the expected result set using the following query:
SELECT NAME,
CASE
WHEN T1SUM IS NULL THEN 0
ELSE COALESCE(T1SUM, 0) + COALESCE(NULLSUM,0)
END AS T1SUM,
CASE
WHEN T1SUM IS NULL AND T2SUM IS NOT NULL
THEN COALESCE(T2SUM, 0) + COALESCE(NULLSUM,0)
ELSE COALESCE(T2SUM, 0)
END AS T2SUM,
CASE
WHEN T1SUM IS NULL AND T2SUM IS NULL THEN COALESCE(NULLSUM,0)
ELSE 0
END AS NULLSUM
FROM (
SELECT NAME,
SUM(CASE WHEN TYPE = 'T1' THEN VALUE END) AS T1SUM,
SUM(CASE WHEN TYPE = 'T2' THEN VALUE END) AS T2SUM,
SUM(CASE WHEN TYPE IS NULL THEN VALUE END) AS NULLSUM
FROM mytable
GROUP BY NAME) AS t
So in Giorgos's answer that totals are given in a pivoted, or single row be case form, not many rows per case, and this can be written simpler:
with this data:
WITH data_table(name, type, value) AS (
SELECT * FROM VALUES
(10, 1, 100 ),
(10, 2, 200 ),
(10, null, 400 ),
(11, 2, 100 ),
(11, null, 200 ),
(12, null, 100 )
)
and this SQL
SELECT name
,SUM(IFF(type=1, value, null)) as t1_val
,SUM(IFF(type=2, value, null)) as t2_val
,SUM(IFF(type is null, value, null)) as tnull_val
,IFF(t1_val is not null, t1_val + zeroifnull(tnull_val), null) as c1_sum
,IFF(t1_val is not null, t2_val, t2_val + zeroifnull(tnull_val)) as c2_sum
,IFF(t1_val is null AND t2_val is null, tnull_val, null) as c3_sum
FROM data_table
GROUP BY 1;
we get:
NAME
T1_VAL
T2_VAL
TNULL_VAL
C1_SUM
C2_SUM
C3_SUM
10
100
200
400
500
200
null
11
null
100
200
null
300
null
12
null
null
100
null
null
100
which shows for the 10 row the null sum binds with 1 sum, for the 11 row the null sum binds with the 2 sum, and in the 12 row we get the null sum by itself.
We can unpivot these values if we wish, but joining to a mini table with 3 rows like so:
SELECT d.name,
p.c2 as type,
case p.c1
WHEN 1 then d.c1_sum
WHEN 2 then d.c2_sum
ELSE d.c3_sum
end as value
FROM (
SELECT name
,SUM(IFF(type=1, value, null)) as t1_val
,SUM(IFF(type=2, value, null)) as t2_val
,SUM(IFF(type is null, value, null)) as tnull_val
,IFF(t1_val is not null, t1_val + zeroifnull(tnull_val), null) as c1_sum
,IFF(t1_val is not null, t2_val, t2_val + zeroifnull(tnull_val)) as c2_sum
,IFF(t1_val is null AND t2_val is null, tnull_val, null) as c3_sum
FROM data_table
GROUP BY 1
) AS d
JOIN (
SELECT column1 as c1, column2 as c2
FROM VALUES (1,'T1'),(2,'T2'),(null,'null')
) AS p
ON ((d.c1_sum is not null AND p.c1 = 1)
OR (d.c2_sum is not null AND p.c1 = 2)
OR (d.c3_sum is not null AND p.c1 is null))
ORDER BY 1,2;
which gives the original requested output:
NAME
TYPE
VALUE
10
T1
500
10
T2
200
11
T2
300
12
null
100

Max and Min value's corresponding records

I have a scenario to get the respective field value of "Max" and "Min" records
Please find the sample data below
-----------------------------------------------------------------------
ID Label ProcessedDate
-----------------------------------------------------------------------
1 Label1 11/01/2016
2 Label2 11/02/2016
3 Label3 11/03/2016
4 Label4 11/04/2016
5 Label5 11/05/2016
I have the "ID" field populated in another table as a foreign key. While querying those records in that table based on the "ID" field I need to get the "Label" field of "Max" Processed date and "Min" processed date.
-----------------------------------------------------------------------
ID LabelID GroupingField
-----------------------------------------------------------------------
1 1 101
2 2 101
3 3 101
4 4 101
5 5 101
6 1 102
7 2 102
8 3 102
9 4 102
And the final result set I expect it to look something like this.
-----------------------------------------------------------------------
GroupingField FirstProcessed LastProcessed
-----------------------------------------------------------------------
101 Label1 Label5
102 Label1 Label4
I have 'almost' managed to get this above result using rank function but still not satisfied with it. So I am looking if someone can provide me with a better option.
Thanks,
Prakazz
CREATE TABLE #Details (ID INT,LabelID INT,GroupingField INT)
CREATE TABLE #Details1 (ID INT,Label VARCHAR(100),ProcessedDate VARCHAR(100))
INSERT INTO #Details1 (ID ,Label ,ProcessedDate )
SELECT 1,'Label1','11/01/2016' UNION ALL
SELECT 2,'Label2','11/02/2016' UNION ALL
SELECT 3,'Label3','11/03/2016' UNION ALL
SELECT 4,'Label4','11/04/2016' UNION ALL
SELECT 5,'Label5','11/05/2016'
INSERT INTO #Details (ID ,LabelID ,GroupingField )
SELECT 1,1,101 UNION ALL
SELECT 2,2,101 UNION ALL
SELECT 3,3,101 UNION ALL
SELECT 4,4,101 UNION ALL
SELECT 5,5,101 UNION ALL
SELECT 6,1,102 UNION ALL
SELECT 7,2,102 UNION ALL
SELECT 8,3,102 UNION ALL
SELECT 9,4,102
;WITH CTE (GroupingField , MAXId ,MinId) AS
(
SELECT GroupingField,MAX(LabelID) MAXId,MIN(LabelID) MinId
FROM #Details
GROUP BY GroupingField
)
SELECT GroupingField ,B.Label FirstProcessed, A.Label LastProcessed
FROM CTE
JOIN #Details1 A ON MAXId = A.ID
JOIN #Details1 B ON MinId = B.ID
You can use SQL Row_Number() function using Partition By as follows with a combination of Group By
;with cte as (
select
t.Label, t.ProcessedDate,
g.GroupingField,
ROW_NUMBER() over (partition by GroupingField Order By ProcessedDate ASC) minD,
ROW_NUMBER() over (partition by GroupingField Order By ProcessedDate DESC) maxD
from tbl t
inner join GroupingFieldTbl g
on t.ID = g.LabelID
)
select GroupingField, max(FirstProcessed) FirstProcessed, max(LastProcessed) LastProcessed
from (
select
GroupingField,
FirstProcessed = CASE when minD = 1 then Label else null end,
LastProcessed = CASE when maxD = 1 then Label else null end
from cte
where
minD = 1 or maxD = 1
) t
group by GroupingField
order by GroupingField
I also used CTE expression to make coding easier and understandable
Output is as

Procedure to copy data from a table to another table in SQL Server

I have a table A, with 4 columns:
first_name, invoice, value, date.
And a table B (first_name, max_invoice_name, max_invoice_value, last_date)
I want to create a procedure in order to move data from A, to B, but:
first_name should be one time in B,
max_invoice_name is the name of the max invoice value
max_invoice_value is the max value
last_date is the latest date from invoices from the same first_name.
For example:
TABLE A:
Smith | Invoice1 | 100 | 23.06.2016
John | Invoice13 | 23 | 18.07.2016
Smith | Invoice3 | 200 | 01.01.2015
Table B should be:
Smith |Invoice3 | 200 | 23.06.2016
John |Invoice13| 23 | 18.07.2016
Something like this should work:
select *, (select max(date) from #Table1 T1 where T1.first_name = X.first_name)
from (
select
*,
row_number() over (partition by first_name order by invoice_Value desc) as RN
from
#Table1
) X
where RN = 1
Row number takes care of selecting the row with biggest value, and the max get's the date. You'll need to list the columns in correct place instead of *
You will need to create 2 scalar functions getMaxNameForMaxValue AND getLastDateByFirstName to get the values you want.
INSERT INTO TableB (first_name, max_invoice_name, max_invoice_value, last_date) (SELECT DISTINCT first_name, getMaxNameForMaxValue(MAX(max_value)) AS 'max_invoice_name', MAX(max_invoice_value) AS 'max_invoice_value', getLastDateByFirstName(first_name) AS 'lastDate' FROM Table A)
You can use something like this:
--INSERT INTO TableB
SELECT first_name,
invoice_name,
invoice_value,
last_date
FROM (
SELECT a.first_name,
a.invoice_name,
a.invoice_value,
COALESCE(p.last_date,a.last_date) as last_date,
ROW_NUMBER() OVER (PARTITION BY a.first_name ORDER BY a.last_date) as rn
FROM TableA a
OUTER APPLY (SELECT TOP 1 * FROM TableA WHERE first_name = a.first_name and last_date > a.last_date) as p
) as res
WHERE rn = 1
As output:
first_name invoice_name invoice_value last_date
John Invoice13 23 2016-07-18
Smith Invoice3 200 2016-06-23
Try this
Insert into TableB(first_name, max_invoice_name, max_invoice_value, last_date)
select t1.first_name,t1.invoice,t1,value,t2.date from TableA as t1 inner join
(
select first_name, max(replace(invoice,'invoice','')) as invoice, max(date) as date
from TableA group by first_name
) as t2 on t1.first_name=t2.first_name and t1.invoice=t2.invoice

Retrieve a report on any duplicate rows of data in the emp table along with the count of -- the number of times that row of data is duplicated

I have EMP table as follows:
CREATE TABLE EMP
(
[ID] INT NOT NULL PRIMARY KEY,
[MGR_ID] INT,
[DEPT_ID] INT,
[NAME] VARCHAR(30),
[SAL] INT,
[DOJ] DATE
);
I need to retrieve a report on any duplicate rows of data in the emp table along with the count of -- the number of times that row of data is duplicated.
I partially solved this:
This query returns a singe instance of each of the duplicated rows
SELECT [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
from EMP
group by [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
having count(*) > 1
the output will be:
MGR_ID DEPT_ID NAME SAL DOJ
NULL 2 Hash 100 2012-01-01
1 2 Robo 100 2012-01-01
2 1 Privy 50 2012-05-01
I still need to group this output by the number of times each of these rows are duplicated in the EMP table.
I tried this:
WITH CTE
AS
(
SELECT * from EMP A
join ( SELECT [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
from EMP
group by [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
having count(*) > 1 ) B
on a.[MGR_ID] = b.[MGR_ID]
OR a.[MGR_ID] != b.[MGR_ID]
AND a.[DEPT_ID] = b.[DEPT_ID]
AND a.[NAME] = b.[NAME]
AND a.[SAL] = b.[SAL]
AND a.[DOJ] = b.[DOJ]
)
SELECT [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ], DENSE_RANK() OVER
(PARTITION BY [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ] ORDER BY DUPICATES) AS [DUPLICATES]
FROM CTE
But I got this error:
Msg 8156, Level 16, State 1, Line 1
The column 'MGR_ID' was specified multiple times for 'CTE'.
Please help.
The solution was partially found, except from I still need to do return MRG_ID column in the output for 3 records where it is = NULL
with cte as
(
SELECT A.[DEPT_ID],A.[NAME],A.[SAL],A.[DOJ] from EMP A
join ( SELECT [DEPT_ID],[NAME],[SAL],[DOJ]
from EMP
group by [DEPT_ID],[NAME],[SAL],[DOJ]
having count(*) > 1 ) B
ON a.[DEPT_ID] = b.[DEPT_ID]
AND a.[NAME] = b.[NAME]
AND a.[SAL] = b.[SAL]
AND a.[DOJ] = b.[DOJ]
)
SELECT [DEPT_ID],[NAME],[SAL],[DOJ], DENSE_RANK() OVER
(PARTITION BY [NAME] ORDER BY [NAME] DESC) AS [DUPLICATES], RANK() OVER
(PARTITION BY [NAME] ORDER BY [NAME] DESC) AS [SimpleRank]
FROM CTE
DEPT_ID NAME SAL DOJ DUPLICATES SimpleRank
2 Hash 100 2012-01-01 1 1
2 Hash 100 2012-01-01 1 1
2 Hash 100 2012-01-01 1 1
1 Privy 50 2012-05-01 1 1
1 Privy 50 2012-05-01 1 1
1 Privy 50 2012-05-01 1 1
2 Robo 100 2012-01-01 1 1
2 Robo 100 2012-01-01 1 1
2 Robo 100 2012-01-01 1 1
much
The final solution appears to be much easier:
Select [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ], count(name) From EMP group by [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ] having Count(Name) >1
It produces this result set
MGR_ID DEPT_ID NAME SAL DOJ Count_Of_ Duplicated_Rows
NULL 2 Hash 100 2012-01-01 3
1 2 Robo 100 2012-01-01 3
2 1 Privy 50 2012-05-01 3
Note: This will work only if you group by column that is duplicated.
The example below is based on previous more complex query, but it validates all the fields in the row, in comparison to the simple query above that checks condition of a one particular column that you are grouping the query by.
WITH CTE
AS
(
SELECT A.[MGR_ID], A.[DEPT_ID], A.[NAME], A.[SAL], A.[DOJ]
FROM EMP A
JOIN (SELECT [MGR_ID], [DEPT_ID], [NAME], [SAL], [DOJ]
FROM EMP
GROUP BY [MGR_ID], [DEPT_ID], [NAME], [SAL], [DOJ]
HAVING count(*) > 1) B
ON a.[MGR_ID] = b.[MGR_ID]
AND a.[DEPT_ID] = b.[DEPT_ID]
AND a.[NAME] = b.[NAME]
AND a.[SAL] = b.[SAL]
AND a.[DOJ] = b.[DOJ]
)
SELECT [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ],
count(*) As Count_Of_Duplicated_Rows
FROM EMP
GROUP BY [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
--HAVING Count(*) >1
Your problem is that you do not explicitly name the selected columns inside your CTE. Since both EMP and the subquery have a column called MGR_ID, doing select * on the join returns the column MGR_ID twice. According to MSDN, this is not allowed:
The list of column names is optional only if distinct names for all resulting columns are supplied in the query definition.
Note that you will encounter the same error for each pair of columns that exists on both sides of the join. To resolve this, you can either explicitly name the columns returned by the CTE in a column list with an alias for the repeated columns, like so:
WITH CTE (mgr_id,dept_id,name,sal,doj,mgr_id2,...) //mgr_id2 is an alias for b.mgr_id
AS
...
You can refer to this SQLFiddle for a demo. Remove the column list and you will see the same error you see now.
Alternatively, you can specify the columns to be selected in the CTE itself, I would recommend this since you don't actually need any repeated columns in your query:
;with cte as
(
SELECT A.[MGR_ID],A.[DEPT_ID],A.[NAME],A.[SAL],A.[DOJ] from EMP A
join ( SELECT [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
from EMP
group by [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
having count(*) > 1 ) B
...
try this
WITH CTE
AS
(
SELECT a.* from EMP A
join ( SELECT [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
from EMP
group by [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]
having count(*) > 1 ) B
on a.[MGR_ID] = b.[MGR_ID]
--OR a.[MGR_ID] != b.[MGR_ID]
AND a.[DEPT_ID] = b.[DEPT_ID]
AND a.[NAME] = b.[NAME]
AND a.[SAL] = b.[SAL]
AND a.[DOJ] = b.[DOJ]
),cte2 as(
SELECT [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ], DENSE_RANK() OVER
(PARTITION BY [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ] ORDER BY [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ]) AS [DUPLICATES]
FROM CTE )
select [MGR_ID],[DEPT_ID],[NAME],[SAL],[DOJ] from cte2 where DUPLICATES=1