无法解析 SQL Server XML 列当 reportdefinition 为 “defaultfontfamily” 时。

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

Cannot parse SQL Server XML column when reportdefinition is "defaultfontfamily"

问题

我在SQL Server表中使用了一个XML列。有些行的XML没有正确解析,我已经找到了原因 - 对于无法读取的行,XML使用了' http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily'。我尝试将其添加为命名空间,但没有帮助。

没有问题的行具有以下XML命名空间:

<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
  <AutoRefresh>0</AutoRefresh>
  <DataSources>
    ...
</Report>

有问题的行具有以下XML命名空间:

<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns:df="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily" MustUnderstand="df">
  <df:DefaultFontFamily>Segoe UI</df:DefaultFontFamily>
  <AutoRefresh>0</AutoRefresh>
  <DataSources>
    ...
</Report>

当xmlcolumn.values被分配时,您可以看到它何时有效 - 这些不包含额外的行<df:DefaultFontFamily>Segoe UI</df:DefaultFontFamily>

以下是我使用的SQL。提前感谢您的帮助。

WITH XMLNAMESPACES 
( DEFAULT 
  'http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition'
, 'http://schemas.microsoft.com/SQLServer/reporting/reportdesigner' AS ReportDefinition
, 'http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily' AS df)
SELECT  
CATDATA.Name AS ReportName
,CATDATA.Path AS ReportPathLocation
,xmlcolumn.value('(@Name)[1]', 'VARCHAR(250)') AS DataSetName  
,xmlcolumn.value('(Query/DataSourceName)[1]','VARCHAR(250)') AS DataSourceName 
,xmlcolumn.value('(Query/CommandText)[1]','VARCHAR(2500)') AS CommandText
,reportXML
FROM (  
    SELECT C.Name
    ,c.Path
    ,CONVERT(XML,CONVERT(VARBINARY(MAX),C.Content)) AS reportXML
    FROM  ReportServer.dbo.Catalog C
    WHERE  C.Content is not null
    AND  C.Type = 2
    ) CATDATA 
outer APPLY reportXML.nodes('/Report/DataSets/DataSet') xmltable ( xmlcolumn )
--WHERE xmlcolumn.value('(Query/CommandText)[1]','VARCHAR(250)') LIKE '%2_%'
ORDER BY CATDATA.Name
英文:

I am using an XML column in a SQL Server table. There are certain rows where the XML is not properly parsed, and I have identified why - for rows where it cannot be read, the XML is using 'http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily'. I have tried adding it as a namespace, but it did not help.

Those rows with no problem have xml namespaces as:

&lt;Report xmlns=&quot;http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition&quot; xmlns:rd=&quot;http://schemas.microsoft.com/SQLServer/reporting/reportdesigner&quot;&gt;
  &lt;AutoRefresh&gt;0&lt;/AutoRefresh&gt;
  &lt;DataSources&gt;

...

Those rows with a problem have xml namespaces as:

&lt;Report xmlns=&quot;http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition&quot; xmlns:rd=&quot;http://schemas.microsoft.com/SQLServer/reporting/reportdesigner&quot; xmlns:df=&quot;http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily&quot; MustUnderstand=&quot;df&quot;&gt;
  &lt;df:DefaultFontFamily&gt;Segoe UI&lt;/df:DefaultFontFamily&gt;
  &lt;AutoRefresh&gt;0&lt;/AutoRefresh&gt;
  &lt;DataSources&gt;

...

You can see when it works when xmlcolumn.values are assigned - these do not contain the additional line <df:DefaultFontFamily>Segoe UI</df:DefaultFontFamily>
无法解析 SQL Server XML 列当 reportdefinition 为 “defaultfontfamily” 时。

Here is the SQL I am using. Thank you in advance.

