I have a table that defines the layout of a form in XML. There are <Control ...> nodes inside of it that have attributes like Id (GUID), and DataType (char), etc...

This XML data is used to create a form at run-time, when data is saved it's writen to XML in elements called <Field ...> that have the attributes: Name (matches the GUID in the Control node's Id), and Data (holds the value that was entered into the control).

We have an issue where when you .ToString a Date object it uses the machine's settings for date format. So dates could be saved in any possible format. I have updated the code to always save dates in YYYY/MM/DD format, but now I need to update existing data in the database to YYYY/MM/DD. Since there's no way for us to possibly know what format the date was saved in, we're just going to assume it was saved as MM/DD/YYYY.

So now my issue is trying to update the XML in SQL Server. Using xquery and the CROSS APPLY .nodes( ) stuff I am able to write a query to find all of the incorrectly formatted dates, but I cannot figure out how to update them.

This SQL script will create table variables and populate them with some test data, and has the query that will return the values of the incorect dates. the last part that is commented out is how I attempted to update them to the new YYYY/MM/DD format, but if you uncomment it you can see that it doesn't work.

Anyone have any ideas?

P.S. This can only be done in an SQL script. I know this would be super easy to write a function in .NET that reads in all the xml data and updates the attributes and then saves the data back to the database, but that is not possible in this case because: reasons.

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

--Edit-- The xml in @ScreenData can also contain <Item> nodes that contain <Field> nodes. When this happens you will have multiple <Field> nodes that have the same Name attribute value. This is the case when a screen has a listview of items, you will have multiple items that each reference the same control but have their own values. ScreenDataID 5 show this.

有帮助吗?

解决方案

I won't show you exactly what you want. I will show you how to change all your dates to yyyy-mm-dd since that is what they should be in an XML.

You have figured things out very well so far so I don't think you will have any problems to change the code below to your needs if you decide to use another date format.

You can't update more than one value at a time in the XML so the update has to be done in a loop over the the forms and fields you need to update.

SET DATEFORMAT ymd; is necessary for the conversion from string to date to work from both yyyy/mm/dd and mm/yy/dd at the same time.

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;
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top