【问题标题】:Updating an attribute with xquery in sql server 2008 R2在 sql server 2008 R2 中使用 xquery 更新属性
【发布时间】:2013-02-05 21:39:29
【问题描述】:

我有一个表格,它用 XML 定义了表单的布局。它内部有<Control ...> 节点,具有Id (GUID) 和DataType (char) 等属性...

此 XML 数据用于在运行时创建表单,当保存数据时,它会在名为 <Field ...> 的元素中写入 XML,这些元素具有以下属性:名称(与控制节点 ID 中的 GUID 匹配)和数据(保存输入到控件中的值)。

我们有一个问题,当您 .ToString 一个 Date 对象时,它使用机器的日期格式设置。所以日期可以以任何可能的格式保存。我已更新代码以始终以YYYY/MM/DD 格式保存日期,但现在我需要将数据库中的现有数据更新为YYYY/MM/DD。由于我们无法知道日期的保存格式,因此我们假设它保存为MM/DD/YYYY

所以现在我的问题是尝试更新 SQL Server 中的 XML。使用 xquery 和 CROSS APPLY .nodes( ) 东西,我可以编写一个查询来查找所有格式错误的日期,但我不知道如何更新它们。

此 SQL 脚本将创建表变量并使用一些测试数据填充它们,并具有将返回错误日期值的查询。最后被注释掉的部分是我如何尝试将它们更新为新的YYYY/MM/DD 格式,但如果您取消注释,您会发现它不起作用。

有人有什么想法吗?

附:这只能在 SQL 脚本中完成。我知道在 .NET 中编写一个读取所有 xml 数据并更新属性然后将数据保存回数据库的函数非常容易,但在这种情况下这是不可能的,因为:原因。

DECLARE @Form AS Table
(
  FormID INT,
  FormDataXML XML
);

DECLARE @ScreenData AS Table
(
  ScreenDataID INT,
  FormID INT,
  ScreenDataXML XML 
);

DECLARE @ControlsToUpdate AS TABLE
(
  FormID INT,
  ControlID char(36),
  DataType CHAR(1)
);

INSERT INTO @Form
VALUES (1, '<Form><Control Id="00000000-0000-0000-0000-000000000000" DataType="D" /><Control Id="00000000-0000-0000-0000-000000000001" DataType="N" /></Form>');
INSERT INTO @Form
VALUES (2, '<Form><Control Id="00000000-0000-0000-0000-000000000002" DataType="D" /><Control Id="00000000-0000-0000-0000-000000000003" DataType="D" /></Form>');

INSERT INTO @ScreenData
VALUES (1, 1, '<ScreenData><Field Name="00000000-0000-0000-0000-000000000000" Data="01/31/2012" /><Field Name="00000000-0000-0000-0000-000000000001" Data="1234.56" /></ScreenData>');
INSERT INTO @ScreenData
VALUES (2, 1, '<ScreenData><Field Name="00000000-0000-0000-0000-000000000000" Data="02/28/2013" /><Field Name="00000000-0000-0000-0000-000000000001" Data="0" /></ScreenData>');
INSERT INTO @ScreenData
VALUES (3, 2, '<ScreenData><Field Name="00000000-0000-0000-0000-000000000002" Data="03/31/2013" /><Field Name="00000000-0000-0000-0000-000000000003" Data="04/30/2013" /></ScreenData>');
INSERT INTO @ScreenData
VALUES (4, 2, '<ScreenData><Field Name="00000000-0000-0000-0000-000000000002" Data="2013/05/31" /><Field Name="00000000-0000-0000-0000-000000000003" Data="2013/06/30" /></ScreenData>');
--Data treated as scheduled items
INSERT INTO @ScreenData
VALUES (5, 2, '<ScreenData><Item><Field Name="00000000-0000-0000-0000-000000000002" Data="01/01/2012" /><Field Name="00000000-0000-0000-0000-000000000003" Data="02/02/2012" /></Item><Item><Field Name="00000000-0000-0000-0000-000000000002" Data="03/03/2012" /><Field Name="00000000-0000-0000-0000-000000000003" Data="04/04/2012" /></Item></ScreenData>')

INSERT INTO @ControlsToUpdate
SELECT FormID,  
data.control.value('@Id', 'char(36)'),
data.control.value('@DataType', 'char(1)')
FROM @Form
CROSS APPLY FormDataXML.nodes('//Control') data(control)
WHERE data.control.value('@DataType', 'char(1)') = 'D';


--This will display the ScreenDataID, FormID, ControlID, and current Date value for dates that are in the old mm/dd/yyyy format
SELECT d.ScreenDataID, d.FormID, c.ControlID,
data.field.value('@Data', 'char(10)') as Date
FROM @ScreenData d
CROSS APPLY d.ScreenDataXML.nodes('//Field') as data(field)
INNER JOIN @ControlsToUpdate c ON c.ControlID = data.field.value('@Name', 'CHAR(36)') 
                              AND d.FormID = c.FormID
WHERE data.field.value('@Data', 'char(10)') 
LIKE '[01][0123456789]/[0123][0123456789]/[12][0123456789][0123456789][0123456789]';

