1+ WITH ParticipatingRoles(TicketObjectId, ValidFrom, RoleId) AS (
2+ /*
3+ 1. HISTORY OF ROLE ASSIGNMENTS
4+ Reconstructs the timeline of which Role was assigned to a ticket and when.
5+ Parses XML 'SolutionParams' to find the Target Role ID.
6+ */
7+ SELECT
8+ [Expression- ObjectID],
9+ CreatedDate,
10+ -- Extract Role ID from XML for Ticket Creation
11+ TRY_CAST(SolutionParams AS XML).value(
12+ ' (/parameters/JournalEntryParameterBase/FragmentIds/fragmentId)[1]' ,
13+ ' uniqueidentifier'
14+ )
15+ FROM
16+ SPSActivityClassUnitOfWork AS CreateJournal
17+ WHERE
18+ ActivityAction = 1
19+ OR ActivityAction = 19 -- Ticket Creation (Service Desk/SSP) [18 from mail does not expose recipient role!]
20+ UNION
21+ SELECT
22+ [Expression- ObjectID],
23+ CreatedDate,
24+ -- Extract Role ID from XML for Forwarding
25+ TRY_CAST(SolutionParams AS XML).value(
26+ ' (/parameters/JournalEntryParameterBase/FragmentIds/fragmentId)[1]' ,
27+ ' uniqueidentifier'
28+ )
29+ FROM
30+ SPSActivityClassUnitOfWork AS ForwardJournal
31+ WHERE
32+ ActivityAction = 3 -- Forward to Role
33+ OR ActivityAction = 30 -- Forward to Role & User
34+ UNION
35+ SELECT
36+ [Expression- ObjectID],
37+ CreatedDate,
38+ -- Extract New Role ID from XML for Role Changes (Node [2] is usually the 'New' value)
39+ TRY_CAST(SolutionParams AS XML).value(
40+ ' (/parameters/JournalEntryParameterBase/FragmentIds/fragmentId)[2]' ,
41+ ' uniqueidentifier'
42+ )
43+ FROM
44+ SPSActivityClassUnitOfWork AS ChangeJournal
45+ WHERE
46+ ActivityAction = 74 -- Change Assigned Role
47+ ),
48+ PaddedParticipatingRoles(TicketObjectId, ValidFrom, RoleId) AS (
49+ /*
50+ 2. PAD ROLE HISTORY
51+ Adds a fallback entry for tickets that never had their role changed and no creation journal exists.
52+ Uses the Ticket's RecipientRole as the initial role assignment date.
53+ */
54+ SELECT
55+ [Expression- ObjectID],
56+ COALESCE(
57+ MAX (ParticipatingRoles .ValidFrom ),
58+ Ticket .CreatedDate
59+ ),
60+ Ticket .RecipientRole
61+ FROM
62+ ParticipatingRoles
63+ INNER JOIN SPSActivityClassBase AS Ticket ON ParticipatingRoles .TicketObjectId = Ticket.[Expression- ObjectID]
64+ WHERE
65+ Ticket .RecipientRole IS NOT NULL
66+ GROUP BY
67+ [Expression- ObjectID],
68+ Ticket .CreatedDate ,
69+ Ticket .RecipientRole
70+ ),
71+ AgentInteractions(
72+ TicketObjectId,
73+ CreatedDate,
74+ Creator
75+ ) AS (
76+ /*
77+ 3. AGENT ACTIVITY
78+ Filters the journal for specific manual actions (Edits, Emails, Closures)
79+ performed by agents within the last 3 years.
80+ */
81+ SELECT
82+ Ticket.[Expression- ObjectID],
83+ Journal .CreatedDate ,
84+ Journal .Creator
85+ FROM
86+ dbo .SPSActivityClassUnitOfWork AS Journal
87+ INNER JOIN dbo .SPSActivityClassBase AS Ticket ON Journal.[Expression- ObjectID] = Ticket.[Expression- ObjectID]
88+ WHERE
89+ Journal .Creator IS NOT NULL
90+ AND Journal .CreatedDate >= DATEADD(year, - 3 , GETDATE())
91+ AND Journal .ActivityAction IN (
92+ -- 4: Editing/Taking over a Ticket
93+ -- 7: Accepting a Ticket
94+ -- 8: Closing a Ticket
95+ -- 9, 33, 34: Merging a Ticket
96+ -- 11: Sending an E-mail
97+ -- 66: Ticket to Incident Transformation
98+ -- 67: Ticket to Service Request Transformation
99+ -- 68: Incident to Service Request Transformation
100+ -- 69: Service Request to Incident Transformation
101+ -- 82 + 83: Pausing a Ticket
102+ -- 84: Resolving a Ticket
103+ 4 ,
104+ 7 ,
105+ 8 ,
106+ 9 ,
107+ 11 ,
108+ 33 ,
109+ 34 ,
110+ 66 ,
111+ 67 ,
112+ 68 ,
113+ 69 ,
114+ 82 ,
115+ 83 ,
116+ 84
117+ )
118+ AND (
119+ Ticket .UsedInTypeSPSActivityTypeTicket IS NOT NULL
120+ OR Ticket .UsedInTypeSPSActivityTypeIncident IS NOT NULL
121+ OR Ticket .UsedInTypeSPSActivityTypeServiceRequest IS NOT NULL
122+ )
123+ ),
124+ RankedRolesPerInteraction AS (
125+ /*
126+ 4. MATCH INTERACTION TO ACTIVE ROLE
127+ Joins interactions with role history.
128+ Uses ROW_NUMBER to find the MOST RECENT role assignment relative to the interaction date.
129+ */
130+ SELECT
131+ AgentInteractions .TicketObjectId ,
132+ AgentInteractions .CreatedDate AS InteractionDate,
133+ AgentInteractions .Creator ,
134+ Roles .RoleId ,
135+ -- Rank 1 is the most recent role assignment before the interaction happened
136+ ROW_NUMBER() OVER (
137+ PARTITION BY AgentInteractions .TicketObjectId ,
138+ AgentInteractions .CreatedDate ,
139+ AgentInteractions .Creator
140+ ORDER BY
141+ Roles .ValidFrom DESC
142+ ) AS RoleRank
143+ FROM
144+ AgentInteractions
145+ INNER JOIN PaddedParticipatingRoles AS Roles ON AgentInteractions .TicketObjectId = Roles .TicketObjectId
146+ WHERE
147+ -- Ensure we only look at roles assigned BEFORE or AT the same time as the interaction
148+ Roles .ValidFrom <= AgentInteractions .CreatedDate
149+ AND -- This is an unlucky case where RoleId extraction might fail; we exclude NULLs here => Might falsify results slightly
150+ Roles .RoleId IS NOT NULL
151+ )
152+ /*
153+ 5. FINAL OUTPUT
154+ Filters for Rank 1 to retrieve only the single active role for that specific interaction.
155+ */
156+ SELECT
157+ CAST(InteractionDate AS date ) AS [Date ],
158+ Creator AS UserId,
159+ RoleId,
160+ COUNT (* ) AS JournalInteractions
161+ FROM
162+ RankedRolesPerInteraction
163+ WHERE
164+ RoleRank = 1
165+ GROUP BY
166+ CAST(InteractionDate AS date ),
167+ Creator,
168+ RoleId
169+ ORDER BY
170+ -- Optional: Sorts by date descending, then by Creator and RoleId to improve readability
171+ [Date ] DESC ,
172+ [Creator],
173+ [RoleId];
0 commit comments