【问题标题】:Convert XML data into tsql record for any XML response将 XML 数据转换为任何 XML 响应的 tsql 记录
【发布时间】:2017-03-02 12:10:47
【问题描述】:

我不知道它是否已经被回答,但是我的运气不好,所以我在stackoverflow中的任何地方都找不到我的狩猎技术。请忽略我的垃圾邮件

我们有一个要求,我们需要编写一个 API 解析器,该解析器适用于任何提供 XML 输出的 API。

我们不会事先知道 XML 结构。

解决方案应转换 XML 文件并将其保存在通用 tsql 表中,并将 XML 元素/属性名称作为第一行。

所以基本上它是任何 API 的 XML 反序列化器。

我们的 C# 类不能使用任何第三方 dll。

我对C#一无所知,所以不知道它是否可能。但是我已经能够使用 OPENXML 在 tsql 中编写一个通用的 XML->row 转换器。 tsql解决方案的问题是我们无法将一个巨大的XML文件成功导入数据库。

我可以提供任何需要的细节。请在 cmets/answers 中告诉我。

我不希望任何人为我写代码,任何合适的指针就足够了

资源: JSON

[
{
        "id" : 21953,
        "mainReqIdentity" : "xxxx",
        "itemName" : "xxxx",
        "kanbanPhase" : "xxxx",
        "kanbanStatus" : "xxxx",
        "backlogItemType" : "xxxx",
        "identityDomain" : "xxxx",
        "fromDatetime" : "2016-08-05 17:52:34",
        "teams" : [],
        "releases" : [{
                "id" : 1229,
                "release_name" : "xxxx",
                "release_connection_type" : "xxxx"
            }
        ],
        "fpReleases" : [],
        "sources" : [{
                "sourceName" : "xxxx",
                "sourceRecordUrl" : "xxxx",
                "sourceRecordIdentity" : "xxxx"
            }
        ],
        "productNumbers" : [],
        "tags" : [],
        "productComponents" : [],
        "ranPlatforms" : [],
        "subReleases" : [],
        "requirementAreaId" : xxxx,
        "requirementArea" : "xxxx",
        "toBeHandledAtxxxx" : "xxxx"
    }, {
        "id" : 22014,
        "mainReqIdentity" : "xxxx",
        "itemName" : "xxxx",
        "kanbanPhase" : "xxxx",
        "kanbanStatus" : "xxxx",
        "backlogItemType" : "xxxx",
        "identityDomain" : "xxxx",
        "fromDatetime" : "2016-08-05 17:52:34",
        "teams" : [],
        "releases" : [{
                "id" : xxxx,
                "release_name" : "xxxx",
                "release_connection_type" : "xxxx"
            }
        ],
        "fpReleases" : [],
        "sources" : [{
                "sourceName" : "xxxx",
                "sourceRecordUrl" : "xxxx",
                "sourceRecordIdentity" : "xxxx"
            }
        ],
        "productNumbers" : [],
        "tags" : [],
        "productComponents" : [],
        "ranPlatforms" : [],
        "subReleases" : [],
        "requirementAreaId" : xxxx,
        "requirementArea" : "xxxx",
        "f0Date" : "2015-10-01",
        "f1Date" : "2015-10-01",
        "f2Date" : "2016-02-01",
        "f4Date" : "2016-03-31",
        "fgDate" : "2016-04-29",
        "toBeHandledAtxxxx" : "xxxx"
    }
    ]

XML:2 个样本

