SQL Server XML add attribute if non-existent - sql

I am trying to add an attribute if it does not exist. It should be simple, but I am new to XML XPath/XQuery/etc., so excuse my ignorance.
I want to be able to pass XML data and modify it...
ALTER FUNCTION [dbo].[ConvertXmlData](#xmlData XML)
RETURNS XML
AS
BEGIN
RETURN #xmlData.<something here>
END
If I pass data like:
<something>
this is sample data <xxx id="1"/> and <xxx id="2" runat="server" />. More <yyy id="3" />
</something>
I would like
<something>
this is sample data <xxx id="1" runat="server" /> and <xxx id="2" runat="server" />. More <yyy id="3" />
</something>
And not :
<something>
this is sample data <xxx id="1" runat="server" /> and <xxx id="2" runat="server" runat="server"/>. More <yyy id="3" />
</something>

You can do
SET #xmlData.modify('insert attribute runat { "server" } into descendant::xxx[not(#runat)][1]');
This will however only change the first xxx descendant not having a runat attribute, at least with SQL Server 2005 you can only modify one node at a time.
Maybe combining the above with a WHILE helps e.g.
WHILE #xmlData.exist('descendant::xxx[not(#runat)]') = 1
BEGIN
SET #xmlData.modify('insert attribute runat { "server" } into descendant::xxx[not(#runat)][1]');
END
I don't have access to SQL Server 2008 R2 but I think the modify is still limited to one node at a time so you could try
ALTER FUNCTION [dbo].[ConvertXmlData](#xmlData XML)
RETURNS XML
AS
BEGIN
WHILE #xmlData.exist('descendant::xxx[not(#runat)]') = 1
BEGIN
SET #xmlData.modify('insert attribute runat { "server" } into descendant::xxx[not(#runat)][1]');
END
RETURN #xmlData
END

Related

loop / Extract nodes from clob xml column in oracle pl sql

I have this xml content stored in a clob column of a table, I have to loop through the "molecule" nodes under the "reactantList" node,and store each "molecule" node into another table containing a list of molecules,
Any help please?
I tried with xmltype, xmlsequence, xmltable etc but did not work, I also have to specify the namespace "xmlns=.." somewhere as an argument to xmltype I think, to be able to make it work...
<cml xmlns="http://www.chemaxon.com" version="ChemAxon file format v20.20.0, generated by vunknown" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.chemaxon.com http://www.chemaxon.com/marvin/schema/mrvSchema_20_20_0.xsd">
<MDocument>
<MChemicalStruct>
<reaction>
<arrow type="DEFAULT" x1="-8.022119140625" y1="0.8333333333333334" x2="-3.5637858072916657" y2="0.8333333333333334" />
<reactantList>
<molecule molID="m1">
<atomArray>
<atom id="a1" elementType="C" x2="-13.938333333333334" y2="0.7083333333333333" />
<atom id="a2" elementType="O" x2="-15.478333333333333" y2="0.7083333333333333" lonePair="2" />
</atomArray>
<bondArray>
<bond id="b1" atomRefs2="a1 a2" order="1" />
</bondArray>
</molecule>
<molecule molID="m2">
<atomArray>
<atom id="a1" elementType="O" x2="-9.897119140624998" y2="0.8333333333333333" mrvValence="0" lonePair="3" />
</atomArray>
<bondArray />
</molecule>
</reactantList>
<agentList />
<productList />
</reaction>
</MChemicalStruct>
<MReactionSign toption="NOROT" fontScale="14.0" halign="CENTER" valign="CENTER" autoSize="true" id="o1">
<Field name="text">
<![CDATA[{D font=SansSerif,size=18,bold}+]]>
</Field>
<MPoint x="-11.730452473958332" y="0.6666666666666666" />
<MPoint x="-11.217119140624998" y="0.6666666666666666" />
<MPoint x="-11.217119140624998" y="1.18" />
<MPoint x="-11.730452473958332" y="1.18" />
</MReactionSign>
</MDocument>
</cml>
You can use:
INSERT INTO molecules (molecule)
SELECT x.molecule
FROM table_name t
CROSS APPLY XMLTABLE(
XMLNAMESPACES(
'http://www.w3.org/2001/XMLSchema-instance' AS "xsi",
DEFAULT 'http://www.chemaxon.com'
),
'/cml/MDocument/MChemicalStruct/reaction/reactantList/molecule'
PASSING XMLTYPE(t.xml)
COLUMNS
molecule XMLTYPE PATH '.'
) x
db<>fiddle here

The target of 'replace' must be at most one node

I'm trying to modify an XML value but keep getting the message
The target of the replace must be at most one node, found attribute(prefType, xdt:untypedAtomic)
First here is my XML
<preferences>
<categories>
<category id="1" prefType="2">
<subcat id="1" prefType="2" />
<subcat id="2" prefType="2" />
<subcat id="3" prefType="2" />
<subcat id="77" prefType="2" />
</category>
<category id="2" prefType="2">
<subcat id="9" prefType="2" />
<subcat id="10" prefType="2" />
<subcat id="11" prefType="2" />
<subcat id="12" prefType="2" />
<subcat id="13" prefType="2" />
<subcat id="14" prefType="2" />
<subcat id="17" prefType="2" />
<subcat id="78" prefType="2" />
<subcat id="101" prefType="2" />
</category>
<category id="3" prefType="2">
<subcat id="18" prefType="2" />
<subcat id="19" prefType="2" />
<subcat id="20" prefType="2" />
</category>
</categories>
</preferences>
And my code
declare #XMLinput as XML;
declare #custXML as XML;
declare #subcatid as nvarchar(3);
declare #interest as nvarchar(8);
declare #newValue as varchar(1);
declare #cnt as int;
set #XMLinput = '<preferences><categoryId>73</categoryId><interestLevel>POSITIVE</interestLevel></preferences>';
-- get the subcatid and interest level
SET #subcatid = #XMLinput.value('(//preferences/categoryId)[1]','nvarchar(3)');
SET #interest = #XMLinput.value('(//preferences/interestLevel)[1]','nvarchar(20)');
SET #newValue =
CASE #interest
WHEN 'POSITIVE' THEN '1'
WHEN 'NEGATIVE' THEN '3'
ELSE '2'
END;
set #custXML = (select Preferences from Customer_Preferences where custID=11584);
select #custXML.exist('//preferences/categories/category/subcat[#id=sql:variable("#subcatid")]');
if (##ROWCOUNT > 0)
BEGIN TRY
BEGIN TRAN;
set #cnt = CAST(CAST(#custXML.query('count(//preferences/categories/category/subcat[#id=(sql:variable("#subcatid"))])') AS VARCHAR) AS INT);
-- replace the value
UPDATE Customer_Preferences
SET preferences.modify('
replace value of
(//*/subcat[#id=sql:variable("#subcatid")]/#prefType[1])
with sql:variable("#newValue")
')
where CustID = 11584;
COMMIT TRAN;
END TRY
BEGIN CATCH
select XACT_STATE() as 'XACT_STATE', ##TRANCOUNT as '##TRANCOUNT';
if ##TRANCOUNT > 0 ROLLBACK TRANSACTION;
END CATCH
select preferences from Customer_Preferences where custid=11584
SELECT XACT_STATE() as 'XACT_STATE', ##TRANCOUNT AS '##TRANCOUNT'
I've tried removing the sql variables and replacing them with fixed values but still get the same issue. I've also tried removing all the XML subcats except for one and the same error occurs.
After 3 hours of working through this and getting no where, I'd really appreciate your help.
Just as further reference (although you solved your problem already on your own) for others with similar problems: Here is what actually went wrong: The error message already indicates that the replace target can be at most one value (meaning you can replace only one value at a time).
However, (//*/subcat[#id=sql:variable("#subcatid")]/#prefType[1]) yields a sequence of results What is means literally is to take each subcat element and select the first attribute with the name prefType. This actually does not make much sense as an XML element can not have multiple attributes with the same name, so the query would be the same without the [1] predicate.
What you probably wanted to write is: Give me each prefType attribute of each subcat element and return only the first one of the whole result set. That is exactly what your working query is doing: (//*/subcat[#id=sql:variable("#subcatid")]/#prefType)[1]

update xml attribute in xml returns error SQL

Im trying to update an attribute of an xml in SQL.
My XML is stored on a variable #tmpRespXML:
<Responses>
<x id="3" name="Good" val="0" seq="0" createsr="0" />
<x id="4" name="Fair" val="0" seq="0" createsr="0" />
<x id="5" name="Needs Repair" val="1" seq="0" createsr="0" />
<x id="6" name="Not Inspected" val="1" seq="0" createsr="0" />
<x id="7" name="N/A" val="1" seq="0" createsr="0" />
</Responses>
So what I did is to put the xml in a temp table.
DECLARE #tmpRespTBL TABLE(Responses XML)
INSERT #tmpRespTBL VALUES(#tmpRespXML)
and then update the table. I'm trying to set the attribute #createsr to 1 where my attribute #id is equal to #items
UPDATE #tmpRespTBL
SET Responses.modify('replace value of(/Responses/x[#id=("'+#items+'")]/#createsr)[1] with "1"')
This returns the ff error:
Msg 8172, Level 16, State 1, Line 30 The argument 1 of the xml data
type method "modify" must be a string literal.
What am I missing here?
Try with
SET Responses.modify('replace value of(/Responses/x[#id=("''+#items+''")]/#createsr)[1] with "1"')
That should fix your issue. What I did here is escape the '

Updating XML Tags Attributes Values in .NET

I have an xml string like that
<root>
Am trying <br id="9"/>to reorder the <br id="5"/>break
lines <br id="10"/> attributes value
</root>
Any Way to change attribute value of XML BR tag ID attribute to be in sequence
like this
<root>
Am trying <br id="1"/>to reorder the <br id="2"/>break
lines <br id="3"/> attributes value
</root>
Here is one example using LINQ TO XML
Dim doc as XElement = <root>
Am trying <br id="9"/>to reorder the <br id="5"/>break
lines <br id="10"/> attributes value
</root>
Dim index as Integer = 0
For Each br In doc.<br>
index += 1
br.#id = index
Next
This results in the following output
<root>
Am trying <br id="1" />to reorder the <br id="2" />break
lines <br id="3" /> attributes value
</root>
Also, here's an example using a LAMBDA expression.
doc.<br>.ToList().ForEach(Sub(br)
index += 1
br.#id = index
End Sub)

DbUnit Assertion floating-point numbers

I'm testing my DAO layer using DbUnit. I'm prefilling database from XML dataset, doing some actions and then asserting against known result.
Assertion.assertEquals(expectedDataSet, actualDataSet);
Dataset contains column with floating point number. Then these columns are compared, I get:
junit.framework.ComparisonFailure: value (table=OrderLine_T, row=2, col=price) expected:<2.99[]> but was:<2.99[0000009536743]>.
The values are equal, but because floating-point numbers cannot be exactly represented in binary form, the assertion fails.
In JUnit we have assertEquals(double expected, double actual, double delta). How do we set some delta in DbUnit for floating-point number comparison ?
Initial dataset:
<dataset>
<Customer_T id="1" name="Anthony"/>
<Customer_T id="2" name="John"/>
<Order_T id="1" date="2012-06-07 14:30" customer_id="1" />
<Order_T id="2" date="2012-06-07 15:31" customer_id="2" />
<OrderLine_T id="1" order_id="1" product_id="1" price="2.99" quantity="5" />
<OrderLine_T id="2" order_id="2" product_id="2" price="3.49" quantity="10" />
</dataset>
Expected result:
<dataset>
<Customer_T id="1" name="Anthony"/>
<Customer_T id="2" name="John"/>
<Order_T id="1" date="2012-06-07 14:30" customer_id="1" />
<Order_T id="2" date="2012-06-07 15:31" customer_id="2" />
<OrderLine_T id="1" order_id="1" product_id="1" price="2.99" quantity="5" />
<OrderLine_T id="2" order_id="2" product_id="2" price="3.49" quantity="10" />
<!-- Below added -->
<Order_T id="3" date="1987-06-07 9:15:10" customer_id="2" />
<OrderLine_T id="3" order_id="3" product_id="1" price="2.99" quantity="2" />
<OrderLine_T id="4" order_id="3" product_id="5" price="3.55" quantity="8" />
</dataset>
Code:
/* Should save order correctly (including order lines) */
#Test
public void save() throws Exception {
/* Create new order */
Set<OrderLine> lines = new HashSet<OrderLine>();
lines.add(new OrderLine(1, (float)2.99, 2));
lines.add(new OrderLine(5, (float)3.55, 8));
Calendar cal = Calendar.getInstance();
cal.set(1987, 6, 7, 9, 15, 10);
Date date = cal.getTime();
Customer customer = customerDAO.findById(2); // John
Order order = new Order(date, lines, customer);
orderDAO.save(order);
entityManager.flush();
/* Assert order is saved */
IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("data-set-afterAddOrder.xml"));
IDataSet actualDataSet = getDatabaseConnection().createDataSet();
Assertion.assertEquals(expectedDataSet, actualDataSet);
}
Edit:
Probably need to mention what I'm using in-memory HSQLDB. Just tried MySQL and it passes successfully.
Tried setting ToleratedDelta without success:
IDatabaseConnection connection = new DatabaseConnection(((SessionImpl) (entityManager.getDelegate())).connection());
HsqldbDataTypeFactory dataTypeFactory = new HsqldbDataTypeFactory();
dataTypeFactory.addToleratedDelta(new ToleratedDelta("OrderLine_T", "price", 0.01));
connection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, dataTypeFactory);
Edit2:
I was using hibernate.hbm2ddl.auto = create, to export schema to database. After got idea from fredt, I changed the type of field price on my entity to BigDecimal and added additional column precision and scale params:
#Column(precision=10, scale=4)
private BigDecimal price;
This gets translated to PRICE NUMERIC(10,4). Problem solved, thanks to fredt.
If the column is defined as DECIMAL or NUMERIC with the required precision and scale, the value will have the exace number of digits after the decimal point and the issue can be avoided.