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.