示例 1

    <root type="array">
    <id type="number">21286</id>
    <mainReqIdentity type="string">xxxxxx</mainReqIdentity>
    <itemName type="string">xxxxxx</itemName>
    <kanbanPhase type="string">xxxxxx</kanbanPhase>
    <kanbanStatus type="string">xxxxxx</kanbanStatus>
    <kanbanNote type="string">xxxxxx</kanbanNote>
    <backlogItemType type="string">xxxxxx</backlogItemType>
    <identityDomain type="string">xxxxxx</identityDomain>
    <fromDatetime type="string">2016-08-23 17:01:52</fromDatetime>
    <teams type="array">
      <item type="object">
        <team_name type="string">xxxxxx</team_name>
        <preliminary type="boolean">xxxxxx</preliminary>
      </item>
    </teams>
    <releases type="array">
      <item type="object">
        <id type="number">xxxxxx</id>
        <release_name type="string">xxxxxx</release_name>
        <release_connection_type type="string">xxxxxx</release_connection_type>
      </item>
    </releases>
    <fpReleases type="array">
    </fpReleases>
    <sources type="array">
      <item type="object">
        <sourceName type="string">xxxxxx</sourceName>
        <sourceRecordUrl type="string">xxxxxx</sourceRecordUrl>
      </item>
    </sources>
    <productNumbers type="array">
    </productNumbers>
    <tags type="array">
    </tags>
    <productComponents type="array">
    </productComponents>
    <ranPlatforms type="array">
    </ranPlatforms>
    <subReleases type="array">
    </subReleases>
    <requirementAreaId type="number">xxxxxx</requirementAreaId>
    <requirementArea type="string">xxxxxx</requirementArea>
    <itemContact type="string">xxxxxx</itemContact>
    <toBeHandledAtxxx type="string">xxxxxx</toBeHandledAtLuca>
  </item>
    <item type="object">
    <id type="number">xxxxxx</id>
    <mainReqIdentity type="string">xxxxxx</mainReqIdentity>
    <itemName type="string">xxxxxx</itemName>
    <kanbanPhase type="string">xxxxxx</kanbanPhase>
    <kanbanStatus type="string">xxxxxx</kanbanStatus>
    <kanbanNote type="string">xxxxxx</kanbanNote>
    <backlogItemType type="string">xxxxxx</backlogItemType>
    <identityDomain type="string">xxxxxx</identityDomain>
    <fromDatetime type="string">2016-08-23 17:01:52</fromDatetime>
    <teams type="array">
      <item type="object">
        <team_name type="string">xxxxxx</team_name>
        <preliminary type="boolean">xxxxxx</preliminary>
      </item>
    </teams>
    <releases type="array">
      <item type="object">
        <id type="number">xxxxxx</id>
        <release_name type="string">xxxxxx</release_name>
        <release_connection_type type="string">xxxxxx</release_connection_type>
      </item>
    </releases>
    <fpReleases type="array">
    </fpReleases>
    <sources type="array">
      <item type="object">
        <sourceName type="string">xxxxxx</sourceName>
        <sourceRecordUrl type="string">xxxxxx</sourceRecordUrl>
      </item>
    </sources>
    <productNumbers type="array">
    </productNumbers>
    <tags type="array">
    </tags>
    <productComponents type="array">
    </productComponents>
    <ranPlatforms type="array">
    </ranPlatforms>
    <subReleases type="array">
    </subReleases>
    <requirementAreaId type="number">xxxxxx</requirementAreaId>
    <requirementArea type="string">xxxxxx</requirementArea>
    <oaResultReference type="string">xxxxxx</oaResultReference>
    <itemContact type="string">xxxxxx</itemContact>
    <f0Date type="string">2014-10-17</f0Date>
    <f1Date type="string">2015-01-16</f1Date>
    <f2Date type="string">2015-02-13</f2Date>
    <f4Date type="string">2015-06-12</f4Date>
    <faDate type="string">2015-06-12</faDate>
    <fgDate type="string">2015-06-12</fgDate>
    <toBeHandledAtxxx type="string">xxxxxx</toBeHandledAtLuca>
  </item>
 </root>

示例 2

<ROOT>  
<Customer CustomerID="VINET" ContactName="Paul Henriot">  
   <Order CustomerID="VINET" EmployeeID="5" OrderDate="1996-07-04T00:00:00">  
      <OrderDetail OrderID="10248" ProductID="11" Quantity="12"/>  
      <OrderDetail OrderID="10248" ProductID="42" Quantity="10"/>  
   </Order>  
</Customer>  
<Customer CustomerID="LILAS" ContactName="Carlos Gonzlez">  
   <Order CustomerID="LILAS" EmployeeID="3" OrderDate="1996-08-16T00:00:00">  
      <OrderDetail OrderID="10283" ProductID="72" Quantity="3"/>  
   </Order>  
</Customer>  
</ROOT>

SQL

通用暂存表

