Replace multiple values in XML column based on a mapping table - sql

Suppose I have a mapping table called tblMap that just maps an old attribute ID to a new attribute ID (oldID -> newID). NOTEWORTHY: newID is not contained in the list of oldID's.
I then have a table tblData which contains an xml string that has a number of attribute ids. I would like to replace all the current attribute ids with the newIDs that are found in tblMap. If an id mapping is not found in tblMap then it should stay as is. Any hints on how I can achieve this?
WHAT I TRIED:
I was trying to coerce something using XMLText.modify('replace value of ...') as described in: This StackOverflow Article but haven't been successful at getting it to work.
CREATE TABLE tblmap (
oldid INT,
newid INT
)
GO
INSERT INTO tblMap
VALUES
( 58, 1002),
( 85, 5002),
( 70, 3202),
(2, 2340),
(5, 7432)
GO
CREATE TABLE tblData ( [SourceID] int, [SourceRecID] bigint, [Value] xml )
GO
INSERT INTO tblData
VALUES
( 1, 0, N'<attributes><attribute id="58" value="0" /><attribute id="86" value="1" /><attribute id="85" value="1" /><attribute id="70" value="0" /><attribute id="38" value="0" /><attribute id="68" value="0" /><attribute id="42" value="1" /><attribute id="67" value="1" /><attribute id="62" value="1" /></attributes>' ),
( 1, 686, N'<attributes><attribute id="1" value="0.25" /><attribute id="4" value="1" /><attribute id="10" value="3" /><attribute id="11" value="1" /><attribute id="12" value="6" /></attributes>' ),
( 1, 687, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="9" value="1" /><attribute id="10" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' ),
( 1, 688, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' )
SELECT *
FROM tblMap
GO
SELECT *
FROM tblData
GO
I have constructed all the schema/sample data here for your convenience:
https://rextester.com/MUMI61854

I would try to completely recreate the entire XML (or rather the /attributes node) and update the table with new value:
declare #tblmap table (oldid INT, newid INT);
INSERT INTO #tblMap
VALUES
( 58, 1002),
( 85, 5002),
( 70, 3202),
(2, 2340),
(5, 7432);
declare #tblData table ([SourceID] int, [SourceRecID] bigint, [Value] xml);
INSERT INTO #tblData
VALUES
( 1, 0, N'<attributes><attribute id="58" value="0" /><attribute id="86" value="1" /><attribute id="85" value="1" /><attribute id="70" value="0" /><attribute id="38" value="0" /><attribute id="68" value="0" /><attribute id="42" value="1" /><attribute id="67" value="1" /><attribute id="62" value="1" /></attributes>' ),
( 1, 686, N'<attributes><attribute id="1" value="0.25" /><attribute id="4" value="1" /><attribute id="10" value="3" /><attribute id="11" value="1" /><attribute id="12" value="6" /></attributes>' ),
( 1, 687, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="9" value="1" /><attribute id="10" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' ),
( 1, 688, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' );
SELECT * FROM #tblMap;
SELECT * FROM #tblData;
-- Update table with new XML
with cte as (
select d.*, (
select isnull(m.newid, a.c.value('./#id', 'int')) as [#id], a.c.value('./#value', 'nvarchar(max)') as [#value]
from d.Value.nodes('/attributes[1]/attribute') a(c)
left join #tblmap m on m.oldid = a.c.value('./#id', 'int')
for xml path('attribute'), type, root('attributes')
) as [NewValue]
from #tblData d
)
update c set Value = NewValue
from cte c;
-- New version
select * from #tblData;
(I have turned your tables into table variables, as it leaves zero footprint on the instance. Everything else is the same.)
Unfortunately, this approach can become prohibitively difficult to implement if your actual XML schema is more complex than your example shows, and involves additional unpredictable elements and / or attributes under the /attributes node. In that case, I would recommend either a FLWOR (which is slow and quite difficult to write, at least for me) or cursored update.
To DEBUG:
-- Update table with new XML
with cte as (
select d.*, (
select isnull(m.newid, a.c.value('./#id', 'int')) as [#id], a.c.value('./#value', 'nvarchar(max)') as [#value]
from d.Value.nodes('/attributes[1]/attribute') a(c)
left join #tblmap m on m.oldid = a.c.value('./#id', 'int')
for xml path('attribute'), type, root('attributes')
) as [NewValue]
from #tblData d
)
SELECT c.SourceID,
c.SourceRecID,
c.Value,
c.NewValue
from cte c;

My suggestion calls XQuery to the rescue (txh Roger Wolf for the declared table variables, used them too...):
declare #tblmap table (oldid INT, newid INT);
INSERT INTO #tblMap
VALUES
( 58, 1002),
( 85, 5002),
( 70, 3202),
(2, 2340),
(5, 7432);
declare #tblData table ([SourceID] int, [SourceRecID] bigint, [Value] xml);
INSERT INTO #tblData
VALUES
( 1, 0, N'<attributes><attribute id="58" value="0" /><attribute id="86" value="1" /><attribute id="85" value="1" /><attribute id="70" value="0" /><attribute id="38" value="0" /><attribute id="68" value="0" /><attribute id="42" value="1" /><attribute id="67" value="1" /><attribute id="62" value="1" /></attributes>' ),
( 1, 686, N'<attributes><attribute id="1" value="0.25" /><attribute id="4" value="1" /><attribute id="10" value="3" /><attribute id="11" value="1" /><attribute id="12" value="6" /></attributes>' ),
( 1, 687, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="9" value="1" /><attribute id="10" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' ),
( 1, 688, N'<attributes><attribute id="1" value="2.00" /><attribute id="2" value="60.00" /><attribute id="3" value="-1" /><attribute id="5" value="252.00" /><attribute id="6" value="0" /><attribute id="7" value="1" /><attribute id="11" value="2" /><attribute id="12" value="10" /></attributes>' );
--The query will do the whole process in one single pass
WITH CombineThem AS
(
SELECT d.SourceID
,d.SourceRecID
,d.[Value]
,(SELECT
(SELECT *
FROM #tblMap
FOR XML PATH('map'),ROOT('maps'),TYPE)
,[Value] AS [*]
FOR XML PATH('Combined'),TYPE) AS Combined
FROM #tblData d
)
,updateableCTE AS
(
SELECT ct.[Value]
,ct.Combined
.query('<attributes>
{
for $attr in /Combined/attributes/attribute
return <attribute id="{
(
/Combined/maps/map[oldid[1]=$attr/#id]/newid
,$attr/#id
)[1]
}"
value="{$attr/#value}"/>
}
</attributes>') NewValue
FROM CombineThem ct
)
UPDATE updateableCTE SET [Value]=NewValue;
--check the result
SELECT * FROM #tblData;
Some Explanation
In order to use the mapping and the data in XQuery I create a combined XML in the first CTE. This will include the full <attributes> element together with a <maps> element.
The .query() will run through the attributes and search the <maps> for a fitting re-mapping. The magic happens in (val1,val2)[1]. This behaves like COALESCE(). It will pick the first non-null-value, which is either the fitting new id or the existing value.
Instead of updating the XML using .modify() the final step will set the [Value] column to the newly created XML in one go.

Honestly, I'm not 100% the reliability on ORDER BY (SELECT NULL) here, however, I don't have many options other than to hope the order is that of the nodes.
Anyway, the solution involves dynamic SQL; there may be a "better" way of doing this, but if there is I don't know it. I suggest doing some decent testing first, however, this appears to get the results you're after:
DECLARE #SQL nvarchar(MAX);
SET #SQL = STUFF((SELECT NCHAR(10) +
N'UPDATE tblData' + NCHAR(10) +
N'SET [Value].modify(''replace value of (/attributes/attribute/#id)[' + CONVERT(varchar(4),ROW_NUMBER() OVER (PARTITION BY D.SourceID, D.SourceRecID ORDER BY (SELECT NULL))) + N'] with "' + CONVERT(varchar(4),ISNULL(M.newid,V.AA.value('#id','int'))) + N'"'')' + NCHAR(10) +
N'WHERE SourceID = ' + CONVERT(varchar(4),D.SourceID) + NCHAR(10) +
N' AND SourceRecID = ' + CONVERT(varchar(4),D.SourceRecID) + N';'
FROM tblData D
CROSS APPLY D.[Value].nodes('attributes/attribute') V(AA)
LEFT JOIN tblmap M ON V.AA.value('#id','int') = M.oldid
FOR XML PATH(N'')),1,1,N'');
EXEC sp_executesql #SQL;

Related

Add subfield set with a bit map to ISO 8583 message using JPOS

I am new to JPOS. I want to add a set of sub fields into a single field with a bitmap. I tried with following URL but I didn't get any good result.
PISO 8583 subfield using JPOS
What I want to do :
I have to send a "0100" request. In this case I want to put CVV value into "126.10" field. Field "126" has many sub fields and field "126" has a bit map.
Is it possible to do this kind of case using JPOS? If it is possible how can we do that. If you have any sample please help me to complete this task.
Thank you.
With jPos it is possible to configure an isofieldpackager in the packager XML file as shown in the example below. The "emitBitmap" property determines if field 56 will have a bitmap.
Sample packager MXL file with SubFieldPackager in field 56:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE isopackager PUBLIC
"-//jPOS/jPOS Generic Packager DTD 1.0//EN"
"http://jpos.org/dtd/generic-packager-1.0.dtd">
<isopackager>
<isofield id="0" length="4" name="MESSAGE TYPE INDICATOR" pad="true" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="1" length="8" name="BIT MAP" class="org.jpos.iso.IFB_BITMAP" />
<isofield id="2" length="99" name="?" class="org.jpos.iso.IFB_LLNUM" />
<isofield id="3" length="6" name="PROCESSING CODE" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="11" length="6" name="?" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="12" length="6" name="?" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="13" length="4" name="?" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="14" length="4" name="?" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="24" length="3" name="?" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="39" length="2" name="?" class="org.jpos.iso.IF_CHAR" />
<isofield id="41" length="8" name="?" class="org.jpos.iso.IF_CHAR" />
<isofield id="42" length="999" name="?" class="org.jpos.iso.IFB_LLLBINARY" />
<isofieldpackager id="56" length="999" name="ADDITIONAL TRANSACTION DATA" class="org.jpos.iso.IFB_LLLBINARY"
packager="org.jpos.iso.packager.GenericSubFieldPackager" emitBitmap="true">
<isofield id="1" length="2" name="PHONE PREFIX" pad="true" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="2" length="9" name="PHONE" pad="true" class="org.jpos.iso.IFB_NUMERIC" />
<isofield id="3" length="99" name="?" class="org.jpos.iso.IFB_LLBINARY" />
</isofieldpackager>
<isofield id="61" length="999" name="SERVER INFORMATIONS" class="org.jpos.iso.IFB_LLLCHAR" />
</isopackager>
The projects of this file can be found in the repositories of the links below. They are simple projects that send and respond ISO 8583 messages.
Repositories of ISO 8583 projects:
https://github.com/alexlirio/iso-sender
https://github.com/alexlirio/iso-responder
In my case I need to include the bitmap field like this.
<isofield
id="0"
length="8"
name="BIT MAP"
class="org.jpos.iso.IFA_BITMAP" />
Also set this two attributes on the isofieldpackager tag.
emitBitmap="true"
bitmapField="0"
<isofieldpackager
id="56"
length="999"
name="ADDITIONAL TRANSACTION DATA"
class="org.jpos.iso.IFA_LLLNUM"
packager="org.jpos.iso.packager.GenericSubFieldPackager"
emitBitmap="true"
bitmapField="0">
<isofield
id="0"
length="8"
name="BIT MAP"
class="org.jpos.iso.IFA_BITMAP" />
<isofield
id="1"
length="2"
name="???"
pad="true"
class="org.jpos.iso.IFA_NUMERIC" />
<isofield
id="2"
length="9"
name="???"
pad="true"
class="org.jpos.iso.IFA_NUMERIC" />
<isofield
id="3"
length="99"
name="???"
class="org.jpos.iso.IFA_NUMERIC" />
</isofieldpackager>

Get XML from different Tables

Good day to you!
I have a question. Can't get how combine 2 tables in 1 result XML.
Here is the sample
DECLARE #t1 table (ID int identity(1,1), SomeField varchar(50))
DECLARE #t2 table (ID int identity(1,1), SomeField varchar(50), AnotherField varchar(50))
INSERT INTO #t1 (SomeField) VALUES ('rec1'),('rec2'),('rec3'),('rec4')
INSERT INTO #t2 (SomeField,AnotherField) VALUES ('s106','here'),('s12','just'),('s13','sample')
SELECT * FROM #t1 AS FirstTable
SELECT * FROM #t2 AS AnotherTable
Wanted result:
<Root>
<FirstTable ID="1" SomeField="rec1" />
<FirstTable ID="2" SomeField="rec2" />
<FirstTable ID="3" SomeField="rec3" />
<FirstTable ID="4" SomeField="rec4" />
<AnotherTable ID="1" SomeField="s106" AnotherField="here" />
<AnotherTable ID="2" SomeField="s12" AnotherField="just" />
<AnotherTable ID="3" SomeField="s13" AnotherField="sample" />
</Root>
dbfiddle here
New remark (edited)
answered by John Cappelletti, but need put all this inside third table.
Here is new code:
DECLARE #t1 table (ID int identity(1,1), tID int, SomeField varchar(50))
DECLARE #t2 table (ID int identity(1,1), tID int, SomeField varchar(50), AnotherField varchar(50))
DECLARE #t3 table (ID int identity(1,1), field1 varchar(50), field2 varchar(50))
INSERT INTO #t1 (tID,SomeField) VALUES (1,'rec1'),(1,'rec2'),(1,'rec3'),(1,'rec4')
INSERT INTO #t2 (tID,SomeField,AnotherField) VALUES (1,'s106','here'),(1,'s12','just'),(1,'s13','sample')
INSERT INTO #t3 (field1,field2) VALUES ('field1 Value','field2 Value')
Wanted result (finally):
<ThirdTable ID="1" field1="field1 Value" field2="field2 Value">
<FirstTable ID="1" tID="1" SomeField="rec1" />
<FirstTable ID="2" tID="1" SomeField="rec2" />
<FirstTable ID="3" tID="1" SomeField="rec3" />
<FirstTable ID="4" tID="1" SomeField="rec4" />
<AnotherTable ID="1" tID="1" SomeField="s106" AnotherField="here" />
<AnotherTable ID="2" tID="1" SomeField="s12" AnotherField="just" />
<AnotherTable ID="3" tID="1" SomeField="s13" AnotherField="sample" />
</ThirdTable>
Select [root] = cast((Select * From #t1 for xml raw('FirstTable'))
+(Select * From #t2 for xml raw('AnotherTable'))
as xml)
For XML Path(''),Type
Returns
<root>
<FirstTable ID="1" SomeField="rec1" />
<FirstTable ID="2" SomeField="rec2" />
<FirstTable ID="3" SomeField="rec3" />
<FirstTable ID="4" SomeField="rec4" />
<AnotherTable ID="1" SomeField="s106" AnotherField="here" />
<AnotherTable ID="2" SomeField="s12" AnotherField="just" />
<AnotherTable ID="3" SomeField="s13" AnotherField="sample" />
</root>
Added for the Extended Question
Select *
,(select cast(
isnull((Select * From #t1 for xml raw('FirstTable')),'')
+isnull((Select * From #t2 for xml raw('AnotherTable')),'')
as xml)
)
From #t3 for xml raw('ThirdTable')
Returns
<ThirdTable ID="1" field1="field1 Value" field2="field2 Value">
<FirstTable ID="1" tID="1" SomeField="rec1" />
<FirstTable ID="2" tID="1" SomeField="rec2" />
<FirstTable ID="3" tID="1" SomeField="rec3" />
<FirstTable ID="4" tID="1" SomeField="rec4" />
<AnotherTable ID="1" tID="1" SomeField="s106" AnotherField="here" />
<AnotherTable ID="2" tID="1" SomeField="s12" AnotherField="just" />
<AnotherTable ID="3" tID="1" SomeField="s13" AnotherField="sample" />
</ThirdTable>

Concatenate time into sql string

I am trying to execute a stored procedure with parameters of int, varchar,varchar,varchar.
Inside of the last one, the element Step has an attribute start, where i want to hold the value of the current datetime. How can I concatenante this into a string.
The Error:
Incorrect Syntax Near '+'
EXEC spData_Update #num,'<Info></Info>','<Metrics></Metrics>',
'<step start="' + SELECT CONVERT(varchar(50),getdate()) + '" end="" user="InsertFromWeb">
<type id="1" value="2" />
<type id="2" value="0" />
<type id="3" value="0" />
<type id="4" value="0" />
</step>'
You can declare a variable to hold the last string.
declare #date varchar(1000)
set #date = (select
'<step start="' + CONVERT(varchar(50),getdate()) + '" end="" user="InsertFromWeb">
<type id="1" value="2" />
<type id="2" value="0" />
<type id="3" value="0" />
<type id="4" value="0" />
</step>')
EXEC spData_Update #num,'<Info></Info>','<Metrics></Metrics>', #date

XML Data Type In SQL Server 2008 Query

I have a table in SQL Server in which one of the columns is an XML Datatype. There are other columns in the table that are not XML. Here is an example of the XML that is stored in the column:
<AdultAsthma>
<Group>
<Question text="Act Score:" ForeColor="Green" />
<Controls>
<Control type="Label" id="txtScore" text="Enter ACT Score:" ForeColor="Black" />
<Control type="TextBox" id="txtActScore" Answer="" />
</Controls>
</Group>
</AdultAsthma>
What I want is a query that matches some values on the other columns in the table and for those columns that match, I want to get the text attribute from the Question Node and the Answer attribute from the Control node. Can someone help me with this?
EDIT
What needs to be changed if I have more than one Group node? In this scenerio, I would want the text of each question and the answer to go along with each question. See below:
<AdultAsthma>
<Group>
<Question text="Act Score:" ForeColor="Green" />
<Controls>
<Control type="Label" id="txtScore" text="Enter ACT Score:" ForeColor="Black" />
<Control type="TextBox" id="txtActScore" Answer="" />
</Controls>
</Group>
<Group>
<Question text="Do You Have Asthma?:" ForeColor="Black" />
<Controls>
<Control type="RadioButton" id="rbHaveAsthmaYes" text="Yes" GroupName="Diagnosed" ForeColor="Black" Answer="False" />
<Control type="RadioButton" id="rbHaveAsthmaNo" text="No" GroupName="Diagnosed" ForeColor="Black" Answer="False" />
</Controls>
</Group>
</AdultAsthma>
declare #T table
(
XMLCol xml
)
insert into #T values
('<AdultAsthma>
<Group>
<Question text="Act Score:" ForeColor="Green" />
<Controls>
<Control type="Label" id="txtScore" text="Enter ACT Score:" ForeColor="Black"/>
<Control type="TextBox" id="txtActScore" Answer="Answer" />
</Controls>
</Group>
</AdultAsthma>
')
select XMLCol.value(N'(/AdultAsthma/Group/Question/#text)[1]', 'nvarchar(max)'),
XMLCol.value(N'(/AdultAsthma/Group/Controls/Control/#Answer)[1]', 'nvarchar(max)')
from #T
Update:
When you need to shred your XML to multiple rows you can use .nodes() in a cross apply.
declare #T table
(
XMLCol xml
)
insert into #T values
('<AdultAsthma>
<Group>
<Question text="Act Score:" ForeColor="Green" />
<Controls>
<Control type="Label" id="txtScore" text="Enter ACT Score:" ForeColor="Black" />
<Control type="TextBox" id="txtActScore" Answer="" />
</Controls>
</Group>
<Group>
<Question text="Do You Have Asthma?:" ForeColor="Black" />
<Controls>
<Control type="RadioButton" id="rbHaveAsthmaYes" text="Yes" GroupName="Diagnosed" ForeColor="Black" Answer="False" />
<Control type="RadioButton" id="rbHaveAsthmaNo" text="No" GroupName="Diagnosed" ForeColor="Black" Answer="False" />
</Controls>
</Group>
</AdultAsthma>
')
select X.N.value(N'(Question/#text)[1]', 'nvarchar(max)'),
X.N.value(N'(Controls/Control/#Answer)[1]', 'nvarchar(max)')
from #T as T
cross apply T.XMLCol.nodes(N'/AdultAsthma/Group') as X(N)

T-SQL Dynamic xquery

I am trying to figure out how I can load my table variable with data from XML using dynamic xquery? I am getting a result set of nodes from the query and defining the value type of those nodes. It seems that it is the value definition of the nodes that it is blowing up on.
Here is an example of the script that works, but is not dynamic.
Script:
DECLARE #XML XML = '<root>
<data>
<list id="organization" label="Organization">
<options>
<item value="1" label="Organization1" selected="false" />
<item value="2" label="Organization2" selected="false" />
<item value="3" label="Organization3" selected="false" />
<item value="4" label="Organization4" selected="true" />
<item value="5" label="Organization5" selected="true" />
</options>
</list>
</data>
</root>';
DECLARE #Orgs TABLE (ID INT);
Insert Into #Orgs(ID) Select OrgNameIdNodes.ID.value('#value','int') from #xml.nodes('//*[#id="organization"]//item[#selected="true"]') as OrgNameIdNodes(ID);
Select *
from #orgs
What I would like to be able to do is pass in parameters for both value and the #xml.nodes sections so I would have something like:
Insert Into #Orgs(ID) Select OrgNameIdNodes.ID.value(#Value) from #xml.nodes(#Nodes) as OrgNameIdNodes(ID);
Is this possible?
How about using sp_executesql with dynamic sql. Something like:
DECLARE #XML XML = '<root>
<data>
<list id="organization" label="Organization">
<options>
<item value="1" label="Organization1" selected="false" />
<item value="2" label="Organization2" selected="false" />
<item value="3" label="Organization3" selected="false" />
<item value="4" label="Organization4" selected="true" />
<item value="5" label="Organization5" selected="true" />
</options>
</list>
</data>
</root>';
declare #orgs table(ID int);
declare #nodes nvarchar(4000),
#value nvarchar(4000),
#query nvarchar(4000)
select #value = '''#value'',''int'''
select #nodes = '//*[#id="organization"]//item[#selected="true"]'
select #query = 'Select OrgNameIdNodes.ID.value( ' + #value + ') ' +
'from #xml.nodes(''' + #nodes + ''') as OrgNameIdNodes(ID)'
insert into #Orgs(ID) EXEC sp_executesql #query, N'#xml xml', #xml = #xml
Select *
from #orgs