【问题标题】:Upsert on SQL Server table from XML从 XML 更新 SQL Server 表
【发布时间】:2012-05-10 05:10:07
【问题描述】:

我正在尝试在 C# 中创建一个小型控制台应用程序,以便根据 XML 文件的内容以最快的方式在 SQL Server 2008 中的产品表 (ITEMS) 上执行插入操作。我已经有一个 .XSD 文件,其中包含到 SQL 表的正确映射(对于下面概述的方法,这可能不是必需的)。

这是我的方法的高级:

  1. 读取 XML,使用它创建表。
  2. 使用从 XML 文件创建的表对 ITEMS 表执行 MERGE。
    2a.如果该项目存在,请更新它。
    2b。如果该项目不存在,则插入它。
  3. 创建仅包含插入 XML 的记录的日志。

考虑以下ITEMS表和XML文件:

物品

  Item_Id    Name    Price  
     1       Coke     5.00  
     2       Pepsi    3.00  
     3       Sprite   2.00   

ITEMS.XML

   <?xml version="1.0" encoding="ISO-8859-1"?>
   <Item>
    <Id>5</Id>
    <Name>Mountain Dew</Name>
    <Price>4.50</Price>
   </Item>
   <Item>
    <Id>3</Id>
    <Name>Sprite Zero</Name>
    <Price>1.75</Price>
   </Item>

导入后,ITEMS 表应如下所示:

物品

  Item_Id    Name         Price  
     1       Coke          5.00  
     2       Pepsi         3.00  
     3       Sprite Zero   1.75  
     5       Mountain Dew  4.50

完成后,我还需要生成一个 XML 格式的日志文件,其中包含插入到表中的“新”记录 (ITEMS_LOG.XML):

ITEMS_LOG.XML

   <?xml version="1.0" encoding="ISO-8859-1"?>
   <Item>
    <Id>5</Id>
    <Name>Mountain Dew</Name>
    <Price>4.50</Price>
   </Item>

我曾尝试使用 SQLXMLBulkLoad 实现此功能,但不幸的是,它不提供我需要的日志记录,也不允许我访问从 SQL Server 返回的任何消息(即插入/更新的内容)。尽管我有中级的 SQL 专业知识,但我对使用 XML 还是很陌生,尤其是在这种情况下。任何帮助/指导将不胜感激!

【问题讨论】:

  • 你卡在哪里了?你能发一些你的代码吗?
  • 您确定在 2 个不同的 items.xml 文件中,如果一个产品与数据库中已有的 1 个重复(按名称/价格),它会具有相同的 ID?
  • @ChrisW 不,它们不一定具有相同的 ID。有可能有两个不同 id 的同一个项目。
  • @Sam1 现在,我被困在如何将数据实际导入 SQL Server 中。我不知道我是否应该使用 XML 在 C# 中填充数据集,或者只是尝试将我的 C# 应用程序用作实际执行导入的 TSQL 命令的包装器。在这种情况下,速度非常重要。
  • @TelJanini:我将使用上述参数创建一个存储过程,并在代码中传递值。如果您在编码/存储过程方面需要帮助,请告诉我,我有一个示例..

标签: c# .net xml sql-server-2008 tsql


【解决方案1】:

您可以将mergeoutput 一起用于表变量,然后查询表变量以构建日志XML。

将它放在一个存储过程中,其中项目 XML 作为输入参数,日志 XML 作为输出参数。

create procedure AddItemXML
  @ItemsXML xml,
  @ItemsLogXML xml out
as

declare @Changes table
(
  Item_Id int,
  Name nvarchar(20),
  Price money,
  Action nvarchar(10)
);

merge Items as T
using
  (
    select T.N.value('Id[1]', 'int') as Item_Id,
           T.N.value('Name[1]', 'varchar(20)') as Name,
           T.N.value('Price[1]', 'money') as Price
    from @ItemsXML.nodes('/Item') T(N)
  ) as S
on T.Item_Id = S.Item_Id
when matched then
  update set Name = S.Name, Price = S.Price
when not matched then
  insert (Item_Id, Name, Price) values (S.Item_Id, S.Name, S.Price)
output inserted.Item_Id,
       inserted.Name,
       inserted.Price,
       $action 
  into @Changes;

set @ItemsLogXML = 
  (
    select Item_Id as ID,
           Name,
           Price
    from @Changes
    where Action = 'INSERT'
    for xml path('Item'), type
  );

SE-Data 上的工作示例

【讨论】:

  • 感谢@Mikael Eriksson,这对我非常有用。
  • @GR7 您可能还会发现这个有点帮助。 stackoverflow.com/questions/16773242/…
  • 再次感谢@Mikael!虽然这对我有用,但当我尝试使用我将在应用程序中收到的大量 XML 数据时,花了 7 分钟......这对我来说真的不能接受。我正在研究 SqlBulkCopy 将 XML 复制到临时表中并从那里应用 MERGE。有什么建议吗?
  • @GR7 从未使用过 SqlBulkCopy。
【解决方案2】:

希望这对您有所帮助,我所做的是创建一个存储过程,如下所示。基本上,存储过程采用 xml 值并检查从代码传递的标志并确定它是插入还是更新:

DECLARE @xml xml
SET @xml = @xmlCredentials

SELECT
      item.value('@Id', 'int') As ID,
      item.value('@AgentID', 'int') As AgentID,
      item.value('@Username', 'varchar (50)') As Username,
      item.value('@Password', 'varchar (50)') As [Password],
      item.value('@IsDirty', 'bit') As IsDirty,
      item.value('@IsDeleted', 'bit') As IsDeleted
INTO #tmp
FROM @xml.nodes('Credentials/Credential') x(item)

BEGIN TRY
BEGIN TRAN
      INSERT INTO Credentials (AgentID, Username, [Password])
          SELECT
              AgentID, Username, [Password]
          FROM
              #tmp
          WHERE
              ID = 0 AND IsDirty = 1
      UPDATE c
      SET c.[AgentID] = t.AgentID,
          c.[Username] = t.Username,
          c.[Password] = t.[Password]
      FROM
          [dbo].[Credentials] c
      JOIN 
          #tmp t ON t.Id = c.ID
      WHERE
          t.IsDirty = 1 AND t.IsDeleted = 0

      DELETE FROM [dbo].[Credentials]
      FROM [dbo].[Credentials] c
      JOIN #tmp t ON t.Id = c.ID
      WHERE 
          t.IsDirty = 1 AND t.IsDeleted = 1

      COMMIT TRAN
END TRY
BEGIN CATCH

      IF @@TRANCOUNT > 0
            ROLLBACK TRAN

      DECLARE @errorMSG varchar(4000)
      DECLARE @errorSeverity int
      DECLARE @errorState int

      SET @errorMSG = ERROR_MESSAGE()
      SET @errorSeverity = ERROR_SEVERITY()
      SET @errorState = ERROR_STATE()

      RAISERROR (@errorMSG,
                        @errorSeverity, @errorState);

END CATCH

SELECT [ID], [AgentID], [Username], [Password]
FROM [dbo].[Credentials]

在后面的代码中我有我的 xml 并将 xml 作为参数传递给存储过程:

// read xml and assign it to string variable
string xml = readxml();

try
{
    string command = "EXEC SaveCredentails '" + xml + "'";
}
catch(Exception e)
{
}

【讨论】:

    【解决方案3】:

    我会使用临时表将 xml 导入 SQL Server 表。添加一个额外的列来指示操作(插入或更新)。然后像往常一样使用常规 sql 执行 upserts。然后,您可以使用临时表生成所需的 XML 日志记录(读取操作列以确定它是插入还是更新)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多