create table ZZZZZZZZZ
(
api_id int,
record_type char(1),
record_id INT,
last_run_time datetime,
last_run_by varchar(500),
col1 VARCHAR(500),
col2 VARCHAR(500),
col3 VARCHAR(500),
col4 VARCHAR(500),
col5 VARCHAR(500),
col6 VARCHAR(500),
col7 VARCHAR(500),
col8 VARCHAR(500),
col9 VARCHAR(500),
col10 VARCHAR(500),
col11 VARCHAR(500),
col12 VARCHAR(500),
col13 VARCHAR(500),
col14 VARCHAR(500),
col15 VARCHAR(500),
col16 VARCHAR(500),
col17 VARCHAR(500),
col18 VARCHAR(500),
col19 VARCHAR(500),
col20 VARCHAR(500),
col21 VARCHAR(500),
col22 VARCHAR(500),
col23 VARCHAR(500),
col24 VARCHAR(500),
col25 VARCHAR(500),
col26 VARCHAR(500),
col27 VARCHAR(500),
col28 VARCHAR(500),
col29 VARCHAR(500),
col30 VARCHAR(500),
col31 VARCHAR(500),
col32 VARCHAR(500),
col33 VARCHAR(500),
col34 VARCHAR(500),
col35 VARCHAR(500),
col36 VARCHAR(500),
col37 VARCHAR(500),
col38 VARCHAR(500),
col39 VARCHAR(500),
col40 VARCHAR(500),
col41 VARCHAR(500),
col42 VARCHAR(500),
col43 VARCHAR(500),
col44 VARCHAR(500),
col45 VARCHAR(500),
col46 VARCHAR(500),
col47 VARCHAR(500),
col48 VARCHAR(500),
col49 VARCHAR(500),
col50 VARCHAR(500),
col51 VARCHAR(500),
col52 VARCHAR(500),
col53 VARCHAR(500),
col54 VARCHAR(500),
col55 VARCHAR(500),
col56 VARCHAR(500),
col57 VARCHAR(500),
col58 VARCHAR(500),
col59 VARCHAR(500),
col60 VARCHAR(500),
col61 VARCHAR(500),
col62 VARCHAR(500),
col63 VARCHAR(500),
col64 VARCHAR(500),
col65 VARCHAR(500),
col66 VARCHAR(500),
col67 VARCHAR(500),
col68 VARCHAR(500),
col69 VARCHAR(500),
col70 VARCHAR(500),
col71 VARCHAR(500),
col72 VARCHAR(500),
col73 VARCHAR(500),
col74 VARCHAR(500),
col75 VARCHAR(500),
col76 VARCHAR(500),
col77 VARCHAR(500),
col78 VARCHAR(500),
col79 VARCHAR(500),
col80 VARCHAR(500),
col81 VARCHAR(500),
col82 VARCHAR(500),
col83 VARCHAR(500),
col84 VARCHAR(500),
col85 VARCHAR(500),
col86 VARCHAR(500),
col87 VARCHAR(500),
col88 VARCHAR(500),
col89 VARCHAR(500),
col90 VARCHAR(500),
col91 VARCHAR(500),
col92 VARCHAR(500),
col93 VARCHAR(500),
col94 VARCHAR(500),
col95 VARCHAR(500),
col96 VARCHAR(500),
col97 VARCHAR(500),
col98 VARCHAR(500),
col99 VARCHAR(500),
col100 VARCHAR(500),
col101 VARCHAR(500),
col102 VARCHAR(500),
col103 VARCHAR(500),
col104 VARCHAR(500),
col105 VARCHAR(500),
col106 VARCHAR(500),
col107 VARCHAR(500),
col108 VARCHAR(500),
col109 VARCHAR(500),
col110 VARCHAR(500),
col111 VARCHAR(500),
col112 VARCHAR(500),
col113 VARCHAR(500),
col114 VARCHAR(500),
col115 VARCHAR(500),
col116 VARCHAR(500),
col117 VARCHAR(500),
col118 VARCHAR(500),
col119 VARCHAR(500),
col120 VARCHAR(500),
col121 VARCHAR(500),
col122 VARCHAR(500),
col123 VARCHAR(500),
col124 VARCHAR(500),
col125 VARCHAR(500),
col126 VARCHAR(500),
col127 VARCHAR(500),
col128 VARCHAR(500),
col129 VARCHAR(500),
col130 VARCHAR(500),
col131 VARCHAR(500),
col132 VARCHAR(500),
col133 VARCHAR(500),
col134 VARCHAR(500),
col135 VARCHAR(500),
col136 VARCHAR(500),
col137 VARCHAR(500),
col138 VARCHAR(500),
col139 VARCHAR(500),
col140 VARCHAR(500),
col141 VARCHAR(500),
col142 VARCHAR(500),
col143 VARCHAR(500),
col144 VARCHAR(500),
col145 VARCHAR(500),
col146 VARCHAR(500),
col147 VARCHAR(500),
col148 VARCHAR(500),
col149 VARCHAR(500),
col150 VARCHAR(500)
)

样本输出

用 TSQL 编写的通用 XML 解析器。代码中几乎没有 hack 和一些需要删除的杂散代码。这工作正常。但问题在于通过直接调用或通过文件将整个 XML 文档作为输入参数从 C# 代码发送。

CREATE PROC ZZZZZZZ
(
@in_api_id int,
@in_xml_doc XML,
@in_xml_root varchar(100),
@in_tot_result_col int = 150,
@in_need_colnm_result CHAR(1) = 'Y',
@in_debug_flg CHAR(1) = 'N'
)
AS
BEGIN
DECLARE 
    @idoc int, 
    @sqlstr nvarchar(max) = '',
    @param nvarchar(200) = '',
    @runtime datetime = getdate(),
    @runby varchar(30) = suser_name(),
    @cnt int,
    @pre_stg_col_nm varchar(max) = '',
    @max_lvl int,
    @max_node varchar(500)='',
    @max_node_wo_slash varchar(500)='',
    @xml_col nvarchar(max) = '',
    @unq_col nvarchar(max) = '',
    @unq_xml_col nvarchar(max)=''

--Create an internal representation of the XML document.  
EXEC sp_xml_preparedocument @idoc OUTPUT, @in_xml_doc;  
-- Execute a SELECT statement that uses the OPENXML rowset provider.  
set @in_xml_root = concat('/',@in_xml_root)
SELECT * into #tmp FROM OPENXML (@idoc, @in_xml_root,2) where id <> 0;

--select * from #tmp_xml_nodes
--select * from #tmp
--select * from #tmp_pre_staging

;with xml_cte(id, parentid, nodetype, localname, prefix, namespaceuri, datatype, prev, text, lvl,node,parent_localname)
AS
(
select  id, 
                parentid, 
                nodetype, 
                localname, 
                prefix, 
                namespaceuri, 
                datatype, 
                prev, 
                text,
                1 as lvl,
                cast(CONCAT(@in_xml_root,'/',localname) as varchar(100)) node,
                cast('' as varchar(200))
from #tmp
where parentid = 0
UNION all
select  t.id, 
                t.parentid, 
                t.nodetype, 
                t.localname, 
                t.prefix, 
                t.namespaceuri, 
                t.datatype, 
                t.prev, 
                t.text,
                iif(t.nodetype = 1,xc.lvl+1,xc.lvl),
                cast(
                        CONCAT (
                                        xc.node
                                        ,iif(t.nodetype = 1, 
                                                CONCAT (
                                                            '/'
                                                            ,t.localname
                                                            )
                                                ,''
                                                )
                                        ) AS VARCHAR(100)
                            ),
                cast(xc.localname as varchar(200))
from #tmp t
inner join xml_cte xc
on xc.id = t.parentid
)
select * into #xmlcte from xml_cte