--UPDATE d
--SET data.field.modify('replace value of (/@Data) with "' + 
--    SUBSTRING(data.field.value('@Data', 'char(10)'), 7, 4) + '/' + 
--    SUBSTRING(data.field.value('@Data', 'char(10)'), 1, 2) + '/' + 
--    SUBSTRING(data.field.value('@Data', 'char(10)'), 4, 2) + '"')
--FROM @ScreenData d
--CROSS APPLY d.ScreenDataXML.nodes('//Field') as data(field)
--INNER JOIN @ControlsToUpdate c ON c.Id = data.field.value('@Name', 'CHAR(36)') 
--                              AND d.FormID = c.FormID
--WHERE data.field.value('@Data', 'varchar(MAX)') 
--LIKE '[01][0123456789]/[0123][0123456789]/[12][0123456789][0123456789][0123456789]';

GO

--编辑-- @ScreenData 中的 xml 还可以包含 &lt;Item&gt; 节点,其中包含 &lt;Field&gt; 节点。发生这种情况时,您将拥有多个具有相同 Name 属性值的 &lt;Field&gt; 节点。当屏幕具有项目列表视图时就是这种情况,您将拥有多个项目,每个项目都引用相同的控件但有自己的值。 ScreenDataID 5 显示这个。

【问题讨论】:

  • 一行中可以更新多个属性吗?
  • 每个元素只有 1 个属性要更新,但每个 XML 可以有多个元素,每个元素都有 1 个属性要更新。在示例数据中,ScreenDataID 1 有 1 个元素需要更新属性,ScreenDataID 2 有1 个元素需要更新属性,ScreenDataID 3 有2 个元素需要更新属性,ScreenDataID 4 不需要任何更新。

标签: xml sql-server-2008 tsql xquery-sql


【解决方案1】:

我不会确切地告诉你你想要什么。我将向您展示如何将所有您的日期更改为 yyyy-mm-dd,因为它们应该在 XML 中。

到目前为止,您已经很好地解决了问题,所以如果您决定使用另一种日期格式,我认为根据您的需要更改下面的代码不会有任何问题。

您一次不能在 XML 中更新多个值,因此更新必须在您需要更新的表单和字段的循环中完成。

SET DATEFORMAT ymd; 是从字符串到日期的转换同时从yyyy/mm/ddmm/yy/dd 工作所必需的。

SET DATEFORMAT ymd;

DECLARE @FormID INT;
DECLARE @FieldID UNIQUEIDENTIFIER;
DECLARE @I INT;

DECLARE ControlsToUpdate CURSOR FORWARD_ONLY READ_ONLY FOR
SELECT F.FormID,
       T.N.value('@Id', 'uniqueidentifier') AS FieldID
FROM @Form AS F
CROSS APPLY F.FormDataXML.nodes('/Form/Control') AS T(N)
WHERE T.N.value('@DataType', 'char(1)') = 'D';

OPEN ControlsToUpdate;

FETCH NEXT FROM ControlsToUpdate 
INTO @FormID, @FieldID;

WHILE @@FETCH_STATUS = 0
BEGIN

  SELECT @I = MAX(S.ScreenDataXML.value('count(//Field[@Name = sql:variable("@FieldID")])', 'INT'))
  FROM @ScreenData AS S
  WHERE S.FormID = @FormID;

  WHILE @I > 0
  BEGIN
    UPDATE S
    SET ScreenDataXML.modify('replace value of 
                              ((//Field[@Name = sql:variable("@FieldID")]
                                 /@Data)[sql:variable("@I")])[1] 
                              with sql:column("T.D")')
    FROM @ScreenData AS S
    CROSS APPLY 
      (SELECT S.ScreenDataXML.value('((//Field[@Name = sql:variable("@FieldID")]
                                        /@Data)[sql:variable("@I")])[1]', 'DATE')) AS T(D)
    WHERE S.FormID = @FormID;

    SET @I -= 1;
  END

  FETCH NEXT FROM ControlsToUpdate
  INTO @FormID, @FieldID;
END

CLOSE ControlsToUpdate;

【讨论】:

  • 我忘了说,同一个控件可能会在@ScreenData 的 XML 中多次出现。我将使用附加信息编辑问题。
  • 我尝试通过添加嵌套循环并增加索引并将 [1] 替换为 [sql:variable("@Index")] 来修改您的脚本,但这根本不起作用.
  • @Nick 就是这样做的方法。我可以稍后更新答案。同时你可以看看我的第一个答案(编辑后点击链接)。这基本上就是您在游标循环中需要的循环。
  • 哦,我明白了。我试图只做(//Field[@Name = sql:variable("@FieldID")]/@Data)[sql:variable("@Index")],但我需要将整个东西包裹在 ( ) 周围并在其末尾添加 [1] 。谢谢。
  • @Nick 您只需要更改交叉应用中的部分即可。将 varchar 指定为数据类型,并使用仍在交叉应用中的常规 tsql 内容进行修改。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-16
相关资源
最近更新 更多