Рекурсивная функция sql с логикой свертки?
У меня есть SQL, который с помощью рекурсивного CTE для расширения таблицы сотрудников, ссылающихся на себя, создает результирующий набор дефектов, агрегированных по уровню пользователя и серьезности.
вот мой CTE:
ALTER FUNCTION [dbo].[fnGetEmployeeHierarchyByUsername]
(
@NTID varchar(100) = null
)
RETURNS TABLE
AS
RETURN
(
WITH yourcte AS
(
SELECT EmployeeId, ManagerNTID, ManagerID, NTID, FullName--, Name
FROM Employees
WHERE NTID = @NTID
UNION ALL
SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID, e.FullName--, e.Name
FROM Employees e
JOIN yourcte y ON e.ManagerNTID = y.NTID
)
SELECT EmployeeId, ManagerID, NTID, FullName--, Name
FROM yourcte
)
Вот мой SQL для агрегирования дефектов пользователем:
SELECT e.FullName, Urgent, High, Medium, Low
FROM fnGetEmployeeHierarchyByUsername ('ssalvati') e
LEFT OUTER JOIN(
SELECT [AssignedTo],
SUM([1-Urgent]) AS Urgent,
SUM([2-High]) AS High,
SUM([3-Medium]) AS Medium,
SUM([4-Low]) AS Low
FROM (SELECT [AssignedTo],[BusinessSeverity] FROM Defects WHERE Status <> 'Closed') D
PIVOT (COUNT([BusinessSeverity]) FOR [BusinessSeverity] IN ([1-Urgent],[2-High],[3-Medium],[4-Low])) V
GROUP BY [AssignedTo]) AS def
ON e.ntid = def.[AssignedTo]
я хочу иметь porc, который принимает имя пользователя в качестве параметра и генерирует результат как SQL выше, но с 2 улучшениями:
Мне нужно, чтобы перечислить пользователя, переданного в качестве параметра, чтобы быть в списке в качестве первой записи набора результатов.
Мне нужно, чтобы сотрудники, которые отчитывались перед менеджером, показывали только один уровень и не показывали полное дерево. первый уровень должен быть сводом всех основных дефектов, назначенных людям, которые объединяются во всех пользователей первого уровня. другими словами, я не хочу показывать целое дерево под менеджером, как сейчас, мне нужно, чтобы оно показывало только один уровень глубиной, но с суммой дефектов для всех уровней.
идеи?
3 ответа
Вот длинный манекен способ сделать это. у меня это работает, но решение может быть намного лучше. я надеюсь, что кто-то опубликует SQL2005 способ сделать это...
alter PROC sel_DefectReportByManagerNTID_rollup
(@ManagerNTID NVARCHAR(100))
AS
CREATE TABLE #DefectCounts
(
id INT IDENTITY(1, 1) ,
MgrRolledInto NVARCHAR(100) NULL,
AltBusinessSeverity NVARCHAR(100) NULL,
DefectCount INT NULL
);
CREATE TABLE #directReports
(
pk INT IDENTITY(1, 1) ,
directReportNTID NVARCHAR(100) NULL
);
INSERT INTO #directReports
SELECT NTID FROM Employees WHERE ManagerNTID = @ManagerNTID
--select * from #directReports
DECLARE @maxPK INT;
SELECT @maxPK = MAX(PK) FROM #directReports
DECLARE @pk INT;
SET @pk = 1
INSERT INTO #DefectCounts (MgrRolledInto,AltBusinessSeverity,DefectCount)
SELECT @ManagerNTID, d.AltBusinessSeverity, COUNT(*)
FROM Defects d
JOIN StatusCode C ON C.CodeName = d.Status AND c.scid = 10
WHERE d.AssignedTo = @ManagerNTID
GROUP BY d.AltBusinessSeverity
WHILE @pk <= @maxPK
BEGIN
/* Get one direct report at a time to aggregate their defects under them... */
DECLARE @dirRptNTID NVARCHAR(100);
SET @dirRptNTID = (SELECT directReportNTID
FROM #directReports
WHERE PK = @pk)
INSERT INTO #DefectCounts (MgrRolledInto,AltBusinessSeverity,DefectCount)
SELECT @dirRptNTID, d.AltBusinessSeverity, COUNT(*)
FROM Defects d
JOIN StatusCode C ON C.CodeName = d.Status AND c.scid = 10
JOIN (SELECT * FROM fnGetEmployeeHierarchyByUsername(@dirRptNTID) ) emp ON emp.NTID = d.AssignedTo
WHERE d.AssignedTo IS NOT NULL
GROUP BY d.AltBusinessSeverity
SELECT @pk = @pk + 1
END
SELECT e.FullName,
isnull(Urgent,0) as Urgent,
isnull(High,0) as High,
isnull(Medium,0) as Medium,
isnull(Medium3000,0) as Medium3000,
isnull(Low,0) as Low
FROM ( select * from fnGetEmployeeHierarchyByUsername (@ManagerNTID) where depth <= 1) e
left outer join (
SELECT MgrRolledInto,
SUM([1-Urgent]) AS Urgent,
SUM([2-High]) AS High,
SUM([3-Medium]) AS Medium,
SUM([3-Medium (3000)]) AS Medium3000,
SUM([4-Low]) AS Low
FROM #DefectCounts dfs
PIVOT
(sum(DefectCount) FOR AltBusinessSeverity IN ([1-Urgent],[2-High],[3-Medium],[3-Medium (3000)],[4-Low])) V
GROUP BY MgrRolledInto
) def_data on def_data.MgrRolledInto = e.NTID
order by e.depth
Это не проверено, так как у меня нет ни mssql, ни ваших данных, но я думаю, что это должно быть правильно и, по крайней мере, подтолкнуть вас в полезном направлении.
Во-первых, вам нужно изменить запрос в UDF, чтобы получить две дополнительные части информации. "Самый верхний" сотрудник для вашей агрегации агрегатов (я думаю, вы сказали, что это первый прямой отчет, а не самый высокий сотрудник) и общая глубина. В качестве таких:
WITH yourcte AS
(
SELECT EmployeeId, ManagerNTID, ManagerID, NTID, FullName, 0 as Depth, ntid as Topmost
FROM Employees
WHERE NTID = @NTID
UNION ALL
SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID, e.FullName, y.Depth+1, case when y.depth = 0 then e.ntid else y.Topmost end
FROM Employees e
JOIN yourcte y ON e.ManagerNTID = y.NTID
)
SELECT EmployeeId, ManagerID, NTID, FullName, Depth, Topmost
FROM yourcte
Затем ваш фактический запрос нуждается в нескольких дополнительных деталях, чтобы извлечь эту информацию и использовать ее
SELECT
e.FullName,
Urgent,
High,
Medium,
Low
FROM fnGetEmployeeHierarchyByUsername ('ssalvati') e
LEFT OUTER JOIN(
SELECT [AssignedTo],
SUM([1-Urgent]) AS Urgent,
SUM([2-High]) AS High,
SUM([3-Medium]) AS Medium,
SUM([4-Low]) AS Low
FROM (SELECT [AssignedTo],[BusinessSeverity] FROM Defects WHERE Status <> 'Closed') D
join fnGetEmployeeHierarchyByUsername ('ssalvati') e2 on d.AssignedTo = e2.ntid
PIVOT (COUNT([BusinessSeverity]) FOR [BusinessSeverity] IN ([1-Urgent],[2-High],[3-Medium],[4-Low])) V
where e2.TopMost = e.ntid
GROUP BY [AssignedTo]) AS def
ON e.ntid = def.[AssignedTo]
where e.Depth <= 1
Двойной вызов вашей UDF может быть немного дорогим, поэтому вы можете рассмотреть возможность помещения его в sproc и использования временной таблицы, чтобы получить результаты UDF для соединения.
Также обратите внимание, что UDF может принимать дополнительный параметр относительно глубины "самого верхнего", делая это более общим, что он в настоящее время находится в жестко закодированной форме.
Если вы изменили свой cte, чтобы включить глубину, т.е.
WITH yourcte AS
(
SELECT EmployeeId, ManagerNTID, ManagerID, NTID, FullName, 0 AS Depth
FROM Employees
WHERE NTID = @NTID
UNION ALL
SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID, e.FullName, y.Depth + 1
FROM Employees e
JOIN yourcte y ON e.ManagerNTID = y.NTID
)
Затем вы можете упорядочить вывод по глубине (так как пользователь во входном параметре должен быть на нулевой глубине). Используя это, вы также должны иметь возможность ограничивать возвращаемые глубины и агрегировать дефекты, где глубина>= 1
редактировать
С SQL, который я добавил выше, вы в основном хотите свести все дефекты к пользователю на уровне 1? Таким образом, NTID пользователя на этом уровне становится группируемым элементом для всех записей с глубиной>= 1. Другое редактирование в приведенном ниже разделе добавляет NTID в качестве GroupingID, который можно использовать для группировки по / rollup.
WITH yourcte AS
(
SELECT EmployeeId, ManagerNTID, ManagerID, NTID
,FullName, 0 AS Depth, NTID as GroupingID
FROM Employees
WHERE NTID = @NTID
UNION ALL
SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID
,e.FullName, y.Depth + 1, CASE
WHEN y.Depth + 1 = 1 THEN e.NTID
ELSE y.GroupingId
END
FROM Employees e
JOIN yourcte y ON e.ManagerNTID = y.NTID
)