--select * from #xmlcte
--v2 change
select @max_lvl = max(lvl)--iif(max(lvl)>=4,1,0) -- the iif condition is just a hack, I dont know why it works
from #xmlcte 

select 
    @max_node = concat(max(node),'/'),
    @max_node_wo_slash = max(node) 
from #xmlcte 
where lvl = @max_lvl

select *,concat(parent_localname,'_',localname,' varchar(500)') fnl_col_nm,
                                case 
                                when lvl<@max_lvl then concat(replicate('../',@max_lvl-lvl+iif(nodetype=1,nodetype,0)),iif(nodetype=1,'','@'),localname) --v2 change
                                when lvl>@max_lvl then concat(replace(node,@max_node,''),iif(nodetype=1,'','/@'),localname)--v2 change
                                else concat('../',iif(nodetype=1,'',concat(parent_localname,'/@')),localname)--v2 change
                                end col_Struct
        ,concat(parent_localname,'_',localname) col_unq_nm
        ,ROW_NUMBER() over (order by(select 100)) sno
        ,concat('xmlname.value(''/Names[1]/name[',ROW_NUMBER() over (order by(select 100)),']'',''varchar(500)'') AS ',concat(parent_localname,'_',localname)) col_splt_nm
into #xml_col_struct
from #xmlcte
where nodetype <= 2--v2 change

