Parse the tsql_stack XML from an Extended Events session into a readable call stack. Paste the <frames> element from the XE event data into the @stackOrFrame variable.
The COALESCE handles both the old (handle/offsetStart/offsetEnd) and new (sqlhandle/stmtstart/stmtend) XE frame attribute names.
/* Paste the <frames></frames> here */
DECLARE @stackOrFrame xml = ''
;WITH
xmlShred AS
(
SELECT
COALESCE
(
CONVERT(varbinary(64), f.n.value('.[1]/@handle', 'varchar(max)'), 1),
CONVERT(varbinary(64), f.n.value('.[1]/@sqlhandle', 'varchar(max)'), 1)
) AS handle,
COALESCE
(
f.n.value('.[1]/@offsetStart', 'int'),
f.n.value('.[1]/@stmtstart', 'int')
) AS offsetStart,
COALESCE
(
f.n.value('.[1]/@offsetEnd', 'int'),
f.n.value('.[1]/@stmtend', 'int')
) AS offsetEnd,
f.n.value('.[1]/@line', 'int') AS line,
f.n.value('.[1]/@level', 'tinyint') AS stackLevel
FROM @stackOrFrame.nodes('//frame') AS f(n)
)
SELECT
xs.stackLevel,
ca.outerText,
ca2.statementText
FROM
xmlShred AS xs
CROSS APPLY sys.dm_exec_sql_text(xs.handle) AS dest
CROSS APPLY (SELECT LTRIM(RTRIM(dest.text)) FOR XML PATH(''), TYPE) AS ca(outerText)
CROSS APPLY
(
SELECT
SUBSTRING
(
dest.text,
(xs.offsetStart / 2) + 1,
((
CASE
WHEN xs.offsetEnd = -1
THEN DATALENGTH(dest.text)
ELSE xs.offsetEnd
END
- xs.offsetStart
) / 2) + 1
)
FOR XML PATH(''), TYPE
) AS ca2(statementText)
ORDER BY xs.stackLevel
OPTION (RECOMPILE);
I have CAST the text to XML so it's formatted nicely, but if your code contains XML-specific special characters, it might break.
Warning
The stack parsing relies on
sys.dm_exec_sql_text, which reads from the plan cache. If the plan has been evicted (server restart, memory pressure, DBCC FREEPROCCACHE), the query returns NULL. Run it while the plans are still cached.