提取执行计划以XML格式存入表中。

huangapple go评论68阅读模式
英文:

Extract execution plan in XML format to table

问题

我正在尝试以通用的方式从SQL服务器执行计划中提取数据。

例如,对于以下查询的执行计划:

SELECT *
FROM sys.all_objects o1

在SSMS中显示如下:

提取执行计划以XML格式存入表中。

用户界面显示了节点以及每个节点的成本和百分比。我如何将这些信息从底层XML提取到表结构中?

我已经尝试自己查询XML,但似乎XML结构在不同查询之间都在变化。

英文:

I'm trying to extract the data from SQL server execution plans in a generic way.

As an example the execution plan for

SELECT *
FROM sys.all_objects o1

as shown in SSMS is below

提取执行计划以XML格式存入表中。

The UI shows nodes along with costs for each node and percentages. How can I extract this from the underlying XML into a table structure?

I've tried to query the XML by my self, but it seems that the XML structure is changing from query to query.

答案1

得分: 1

这应该可以帮助您入门(DB Fiddle示例)。

DECLARE @X XML = N'<?xml version="1.0" encoding="utf-16"?><ShowPlanXML ...';

DECLARE @Nodes TABLE
(
   PlanId INT,
   NodeId INT,
   PhysicalOp VARCHAR(200),
   EstimatedTotalSubtreeCost FLOAT,
   EstimatedOperatorCost FLOAT,
   ParentNodeId INT NULL,
   PRIMARY KEY(PlanId, NodeId)
);

WITH XMLNAMESPACES (default 'http://schemas.microsoft.com/sqlserver/2004/07/showplan'),
plans AS
(
SELECT ROW_NUMBER() over (order by qp) as PlanId, qp.query('.') as plan_xml
FROM @X.nodes('//QueryPlan') n(qp)
)
INSERT @Nodes(PlanId, NodeId, PhysicalOp, EstimatedTotalSubtreeCost, ParentNodeId)
SELECT PlanId,
       NodeId = relop.value('@NodeId', 'int'),
       PhysicalOp = relop.value('@PhysicalOp', 'varchar(200)'),
       EstimatedTotalSubtreeCost = relop.value('@EstimatedTotalSubtreeCost', 'float'),
       /*XPath ancestor axis not supported so just go up a few levels and look for the closest ancestor Relop*/
       ParentNodeId = COALESCE(
       relop.value('..[local-name() = "RelOp"]/@NodeId', 'int'),
       relop.value('../..[local-name() = "RelOp"]/@NodeId', 'int'),
       relop.value('../../..[local-name() = "RelOp"]/@NodeId', 'int'),
       relop.value('../../../..[local-name() = "RelOp"]/@NodeId', 'int')
       )
FROM plans
CROSS APPLY plan_xml.nodes('//RelOp') n(relop);

UPDATE N1
SET EstimatedOperatorCost = EstimatedTotalSubtreeCost - ISNULL((SELECT SUM(EstimatedTotalSubtreeCost) FROM @Nodes N2 WHERE N1.PlanId = N2.PlanId AND N2.ParentNodeId = N1.NodeId),0)
FROM @Nodes N1

SELECT *,
       EstPctOperatorCost = FORMAT(EstimatedOperatorCost/MAX(EstimatedTotalSubtreeCost) OVER (PARTITION BY PlanId), 'P0')
FROM @Nodes

执行计划是一棵树 - 有可能有更加优雅的方式来获取父运算符,不过上述是我的尝试!

上述代码尚未在超过两个执行计划的样本大小上经过充分测试,因此您可能会遇到需要修复的问题。

您可以访问URI http://schemas.microsoft.com/sqlserver/2004/07/showplan 以查看有关各种模式的信息,尽管由于某种原因,除非我使用隐身模式,否则一直会显示"请求被阻止"


  [1]: https://dbfiddle.uk/hq66aIte
英文:

This should get you started (DB Fiddle example).

DECLARE @X XML = N&#39;&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ShowPlanXML ...&#39;;
        
DECLARE @Nodes TABLE
  (
     PlanId                    INT,
     NodeId                    INT,
     PhysicalOp                VARCHAR(200),
     EstimatedTotalSubtreeCost FLOAT,
     EstimatedOperatorCost     FLOAT,
     ParentNodeId              INT NULL,
     PRIMARY KEY(PlanId, NodeId)
  ); 
 
WITH  XMLNAMESPACES (default &#39;http://schemas.microsoft.com/sqlserver/2004/07/showplan&#39;),
plans AS
(
SELECT ROW_NUMBER() over (order by qp) as PlanId, qp.query(&#39;.&#39;) as plan_xml
FROM @X.nodes(&#39;//QueryPlan&#39;) n(qp)
)
INSERT @Nodes(PlanId, NodeId, PhysicalOp, EstimatedTotalSubtreeCost, ParentNodeId) 
SELECT PlanId, 
       NodeId = relop.value(&#39;@NodeId&#39;, &#39;int&#39;), 
       PhysicalOp = relop.value(&#39;@PhysicalOp&#39;, &#39;varchar(200)&#39;), 
       EstimatedTotalSubtreeCost = relop.value(&#39;@EstimatedTotalSubtreeCost&#39;, &#39;float&#39;),
	   /*XPath ancestor axis not supported so just go up a few levels and look for the closest ancestor Relop*/
	   ParentNodeId = COALESCE(
	   relop.value(&#39;..[local-name() = &quot;RelOp&quot;]/@NodeId&#39;, &#39;int&#39;),
	   relop.value(&#39;../..[local-name() = &quot;RelOp&quot;]/@NodeId&#39;, &#39;int&#39;),
	   relop.value(&#39;../../..[local-name() = &quot;RelOp&quot;]/@NodeId&#39;, &#39;int&#39;),
	   relop.value(&#39;../../../..[local-name() = &quot;RelOp&quot;]/@NodeId&#39;, &#39;int&#39;)
	   )
FROM plans
CROSS APPLY plan_xml.nodes(&#39;//RelOp&#39;) n(relop);

UPDATE N1
SET EstimatedOperatorCost = EstimatedTotalSubtreeCost -  ISNULL((SELECT SUM(EstimatedTotalSubtreeCost) FROM @Nodes N2 WHERE N1.PlanId = N2.PlanId AND N2.ParentNodeId = N1.NodeId),0)
FROM @Nodes N1


SELECT *,
       EstPctOperatorCost = FORMAT(EstimatedOperatorCost/MAX(EstimatedTotalSubtreeCost) OVER (PARTITION BY PlanId), &#39;P0&#39;)
FROM @Nodes

The execution plan is a tree - there are likely more elegant ways of getting the parent operator than my attempt!

The above is not battle tested across a sample size of more than two execution plans so you may well encounter issues with it that you will need to fix.

You can visit the URI http://schemas.microsoft.com/sqlserver/2004/07/showplan to see information about the various schemas though for some reason I've never got to the bottom of it displays "The request is blocked." for me unless I use incognito mode.

huangapple
  • 本文由 发表于 2023年2月14日 05:26:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/75441335.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定