--select * from #xml_col_struct
set @cnt = (select count(distinct col_unq_nm) from #xml_col_struct)

select @pre_stg_col_nm =
(
select concat(',',COLUMN_NAME)
from INFORMATION_SCHEMA.COLUMNS
where table_name = 'ZZZZZZ'
and COLUMN_NAME like 'col%'
and ORDINAL_POSITION <= @cnt+5
order by ORDINAL_POSITION
for xml path('')
)

set @sqlstr = concat(
                                        'insert into ZZZZZ(api_id,record_type,record_id,last_run_time,last_run_by',
                                        @pre_stg_col_nm,
                                        ')'
                                        )
select @xml_col =
(
select distinct concat(',',fnl_col_nm,' ''',col_Struct,'''',char(10)) 
from #xml_col_struct
order by 1
for xml path('')
)
set @xml_col = stuff(@xml_col,1,1,'')

select @unq_col =
(
select distinct concat(',',col_unq_nm )
from #xml_col_struct
order by 1
for xml path('')
)
set @unq_col = stuff(@unq_col,1,1,'')

select @in_tot_result_col = @in_tot_result_col - count(distinct col_unq_nm)
from #xml_col_struct

select @unq_xml_col =
(
select 
concat(',xmlname.value(''/Names[1]/name[',ROW_NUMBER() over (order by(select 100)),']'',''varchar(500)'') AS ',col_unq_nm,char(10))
from (select distinct col_unq_nm from #xml_col_struct) t
for xml path('')
)
set @unq_xml_col = stuff(@unq_xml_col,1,1,'')

set @sqlstr =
                        concat(
                                    iif(@in_need_colnm_result = 'Y',
                                    concat('
                                                ;WITH Split_Names (xmlname)
                                                AS
                                                (
                                                        SELECT 
                                                        CONVERT(XML,''<Names><name>''  
                                                        + REPLACE(''',@unq_col,''','','', ''</name><name>'') + ''</name></Names>'') AS xmlname
                                                )
                                                '
                                                --,@sqlstr
                                                ,char(10),
                                                ' SELECT ',@in_api_id,',''H'',0,''',@runtime,''',''',@runby,''',',char(10)
                                                ,@unq_xml_col,replicate(',NULL',@in_tot_result_col)--v2 change
                                                ,char(10)
                                                ,'FROM Split_Names'
                                                ,char(10)
                                                ,'union all'
                                                )
                                        ,''
                                        )
                                    --,iif(@in_need_colnm_result = 'Y','',@sqlstr)
                                    ,'
                                    SELECT ',@in_api_id,',''D'',ROW_NUMBER() over (order by(select 100)),''',@runtime,''',''',@runby,''',*'  
                                    ,replicate(',NULL',@in_tot_result_col)--v2 change
                                    ,char(10)
                                    ,'FROM   OPENXML (@idoc_inn, ''',@max_node_wo_slash,''',2)'   
                                    ,char(10)
                                    ,'WITH (',@xml_col,')'
                                    )

if @in_debug_flg = 'Y'
    begin
        select @max_lvl+1,@max_lvl,@max_node_wo_slash,@xml_col,@unq_col,@sqlstr,@unq_xml_col
        select * from #xml_col_struct--v2 change
    end
else
    begin
        set @param = '@idoc_inn int'
        exec sys.sp_executesql @sqlstr,@param,@idoc_inn = @idoc
    end
EXEC sp_xml_removedocument @idoc

END

读取 C# 类加载的 XML 文件的 SQL 代码。这也可以正常工作,但问题是所有行都在单独的行中,并且连接在一个点之后截断

create table #tmp(data_line nvarchar(max))

bulk insert #tmp
FROM '\\Server\\ZZZZ\\Downloads\\Data.xml'  
   WITH   
      ( 
                --firstrow = 1          
                 ROWTERMINATOR ='\n'  
      );  

select * from #tmp

C# 类

Object httpConn = Dts.Connections["HTTP"].AcquireConnection(null);
        HttpClientConnection myConnection = new HttpClientConnection(httpConn);
        myConnection.ServerURL = string.Format(("http://xxxx.com/jjjj"),"userid","password");
        byte[] webdata = myConnection.DownloadData();

        String result_data = Convert.ToBase64String(webdata);
        XmlDocument xd = new XmlDocument();
        XmlDictionaryReader xr = JsonReaderWriterFactory.CreateJsonReader(webdata, XmlDictionaryReaderQuotas.Max);

        xr.Read();
        xd.LoadXml(xr.ReadOuterXml());
        xd.Save("\\Server\\ZZZZ\\Downloads\\Data.xml"); 

【问题讨论】:

  • 您在这里可能遇到的是 SQL Server 的一些固有限制。每个非宽表的列数 1024,每个宽表的列数 30000。如果您使用的是非宽表并且 xml 每条记录的元素超过 1024 个,则表创建将失败。
  • 我们确信我们不会在任何 API 中看到超过 100 个元素的属性
  • 好吧,但这不是你在问题中所说的。你说的是任何 XML。
  • 对不起……
  • 重点在这里:在通用 tsql 表中。实际上它只是 a:基本结构是否简单(类似于键值对)或者这可能与 1:n 相关 数据深度嵌套? XML 是一件大事还是它可能携带许多记录?您需要 row-wise 结构(了解键值对),还是需要带有命名列的宽表。请提供一些(简化的)XML 示例,它们的外观以及您希望它如何存储。

标签: c# sql-server xml tsql sql-server-2014


【解决方案1】:

如果您获得格式正确的 XML。您可以使用数据集。具体使用Dataset.ReadXml()。这会将您的 xml 加载到 Dataset 对象中,而与 xml 标签无关。然后您可以使用 ADO.Net、linq2sql、EF 或任何其他通信方法将其放入数据库。

由于您将文件保存在服务器上,您可以使用以下代码:

DataSet ds = new DataSet();
ds.ReadXml("\\Server\\ZZZZ\\Downloads\\Data.xml"); 

然后您可以使用 foreach 循环遍历每个数据集表。 xml 中的属性将成为数据表中的列。

因此您的最终代码将类似于以下内容:

using (DataSet ds = new DataSet())
{
     ds.ReadXml("\\Server\\ZZZZ\\Downloads\\Data.xml");
     int nTableCounts = ds.Tables.Count;
     foreach(DataTable dt in ds.Tables)
     {
          using (dt)
          {
            //Put data in SQL table.
          }
     }
}

如果有什么不清楚的地方请告诉我。

【讨论】:

  • 感谢 pratik 将尝试等待您的示例代码
  • 我c#不太好所以想知道你引用的方法是不是第三方
  • @JayeshPrakash 否。它内置于 .net 功能中。如果您的项目中没有提到它,您将不得不使用 "System.Data" 程序集
  • 我们需要事先创建数据表的结构还是将其以元素作为列名动态创建?
  • @JayeshPrakash 它将被动态创建。
【解决方案2】:

感谢您提供这么多详细信息。现在 - 连同赏金 - 人们冲进来寻求帮助。

这不是真正的答案,至少它不是您实际问题的解决方案!而是一些想法/提示:

说实话……

这应该完全不同!

你目前的做法很糟糕……

  • 您阅读 XML 的方式逐行
  • FROM OPENXML 粉碎它的方式
  • 大量基于字符串的代码...
  • 无类型字符串值的无意义存储
  • 具有无意义列名的多列暂存表...

我怀疑这个概念是否适用于任何 xml 响应...

有效期

您的示例 1 甚至不是有效的 XML。

  • 缺少标签&lt;item&gt;
  • 错误的结束标签&lt;toBeHandledAtxxx type="string"&gt;xxxxxx&lt;/toBeHandledAtLuca&gt;
  • 日期时间格式不正确ISO8601...给定的格式可以正常工作,但表明此 XML 不是以最佳方式创建的...

如果这是您的实际响应,那么任何基于 XML 的方法都不会起作用。我希望发生这种情况是为了减少线路......

层次结构

示例 1 中,&lt;root&gt; 下方可以有任意数量的项目。有 9 !!! 潜在的1:n 数据区域(多个团队,多个版本......)。

在关系概念中,您必须将其分布在多个相关表中

你的 sample 2 很简单,但也有一个 3-level-hierarchy

你想如何把它放在你的多百列表中并排???由于笛卡尔积导致的大量行???

命名

您的 sample 2 可能很容易读入一个简单的结构。您可以使用local-name() 知道哪个是Customer,哪个是OrderDetail

但在您的示例 2 中,所有这些都称为 &lt;item&gt;...

结构

您的 sample 2 是普通的 attribute centered XML,而您的 sample 1 混合了属性和以元素为中心的数据。

导入

总有一天,您希望将临时表中的数据打乱到合适的结构中。从您粗略的多百列表 中读取意味着您必须了解有关原始 XML 的一切。您必须使用复杂的映射表才能知道col107 的值是什么(语义、类型、格式...)。对于此过程,您将需要每种 XML 的非常具体的例程

读取 XML 文件

在底部显示了如何从 SQL Server 读取 XML 文件

您遵循 逐行 方法,它让您声明:但问题是所有行都在单独的行中。最好这样尝试,它会一次性读取整个 XML:

DECLARE @x XML=
(
    SELECT x 
    FROM OPENROWSET(BULK 'C:\YourPath\YourXMLFile.xml', SINGLE_BLOB) AS YourFile(x)
);
select @x;

我的建议

特别是最后一点(每种 XML 的非常具体的例程)要求您 按原样 导入您的 XML。有 - 至少在我看来 - 绝对没有价值,您尝试将 任何 XML 放入一张巨大的普通表格中。最好使用带有一些元列和一个完整 XML 的单列的精简临时表(可能是 NVARCHAR(MAX) 以使其更能容忍无效的 XML)。 您在此处完全按照您阅读的方式填写 XML

无论如何,对于每种 XML,您都需要一个特定的读取过程。 直接为 XML 创建这个

不要使用FROM OPENXML 这已经过时了......在你的情况下,这仍然可能是一种可能的方式,但是 - 如果性能无关紧要 - 你应该更好地使用你在约翰·卡佩莱蒂斯回答。这将递归遍历几乎所有 XML 并在表格中提供信息 - 无需深入了解 XPathXQuery

最好是根据现代 XML 方法 .nodes().query().value() 为每种 XML 定义一个特定的读取过程。这将是干净、快速且可维护的。

如果您需要这方面的帮助,请回来提出新问题!

说服你的最后一个理由

想象一下,您的 XML 发送器在中间某处添加了一个元素。在您的表格中,相同的内容从col108 移动到col109。你真的想对你的阅读过程进行版本控制吗?如果您直接从 XML 读取数据,则添加的元素将被添加到读取过程中。没有此值的旧 XML 可以使用相同的引擎轻松读取...

【讨论】:

  • 非常感谢你们的cmets。有时,无论我们对设计的感觉如何,我们都必须遵守它们作为客户的约束。关于您的评论-“但是在您的示例 2 中,它们都被称为 ...”由于此处在 C# 类中从 JSON 到 XML 的转换 XmlDictionaryReader xr = JsonReaderWriterFactory.CreateJsonReader(webdata, XmlDictionaryReaderQuotas.Max);,该项目在每个级别都被迭代
  • @JayeshPrakash 您是否尝试过几行代码在一次调用中将 XML 文件读入变量?如果这对您有用,请尝试使用 John Capeletti 链接的函数。我很好奇:如果收到的 XML 发生变化(内部顺序、新元素……),会发生什么?您如何存储笛卡尔 1:n 数据?我会默默地添加一个 XML 列,然后按照我的方法告诉客户,一切都取自这个 crazy-generic-mega-moster-table :-)
  • 是的,我做到了,真正的 XML 文件大小为 10MB,关系大小为 6000 条记录仍在运行(一个多小时前开始)。另一个问题是如何通过这行代码XmlDictionaryReader xr = JsonReaderWriterFactory.CreateJsonReader(webdata, XmlDictionaryReaderQuotas.Max); 停止为 C# 中的每个级别生成
  • 出了点问题... 10MB 不是那么大... 尝试使用一个很小(但有效!)的测试文件的方法。这应该工作得更快......
  • 小文件 - 样本 2 快速完成。一个有 100 条记录的大一点的文件也可以正常工作。我怀疑在每个级别之前添加的 级别可能会导致交叉连接。我正在检查它。如果我能以某种方式阻止该 生成,那么两种解决方案的生活将变得更容易。谢谢你和我在一起。
【解决方案3】:

考虑以下

我使用辅助函数(无耻地从http://beyondrelational.com/modules/2/blogs/28/posts/10495/xquery-lab-58-select-from-xml.aspx ... 进行了一些调整,即范围键)将几乎任何 XML 转换为层次结构。

我应该注意,我选择临时表而不是一系列 CTE 只是为了性能和方便。我敢肯定,如果您愿意,您可以轻松迁移到 CTE 方法。

关于性能,我们正在查看提供的示例文件的 90 - 110 毫秒。我无法评价这对 LARGE XML 源的性能有多好。

SQL

--Drop Table #TempBase;Drop Table #TempCols;Drop Table #TempHier;Drop Table #TempPivot

-- Declare @Vars
Declare @in_api_id int = 1
Declare @runby varchar(30)='Some User'
Declare @XML xml ='<Root><Customer CustomerID="VINET" ContactName="Paul Henriot"><Order CustomerID="VINET" EmployeeID="5" OrderDate="1996-07-04T00:00:00"><OrderDetail OrderID="10248" ProductID="11" Quantity="12"/><OrderDetail OrderID="10248" ProductID="42" Quantity="10"/></Order></Customer><Customer CustomerID="LILAS" ContactName="Carlos Gonzlez"><Order CustomerID="LILAS" EmployeeID="3" OrderDate="1996-08-16T00:00:00"><OrderDetail OrderID="10283" ProductID="72" Quantity="3"/></Order></Customer></Root>'
--Declare @XML xml='<catalog><product description="Cardigan Sweater" product_image="cardigan.jpg"><catalog_item gender="Men''s"><item_number>QWZ5671</item_number><price>39.95</price><size description="Medium"><color_swatch image="red_cardigan.jpg">Red</color_swatch><color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch></size><size description="Large"><color_swatch image="red_cardigan.jpg">Red</color_swatch><color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch></size></catalog_item><catalog_item gender="Women''s"><item_number>RRX9856</item_number><price>42.50</price><size description="Small"><color_swatch image="red_cardigan.jpg">Red</color_swatch><color_swatch image="navy_cardigan.jpg">Navy</color_swatch><color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch></size><size description="Medium"><color_swatch image="red_cardigan.jpg">Red</color_swatch><color_swatch image="navy_cardigan.jpg">Navy</color_swatch><color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch><color_swatch image="black_cardigan.jpg">Black</color_swatch></size><size description="Large"><color_swatch image="navy_cardigan.jpg">Navy</color_swatch><color_swatch image="black_cardigan.jpg">Black</color_swatch></size><size description="Extra Large"><color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch><color_swatch image="black_cardigan.jpg">Black</color_swatch></size></catalog_item></product></catalog>'

--Select * From [dbo].[udf-XML-Hier](@XML) Order by R1

-- Generate Base Hier Data from XML
Select * Into #TempBase From [dbo].[udf-XML-Hier](@XML) Order by R1

-- Generate Required Columns with Sequence
Select *,ColSeq   = Row_Number() over (Order by MinR1),ColName  = concat('col',Row_Number() over (Order by MinR1) ),ColTitle = IIF(Attribute='','_','')+Element+IIF(Attribute='','','_'+Attribute)
 Into  #TempCols
 From (
        Select Element,Attribute,MinR1 = Min(R1)
        From   #TempBase
        Where  R1>1 
        Group  By Element,Attribute
      ) A

-- Extend Base Data with Col Seq
Select A.*,C.ColSeq,RowSeq = 1+Sum(RowFlg) over (Order By R1)
Into   #TempHier
From  (Select *,RowFlg =IIF(Lag(Lvl,1) over (Order By R1)>Lvl,1,0) From #TempBase) A
Join  #TempCols C on (A.Element=C.Element and A.Attribute=C.Attribute)

-- Generate Data to be Pivoted and Augment for Inheritance
Select RowSeq=0,ColSeq,ColName,Value = cast(ColTitle as varchar(max))
Into   #TempPivot
From   #TempCols
Union  All
Select A.RowSeq,A.ColSeq,A.ColName,Value = IsNull(B.Value,'')
From  (
        Select A.*,R1 = case when B.R1 is not null then B.R1 else (Select Max(R1) from #TempHier Where ColSeq=A.ColSeq and RowSeq<=A.RowSeq) end
         From (
                Select A.*,B.*
                 From (Select Distinct RowSeq From #TempHier) A
                 Join (Select * From #TempCols) B on (1=1) 
              ) A
         Left Join #TempHier B on (A.RowSeq=B.RowSeq and A.ColSeq=B.ColSeq )
      ) A
 Join  #TempHier B on (A.R1=B.R1)

-- Build and Execute the Final Select
Declare @SQL varchar(max) = ''
Select @SQL = @SQL+concat(',',ColName,'=max(case when ColSeq=',ColSeq,' then Value else null end)') from #TempCols Order by ColSeq
Select @SQL = '
Select api_id        = '+cast(@in_api_id as varchar(25))+' 
      ,record_type   = max(case when RowSeq=0 then ''H'' else ''D'' end)
      ,record_id     = RowSeq
      ,last_run_time = GetDate()
      ,last_run_by   ='''+@runby+''''
      +@SQL+'
 From  #TempPivot
 Group By RowSeq
 Order By RowSeq
'
Exec(@SQL)

退货

性能

我知道这是一个小样本,但结果在 80 到 160 毫秒之间返回。


表值函数(如果需要)

CREATE FUNCTION [dbo].[udf-XML-Hier](@XML xml)
Returns Table 
As Return
with  cte0 as ( 
                  Select Lvl       = 1
                        ,ID        = Cast(1 as int) 
                        ,Pt        = Cast(NULL as int)
                        ,Element   = x.value('local-name(.)','varchar(150)')
                        ,Attribute = cast('' as varchar(150))
                        ,Value     = x.value('text()[1]','varchar(max)')
                        ,XPath     = cast(concat(x.value('local-name(.)','varchar(max)'),'[' ,cast(Row_Number() Over(Order By (Select 1)) as int),']') as varchar(max))
                        ,Seq       = cast(10000001 as varchar(max))
                        ,AttData   = x.query('.') 
                        ,XMLData   = x.query('*') 
                  From   @XML.nodes('/*') a(x) 
                  Union  All
                  Select Lvl       = p.Lvl + 1 
                        ,ID        = Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10
                        ,Pt        = p.ID
                        ,Element   = c.value('local-name(.)','varchar(150)')
                        ,Attribute = cast('' as varchar(150))
                        ,Value     = cast( c.value('text()[1]','varchar(max)') as varchar(max) ) 
                        ,XPath     = cast(concat(p.XPath,'/',c.value('local-name(.)','varchar(max)'),'[',cast(Row_Number() Over(PARTITION BY c.value('local-name(.)','varchar(max)') Order By (Select 1)) as int),']') as varchar(max) )
                        ,Seq       = cast(concat(p.Seq,' ',10000000+Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10) as varchar(max))
                        ,AttData   = c.query('.') 
                        ,XMLData   = c.query('*') 
                  From   cte0 p 
                  Cross  Apply p.XMLData.nodes('*') b(c) 
              )
    , cte1 as (   
                  Select R1 = Row_Number() over (Order By Seq),A.*
                  From  (
                          Select  Lvl,ID,Pt,Element,Attribute,Value,XPath,Seq From cte0
                          Union All
                          Select Lvl       = p.Lvl+1
                                ,ID        = p.ID + Row_Number() over (Order By (Select NULL)) 
                                ,Pt        = p.ID
                                ,Element   = p.Element
                                ,Attribute = x.value('local-name(.)','varchar(150)')
                                ,Value     = x.value('.','varchar(max)')
                                ,XPath     = p.XPath + '/@' + x.value('local-name(.)','varchar(max)')
                                ,Seq       = cast(concat(p.Seq,' ',10000000+p.ID + Row_Number() over (Order By (Select NULL)) ) as varchar(max))
                          From   cte0 p 
                          Cross  Apply AttData.nodes('/*/@*') a(x) 
                        ) A 
               )

Select A.R1
      ,R2  = IsNull((Select max(R1) From cte1 Where Seq Like A.Seq+'%'),A.R1)
      ,A.Lvl
      ,A.ID
      ,A.Pt
      ,A.Element
      ,A.Attribute
      ,A.XPath
      ,Title = Replicate('|---',Lvl-1)+Element+IIF(Attribute='','','@'+Attribute)
      ,A.Value
 From  cte1 A

/*
Source: http://beyondrelational.com/modules/2/blogs/28/posts/10495/xquery-lab-58-select-from-xml.aspx

Declare @XML xml='<person><firstname preferred="Annie" nickname="BeBe">Annabelle</firstname><lastname>Smith</lastname></person>'
Select * from [dbo].[udf-XML-Hier](@XML) Order by R1
*/

编辑 - 只是为了好玩,我从 http://www.service-architecture.com/articles/object-oriented-databases/xml_file_for_complex_data.html ,结果如下:

【讨论】:

  • 我打赌这个功能是你最好的朋友 :-) 我只是想自己放置它。还是祝你好运……
  • @Shnugo 我只是喜欢可重用的代码。我想服务很多大师,而不必每次都重新发明轮子。
  • @Shnugo TVF 确实是这里的主力。 OP 有一些奇怪的格式要求,因此所有的二级代码。
  • 只能逐行分解未知的XML...链接函数是一个很好的工具,可以使用元数据(如完整路径)获取每个值.但我认为不应该支持 OP 希望存储所有数据并排...无论如何 +1 从我这边
  • @Shnugo 只需阅读您的帖子(两次),我同意您的看法。老实说,我认为这种结构几乎没有价值。也就是说,我过去做过一些遇到阻力并最终被证明是有价值的事情。我的祖父总是告诉我要“慢慢判断”。此外,我在挖掘谜题。
【解决方案4】:

为什么使用 SQL Server 反序列化文件。这是最好留给 c# 去做的事情。

我还没有在 C# 中这样做,但是您可以将 XML deserialize 转换为字符串列表,并遍历列表以构建要插入数据库的记录。这是一个generic deserialization method 可能会有所帮助。

我认为它会更快,并且不会惹恼您的 DBA 尝试在服务器中进行繁重的字符串操作。

【讨论】:

  • 嗨,迈克,感谢您的指点,我对 c# 不太擅长,所以请原谅我对存在的误解。如果我正确阅读了这个解决方案,仍然需要在一些中定义 XML 的结构类,这意味着我将让你定义我需要反序列化的所有 XML 的结构
  • 不是最好的例子,但你应该可以将它作为字符串传递。
  • 上面的海报可能已经有了更好的解决方案,但您也应该能够使用 XmlDictionaryReader 进行迭代。
  • 我同意这一点,应该使用 c# 从 xml 获取值然后插入到数据库中。如果你想反序列化 xml 字符串或文件,你可以试试我写的这个tool,它使用起来非常简单。如果您的公司不允许第三方 dll,您可以将源代码(只有少数类)复制到您的项目中。
猜你喜欢
  • 2019-06-21
  • 2021-11-02
  • 2019-09-21
  • 2013-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-10
  • 1970-01-01
相关资源
最近更新 更多