WITH XMLNAMESPACES 
( DEFAULT 
  &#39;http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition&#39;
, &#39;http://schemas.microsoft.com/SQLServer/reporting/reportdesigner&#39; AS ReportDefinition
, &#39;http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily&#39; AS df)
SELECT  
CATDATA.Name AS ReportName
,CATDATA.Path AS ReportPathLocation
,xmlcolumn.value(&#39;(@Name)[1]&#39;, &#39;VARCHAR(250)&#39;) AS DataSetName  
,xmlcolumn.value(&#39;(Query/DataSourceName)[1]&#39;,&#39;VARCHAR(250)&#39;) AS DataSourceName 
,xmlcolumn.value(&#39;(Query/CommandText)[1]&#39;,&#39;VARCHAR(2500)&#39;) AS CommandText
,reportXML
FROM (  
	SELECT C.Name
	,c.Path
	,CONVERT(XML,CONVERT(VARBINARY(MAX),C.Content)) AS reportXML
	FROM  ReportServer.dbo.Catalog C
	WHERE  C.Content is not null
	AND  C.Type = 2
	) CATDATA 
outer APPLY reportXML.nodes(&#39;/Report/DataSets/DataSet&#39;) xmltable ( xmlcolumn )
--WHERE xmlcolumn.value(&#39;(Query/CommandText)[1]&#39;,&#39;VARCHAR(250)&#39;) LIKE &#39;%2_%&#39;
ORDER BY CATDATA.Name

答案1

得分: 3

你的XML确切内容不清晰,因为你没有展示一个完整的示例,所以我不得不猜测。似乎Report有一个不同的默认命名空间。

你有三个选择:

  • 要么完全忽略命名空间,使用*:
    WITH XMLNAMESPACES 
    ( DEFAULT 
      'http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition'
    , 'http://schemas.microsoft.com/SQLServer/reporting/reportdesigner' AS ReportDefinition
    , 'http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily' AS df
    )
    SELECT  
      C.Name AS ReportName
     ,C.Path AS ReportPathLocation
     ,xmlcolumn.value('(@Name)[1]', 'VARCHAR(250)') AS DataSetName  
     ,xmlcolumn.value('(*:Query/*:DataSourceName/text())[1]','VARCHAR(250)') AS DataSourceName 
     ,xmlcolumn.value('(*:Query/*:CommandText/text())[1]','VARCHAR(2500)') AS CommandText
     ,reportXML
    FROM ReportServer.dbo.Catalog C
    CROSS APPLY (
        SELECT
          CONVERT(XML, CONVERT(VARBINARY(MAX), C.Content)) AS reportXML
    ) CATDATA 
    OUTER APPLY reportXML.nodes('/*:Report/*:DataSets/*:DataSet') xmltable ( xmlcolumn )
    WHERE C.Content is not null
      AND C.Type = 2
    ORDER BY C.Name
    
  • 或者你可以使用多个.nodes
    WITH XMLNAMESPACES 
    ( DEFAULT 
      'http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition'
    , 'http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition' AS r2016
    , 'http://schemas.microsoft.com/SQLServer/reporting/reportdesigner' AS ReportDefinition
    , 'http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily' AS df)
    SELECT  
      C.Name AS ReportName
     ,C.Path AS ReportPathLocation
     ,ISNULL(
        xmlcolumn1.value('(@Name)[1]', 'VARCHAR(250)'),
        xmlcolumn2.value('(@Name)[1]', 'VARCHAR(250)')) AS DataSetName  
     ,ISNULL(
        xmlcolumn1.value('(Query/DataSourceName/text())[1]','VARCHAR(250)'),
        xmlcolumn2.value('(r2016:Query/r2016:DataSourceName/text())[1]','VARCHAR(250)')) AS DataSourceName 
     ,ISNULL(
        xmlcolumn1.value('(Query/CommandText/text())[1]','VARCHAR(2500)'),
        xmlcolumn2.value('(r2016:Query/r2016:CommandText/text())[1]','VARCHAR(2500)')) AS CommandText
     ,reportXML
    FROM ReportServer.dbo.Catalog C
    CROSS APPLY (
        SELECT
          CONVERT(XML, CONVERT(VARBINARY(MAX), C.Content)) AS reportXML
    ) CATDATA 
    OUTER APPLY reportXML.nodes('/Report/DataSets/DataSet') xmltable1 ( xmlcolumn1 )
    OUTER APPLY reportXML.nodes('/r2016:Report/r2016:DataSets/r2016:DataSet') xmltable2 ( xmlcolumn2 )
    WHERE C.Content is not null
      AND C.Type = 2
    ORDER BY C.Name
    

很难说哪个版本更快。忽略命名空间是慢的,但查询两次也是慢的。

注意其他改进:

  • 使用CROSS APPLY进行转换以避免嵌套。
  • 在进行XQuery时使用/text()来提高性能。
  • 如果可以将列实际更改为xml并避免转换,那将是更好的。

作为一个副注,如果你实际上想要那个WHERE,最好的方法是将其放在.nodes内部(请注意这里使用的是CROSS APPLY而不是OUTER APPLY

CROSS APPLY reportXML.nodes('
  /*Report/*:DataSets/*:DataSet
  [*:Query/*:CommandText/text()[contains(., "2_")]
  ') xmltable ( xmlcolumn )
英文:

Your exact XML is unclear as you haven't shown us a full sample, so I've had to guess. It seems Report has a different default namespace.

You have three options:

  • Either ignore the namespace completely using *:
    WITH XMLNAMESPACES 
    ( DEFAULT 
      &#39;http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition&#39;
    , &#39;http://schemas.microsoft.com/SQLServer/reporting/reportdesigner&#39; AS ReportDefinition
    , &#39;http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily&#39; AS df
    )
    SELECT  
      C.Name AS ReportName
     ,C.Path AS ReportPathLocation
     ,xmlcolumn.value(&#39;(@Name)[1]&#39;, &#39;VARCHAR(250)&#39;) AS DataSetName  
     ,xmlcolumn.value(&#39;(*:Query/*:DataSourceName/text())[1]&#39;,&#39;VARCHAR(250)&#39;) AS DataSourceName 
     ,xmlcolumn.value(&#39;(*:Query/*:CommandText/text())[1]&#39;,&#39;VARCHAR(2500)&#39;) AS CommandText
     ,reportXML
    FROM ReportServer.dbo.Catalog C
    CROSS APPLY (
        SELECT
          CONVERT(XML, CONVERT(VARBINARY(MAX), C.Content)) AS reportXML
    ) CATDATA 
    OUTER APPLY reportXML.nodes(&#39;/*:Report/*:DataSets/*:DataSet&#39;) xmltable ( xmlcolumn )
    WHERE C.Content is not null
      AND C.Type = 2
    ORDER BY C.Name
    
  • Or you can use multiple .nodes
    WITH XMLNAMESPACES 
    ( DEFAULT 
      &#39;http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition&#39;
    , &#39;http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition&#39; AS r2016
    , &#39;http://schemas.microsoft.com/SQLServer/reporting/reportdesigner&#39; AS ReportDefinition
    , &#39;http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition/defaultfontfamily&#39; AS df)
    SELECT  
      C.Name AS ReportName
     ,C.Path AS ReportPathLocation
     ,ISNULL(
        xmlcolumn1.value(&#39;(@Name)[1]&#39;, &#39;VARCHAR(250)&#39;),
        xmlcolumn2.value(&#39;(@Name)[1]&#39;, &#39;VARCHAR(250)&#39;)) AS DataSetName  
     ,ISNULL(
        xmlcolumn1.value(&#39;(Query/DataSourceName/text())[1]&#39;,&#39;VARCHAR(250)&#39;),
        xmlcolumn2.value(&#39;(r2016:Query/r2016:DataSourceName/text())[1]&#39;,&#39;VARCHAR(250)&#39;)) AS DataSourceName 
     ,ISNULL(
        xmlcolumn1.value(&#39;(Query/CommandText/text())[1]&#39;,&#39;VARCHAR(2500)&#39;),
        xmlcolumn2.value(&#39;(r2016:Query/r2016:CommandText/text())[1]&#39;,&#39;VARCHAR(2500)&#39;)) AS CommandText
     ,reportXML
    FROM ReportServer.dbo.Catalog C
    CROSS APPLY (
        SELECT
          CONVERT(XML, CONVERT(VARBINARY(MAX), C.Content)) AS reportXML
    ) CATDATA 
    OUTER APPLY reportXML.nodes(&#39;/Report/DataSets/DataSet&#39;) xmltable1 ( xmlcolumn1 )
    OUTER APPLY reportXML.nodes(&#39;/r2016:Report/r2016:DataSets/r2016:DataSet&#39;) xmltable2 ( xmlcolumn2 )
    WHERE C.Content is not null
      AND C.Type = 2
    ORDER BY C.Name
    

It's hard to say which version is faster. Ignoring namespaces is slow, but so is querying twice.

Note other improvements:

  • Use of CROSS APPLY for the conversion to avoid nesting.
  • Use of /text() to increase performance when doing XQuery.
  • If you can change your column to actually be xml and avoid the conversion that would be good.

As a side note, if you actually wanted that WHERE, the best way to do it would be to put it inside the .nodes (note the use of CROSS APPLY here, not OUTER APPLY.

CROSS APPLY reportXML.nodes(&#39;
  /*Report/*:DataSets/*:DataSet
  [*:Query/*:CommandText/text()[contains(., &quot;2_&quot;)]
  &#39;) xmltable ( xmlcolumn )

huangapple
  • 本文由 发表于 2023年5月28日 15:53:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76350485.html
匿名

发表评论

匿名网友

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

确定