Something like this should work:
WITH cteData (Id, ManagerId, EmployeePath, IsLoop) As
(
SELECT
Id,
ManagerId,
'/' + CAST(Id As varchar(max)) + '/',
CASE
WHEN ManagerId = Id THEN CAST(1 As bit)
ELSE CAST(0 As bit)
END
FROM
Employee
UNION ALL
SELECT
D.Id,
E.ManagerId,
D.EmployeePath + CAST(E.Id As varchar(max)) + '/',
CASE
WHEN D.EmployeePath Like '%' + CAST(E.Id As varchar(max)) + '%' THEN CAST(1 As bit)
ELSE CAST(0 As bit)
END
FROM
cteData As D
INNER JOIN Employee As E
ON E.Id = D.ManagerId
WHERE
D.IsLoop = 0
)
SELECT
Id,
EmployeePath
FROM
cteData
WHERE
IsLoop = 1
;
That will give you the list of all employees which are part of a loop, with the list of employee IDs that form the loop.
EDIT:
Based on the suggestion posted by Jörgen Andersson in the comments, this should give you the same results with better performance:
WITH cteData (Id, ManagerId, EmployeePath, Distance) As
(
SELECT
Id,
ManagerId,
CAST(Id As varchar(max)),
0 As Distance
FROM
Employee
UNION ALL
SELECT
D.Id,
E.ManagerId,
D.EmployeePath + ' > ' + CAST(E.Id As varchar(max)),
D.Distance + 1
FROM
cteData As D
INNER JOIN Employee As E
ON E.Id = D.ManagerId
WHERE
D.ManagerId != D.Id
And
E.ManagerId != E.Id
)
SELECT
Id,
EmployeePath,
Distance
FROM
cteData
WHERE
ManagerId = Id
And
Distance != 0
;