Skip to content

Commit 95e90de

Browse files
author
Torben
committed
✅ Finalize KPIs
1 parent 32e2236 commit 95e90de

3 files changed

Lines changed: 115 additions & 40 deletions

File tree

matrix42/participating_roles.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ WITH ParticipatingRoles(TicketObjectId, ValidFrom, RoleId) AS (
1010
SPSActivityClassUnitOfWork AS CreateJournal
1111
WHERE
1212
ActivityAction = 1 -- Ticket Creation in Service Desk
13-
OR ActivityAction = 18 -- Ticket Creation in Self Service Portal
13+
OR ActivityAction = 19 -- Ticket Creation in Self Service Portal
1414
UNION
1515
SELECT
1616
[Expression-ObjectID],
Lines changed: 112 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
WITH FirstLevelSupportTeams(RoleId) AS (
2-
/* 1. First-Level-Support Roles
2+
/*
3+
1. First-Level-Support Roles
34
Identifies all Roles classified as First-Level-Support.
45
These Role IDs are used to determine if a ticket was created for First-Level-Support
56
and whether it was escalated to higher support levels.
@@ -12,26 +13,29 @@ WITH FirstLevelSupportTeams(RoleId) AS (
1213
role.Ud_SupportRoleType = 10 -- First-Level-Support
1314
),
1415
ParticipatingRoles(TicketObjectId, ValidFrom, RoleId) AS (
15-
/* 2. HISTORY OF ROLE ASSIGNMENTS
16+
/*
17+
2. HISTORY OF ROLE ASSIGNMENTS
1618
Reconstructs the timeline of which Role was assigned to a ticket and when.
1719
Parses XML 'SolutionParams' to find the Target Role ID.
1820
*/
1921
SELECT
2022
[Expression-ObjectID],
2123
CreatedDate,
24+
-- Extract Role ID from XML for Ticket Creation
2225
TRY_CAST(SolutionParams AS XML).value(
2326
'(/parameters/JournalEntryParameterBase/FragmentIds/fragmentId)[1]',
2427
'uniqueidentifier'
2528
)
2629
FROM
2730
SPSActivityClassUnitOfWork AS CreateJournal
2831
WHERE
29-
ActivityAction = 1 -- Ticket Creation in Service Desk
30-
OR ActivityAction = 18 -- Ticket Creation in Self Service Portal
32+
ActivityAction = 1
33+
OR ActivityAction = 19 -- Ticket Creation (Service Desk/SSP) [18 from mail does not expose recipient role!]
3134
UNION
3235
SELECT
3336
[Expression-ObjectID],
3437
CreatedDate,
38+
-- Extract Role ID from XML for Forwarding
3539
TRY_CAST(SolutionParams AS XML).value(
3640
'(/parameters/JournalEntryParameterBase/FragmentIds/fragmentId)[1]',
3741
'uniqueidentifier'
@@ -40,68 +44,142 @@ ParticipatingRoles(TicketObjectId, ValidFrom, RoleId) AS (
4044
SPSActivityClassUnitOfWork AS ForwardJournal
4145
WHERE
4246
ActivityAction = 3 -- Forward to Role
43-
OR ActivityAction = 30 -- Forward to Role AND User
47+
OR ActivityAction = 30 -- Forward to Role & User
4448
UNION
4549
SELECT
4650
[Expression-ObjectID],
4751
CreatedDate,
52+
-- Extract New Role ID from XML for Role Changes (Node [2] is usually the 'New' value)
4853
TRY_CAST(SolutionParams AS XML).value(
4954
'(/parameters/JournalEntryParameterBase/FragmentIds/fragmentId)[2]',
5055
'uniqueidentifier'
5156
)
5257
FROM
5358
SPSActivityClassUnitOfWork AS ChangeJournal
5459
WHERE
55-
ActivityAction = 74 -- Change Assigned Role From Old -> New
56-
UNION
60+
ActivityAction = 74 -- Change Assigned Role
61+
),
62+
PaddedParticipatingRoles(TicketObjectId, ValidFrom, RoleId) AS (
63+
/*
64+
3. PAD ROLE HISTORY
65+
Adds a fallback entry for tickets that never had their role changed and no creation journal exists.
66+
Uses the Ticket's RecipientRole as the initial role assignment date.
67+
*/
5768
SELECT
5869
[Expression-ObjectID],
59-
COALESCE(ClosedDate, GETDATE()),
60-
RecipientRole
70+
COALESCE(
71+
MAX(ParticipatingRoles.ValidFrom),
72+
Ticket.CreatedDate
73+
),
74+
Ticket.RecipientRole
6175
FROM
62-
SPSActivityClassBase
76+
ParticipatingRoles
77+
INNER JOIN SPSActivityClassBase AS Ticket ON ParticipatingRoles.TicketObjectId = Ticket.[Expression-ObjectID]
6378
WHERE
64-
RecipientRole IS NOT NULL
79+
Ticket.RecipientRole IS NOT NULL
80+
GROUP BY
81+
[Expression-ObjectID],
82+
Ticket.CreatedDate,
83+
Ticket.RecipientRole
6584
),
6685
RankedParticipatingRoles(TicketObjectId, RoleId, RoleAssignmentRank) AS (
67-
/* 3. RANKED ROLE ASSIGNMENTS
86+
/*
87+
4. RANKED ROLE ASSIGNMENTS
6888
Assigns a rank to each Role assignment per Ticket based on the ValidFrom date.
6989
This allows identifying the first assigned Role and any subsequent Role changes.
7090
*/
7191
SELECT
7292
TicketObjectId,
73-
RoleId,
93+
ParticipatingRoles.RoleId,
7494
ROW_NUMBER() OVER (
7595
PARTITION BY TicketObjectId
7696
ORDER BY
7797
ValidFrom ASC
7898
) AS RoleAssignmentRank
7999
FROM
80100
ParticipatingRoles
101+
LEFT OUTER JOIN FirstLevelSupportTeams ON ParticipatingRoles.RoleId = FirstLevelSupportTeams.RoleId
81102
WHERE
82-
RoleId IS NOT NULL -- this ASSUMES that for activity=18 (mail) a dispatch to a role happens BEFORE any work is performed!
103+
/*
104+
We have the problem, that sometimes we have no knowledge about the initial role assigned to a ticket (NULL) or a default role (interpreted as Dispatcher) is assigned.
105+
We want to ignore these roles when determining if a ticket started in First-Level-Support or was escalated.
106+
*/
107+
ParticipatingRoles.RoleId IS NOT NULL
108+
AND (
109+
FirstLevelSupportTeams.RoleId IS NOT NULL
110+
OR ParticipatingRoles.RoleId NOT IN (
111+
SELECT
112+
DefaultResponsibleRoleTickets
113+
FROM
114+
SPSGlobalConfigurationClassServiceDesk
115+
UNION
116+
SELECT
117+
DefaultResponsibleRoleIncidents
118+
FROM
119+
SPSGlobalConfigurationClassServiceDesk
120+
UNION
121+
SELECT
122+
DefaultResponsibleRoleServiceRequests
123+
FROM
124+
SPSGlobalConfigurationClassServiceDesk
125+
)
126+
)
83127
),
128+
TicketStatistics AS (
129+
/*
130+
5. AGGREGATION
131+
Determine FLS status and Escalation status before joining back to main ticket data.
132+
*/
133+
SELECT
134+
RankedParticipatingRoles.TicketObjectId,
135+
/*
136+
A ticket was created for First-Level-Support if:
137+
- the first "relevant" role assigned to the ticket is a First-Level-Support role
138+
*/
139+
MAX(
140+
CASE
141+
WHEN RankedParticipatingRoles.RoleAssignmentRank = 1
142+
AND FirstLevelSupportTeams.RoleId IS NOT NULL THEN 1
143+
ELSE 0
144+
END
145+
) AS StartedInFLS,
146+
-- Check if ANY role assigned in the history was NOT a First-Level-Support role
147+
MAX(
148+
CASE
149+
WHEN FirstLevelSupportTeams.RoleId IS NULL THEN 1
150+
ELSE 0
151+
END
152+
) AS HasNonFLSHistory
153+
FROM
154+
RankedParticipatingRoles
155+
LEFT JOIN FirstLevelSupportTeams ON RankedParticipatingRoles.RoleId = FirstLevelSupportTeams.RoleId
156+
GROUP BY
157+
RankedParticipatingRoles.TicketObjectId
158+
)
159+
/*
160+
* 6. Final Output
161+
*/
84162
SELECT
85-
ticket.ID,
163+
Ticket.ID,
86164
CASE
87-
WHEN ticketCommon.State = 204 THEN 1
165+
WHEN TicketCommon.State = 204 THEN 1
88166
ELSE 0
89167
END AS IsClosed,
90-
/*
91-
A ticket was created for First-Level-Support if:
92-
- the first "relevant" role assigned to the ticket is a First-Level-Support role
93-
*/
94-
/*
95-
A ticket was closed without escalation if:
96-
- the ticket is in "Closed" status AND
97-
- there are NO non-First-Level-Support role assignments in the ticket history
98-
- the first "relevant" role assigned to the ticket is a First-Level-Support role
99-
*/
100-
/*
101-
A ticket was escalated if:
102-
- there is at least one non-First-Level-Support role assignment in the ticket history
103-
- the first "relevant" role assigned to the ticket is a First-Level-Support role
104-
*/
168+
-- Metric 1: Created for First-Level-Support
169+
Stats.StartedInFLS AS IsCreatedForFirstLevelSupport,
170+
-- Metric 2: Resolved in First-Level-Support (No Escalation)
171+
CASE
172+
WHEN Stats.StartedInFLS = 1
173+
AND Stats.HasNonFLSHistory = 0
174+
AND TicketCommon.State = 204 THEN 1
175+
ELSE 0
176+
END AS IsFirstLevelSupportResolution,
177+
-- Metric 3: Escalated from First-Level-Support
178+
CASE
179+
WHEN Stats.StartedInFLS = 1
180+
AND Stats.HasNonFLSHistory = 1 THEN 1
181+
ELSE 0
182+
END AS IsEscalated
105183
FROM
106184
dbo.SPSActivityClassBase AS Ticket
107185
INNER JOIN (
@@ -110,16 +188,12 @@ FROM
110188
State
111189
FROM
112190
dbo.SPSCommonClassBase
113-
) AS TicketCommon ON Ticket.[Expression-ObjectID] = TicketCommon.[Expression-ObjectID] --Join with a minimal set of common properties
114-
INNER JOIN RankedParticipatingRoles ON Ticket.[Expression-ObjectID] = RankedParticipatingRoles.TicketObjectId -- Join with Participating Users to find ~all users involved with the ticket
115-
LEFT OUTER JOIN FirstLevelSupportTeams ON ParticipatingRoles.RoleId = FirstLevelSupportTeams.RoleId
191+
) AS TicketCommon ON Ticket.[Expression-ObjectID] = TicketCommon.[Expression-ObjectID]
192+
INNER JOIN TicketStatistics AS Stats ON Ticket.[Expression-ObjectID] = Stats.TicketObjectId
116193
WHERE
117194
Ticket.CreatedDate >= DATEADD(year, -3, GETDATE()) -- 3 year sliding window
118195
AND (
119196
Ticket.UsedInTypeSPSActivityTypeTicket IS NOT NULL
120197
OR Ticket.UsedInTypeSPSActivityTypeIncident IS NOT NULL
121198
OR Ticket.UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL
122-
)
123-
GROUP BY
124-
TicketCommon.State,
125-
Ticket.ID;
199+
)

matrix42/single_contact_resolution.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
WITH ParticipatingUsers(TicketObjectId, UserId) AS (
2-
/* 1. HISTORY OF USER ASSIGNMENTS
2+
/*
3+
1. HISTORY OF USER ASSIGNMENTS
34
Reconstructs the timeline of which User was assigned to a ticket.
45
Might parse XML 'SolutionParams' to find the Target User ID.
56
*/

0 commit comments

Comments
 (0)