I have a table named VehicleHistoryBlob that has the following structure:
VehicleHistoryBlobId int PRIMARY KEY
VehicleHistoryBlob XML
I need to write SQL that finds all entries in VehicleHistoryBlob XML that have Bus as a parent node and Destination as a child node (Bus can have many Destinations, and the parent node in the XML is not always a Bus).
<Bus>
...
<Destination>
<Name>The big building</Name>
<DestinationCode> A21301423 </DestinationCode>
<DestinationAddress> 440 Mountain View Parade </DestinationAddress>
<DestinationCountry> USA </DestinationCountry>
</Destination>
</Bus>'
I need to query through the XML and find all entries that have Bus as a parent node and Destination as a child node, and pass the VehicleHistoryBlobId associated with the XML into my temporary table #tmpTable
DECLARE #tmpTable TABLE(theints INT)
I have been trying to manipulate the .nodes function but I am struggling to yield accurate results due to my lack of experience with XML as a data type.
Thanks in advance!
To filter row by certain condition on the XML column, you can use exist() method instead of nodes(). For example, the following query insert to #temptable VehicleHistoryBlobId where corresponding XML has Bus as root element and Destination child element :
INSERT INTO #tmpTable
SELECT v.VehicleHistoryBlobId
FROM VehicleHistoryBlob v
WHERE v.VehicleHistoryBlob.exist('/Bus/Destination') = 1
sqlfiddle demo
Related
I have a table that contains a list of xml tags/values that I need to use to join to another table to retrieve their actual value and display the result as a csv list.
Example varchar data:
<choice id="100"/><choice id="101"/><choice id="102"/>
However, these values actually translate to other values: red, white, blue respectively. I need to convert that list to the following list:
red,white,blue
As a recap, the "source" table column is varchar, and contains a list of xml attribute values, and those values translate to other values by joining to another table. So the other table has a primary key of id (int) with rows for 100,101,102. Each of those rows has values red,white,blue respectively. I hope this makes enough sense.
Here is the ddl to set up the scenario:
create table datatable(
id int,
data nvarchar(449)
primary key (id)
);
insert into datatable(id, data)
values(1,'<choice id="100"/><choice id="101"/><choice id="102"/>')
,(2,'<choice id="100"/>')
,(3,'<choice id="101"/>')
,(4,'<choice id="102"/>');
create table choicetable(
id int,
choicevalue nvarchar(449)
primary key (id)
);
insert into choicetable(id, choicevalue)
values(100,'red')
,(101,'white')
,(102,'blue');
This would be the first time I've tried parsing XML in this manner so I'm a little stumped where to start. Also, I do not have control over the database I am retrieving the data from (3rd party software).
Without proper sample data it's hard to give an exact query. But you would do something like this
Use CROSS APPLY to convert the varchar to xml
Use .nodes to shred the XML into separate rows.
Join using .value to get the id attribute
Group up, and concatenate using STRING_AGG. You may not need GROUP BY depending on your situation.
SELECT
xt.Id,
STRING_AGG(ot.Value, ',')
FROM XmlTable xt
CROSS APPLY (SELECT CAST(xt.XmlColumn AS xml) ) v(XmlData)
CROSS APPLY v.XmlData.nodes('/choice') x1(choice)
JOIN OtherTable ot ON ot.Id = x1.choice.value('#id','int')
GROUP BY
xt.Id;
I would advise you to store XML data in an xml typed column if at all possible.
I have got two tables, Table A containing my subject that needs to be updated and Table B that contains XML used to populate the columns in Table A.
A new column has been added to Table A which relates to a specific piece of in the XML.
Any ideas how I can query the XML and insert the into the new column?
UPDATE:
I have tried to query the XML using
select
xml
from
TableB
where
xml.value('(/path/value)[1]','string') like 'VALUE'
However when I do this, "xml.value" is highlghted as an error "Cannot find either column "xml" or the user-defined function or aggregate "xml.value", or the name in ambiguous"
Boy, I've been researching this subject, but I am just not getting it. Sorry if I'm asking a question that has all ready been asked a million times, but its hard to understand when you're a noob like me, and the values in my tables aren't like the others I've seen. So here it goes...
In my SQL Server database, I have a table that has all my listings in it, called ItemsEbay.
The main identifier for each individual item has a column ID value called ItemID, so I would like to refer to that as needed.
Within that table is a column named ItemSpecifics that contains XML data.
Within the ItemSpecifics XML Data, is a node with a <Name> of UPC text and random value for that node:
What I would like, is a query that would allow me to search all the items in the ItemsEbay table that have a specific UPC value of my choosing, such as 1000100 or 10U100 for instance.
When I find the match values I'm querying, I would like to be able to replace them all at once with a new value Does Not Apply.
First of all: do not poste pictures!
What I've done here is your job: How to create a MCVE!
A dummy table with two rows, one contains the searched value, one doesn't
DECLARE #dummyTable TABLE(ID INT IDENTITY, ItemSpecifications XML);
INSERT INTO #dummyTable VALUES
(N'<SelectedValues>
<SelectedValue>
<Name>TestName</Name>
<Value>Xml1</Value>
</SelectedValue>
<SelectedValue>
<Name>UPC</Name>
<Value>123</Value><!--The UPC named value = 123 -->
</SelectedValue>
</SelectedValues>')
,(N'<SelectedValues>
<SelectedValue>
<Name>TestName</Name>
<Value>Xml2</Value>
</SelectedValue>
<SelectedValue>
<Name>UPC</Name>
<Value>999</Value><!--The UPC named value = 999 -->
</SelectedValue>
</SelectedValues>');
--I search for "123" and want to replace it with "SomeOther"
DECLARE #SearchFor VARCHAR(100)='123';
DECLARE #ReplaceWith VARCHAR(100)='SomeOther';
--The update statement uses .modify() for the XML change and .exist() to check for the search value below a <SelectedValue>, which <Name> element has a text() of "123":
UPDATE #dummyTable SET ItemSpecifications.modify(N'replace value of (/SelectedValues
/SelectedValue[(Name/text())[1]="UPC"]
/Value/text())[1]
with sql:variable("#ReplaceWith")')
WHERE ItemSpecifications.exist(N'/SelectedValues
/SelectedValue[(Name/text())[1]="UPC"]
/Value[text()=sql:variable("#SearchFor")]')=1;
--Check the result
SELECT * FROM #dummyTable;
Let's assume we have a table in database with the following structure:
id (int32), parentId (int32), nodeName, nodeBodyText, ...
Of course some kind of "tree" is stored there.
User exports some branch of the tree to csv/xml/etc file.
When this file is being imported to another db (with a different nodes of course) there often may happen id's conflicts.
1) Records with the same id's may exist already
2) Db has the id column with the auto-incrementing enabled
(so you can't explicitly specify id for newly created record)
How this problem is usually solved?
Especially in case nodeBodyText also may contain text with relations to other nodes
(using hardcoded ids from a previous db)
P.S.
Usage of guid's is not acceptable for us.
Assuming that the imported subtree has parent references confined to that subtree only and you are inserting the nodes only, not updating. In SQL server you can do this:
You need a mapping table to store new and old ids.
declare #idmap table
(
old_id int, new_id int
)
Then insert imported nodes using MERGE command
MERGE [target] as t
USING [source] as s ON 1=0 -- don't match anythig, all nodes are new
WHEN NOT MATCHED
THEN INSERT(parentid,nodename) VALUES(s.parentid,s.nodename)
OUTPUT s.id, inserted.id INTO #idmap; -- store new and old id in mapping table
Finally re-map target table's parent ids
update t
set parentid = x.new_id
from [target] t
inner join #idmap x on x.old_id = t.parentid
where t.parentid is not null
and -- only the newly inserted nodes
exists(select * from #idmap where new_id = t.id);
I have the following complex XML
<Collection>
<VOUCHER>
<DATE TYPE="Date">20110401</DATE>
<NARRATION TYPE="String">MUNNA CONVENT ROAD</NARRATION>
<VOUCHERTYPENAME>RETAIL</VOUCHERTYPENAME>
<VOUCHERNUMBER>R-2-I2-9-6-27751</VOUCHERNUMBER>
<ALLLEDGERENTRIES.LIST>
<LEDGERNAME>U.S.T. CANTEEN</LEDGERNAME>
<AMOUNT>-2678.9985</AMOUNT>
</ALLLEDGERENTRIES.LIST>
<ALLLEDGERENTRIES.LIST>
<LEDGERNAME>U.S.T. CANTEEN</LEDGERNAME>
<AMOUNT>-2678.9985</AMOUNT>
</ALLLEDGERENTRIES.LIST>
</VOUCHER>
<VOUCHER>
<DATE TYPE="Date">20110401</DATE>
<NARRATION TYPE="String">MUNNA CONVENT ROAD</NARRATION>
<VOUCHERTYPENAME>RETAIL</VOUCHERTYPENAME>
<VOUCHERNUMBER>R-2-I2-9-6-27751</VOUCHERNUMBER>
<ALLLEDGERENTRIES.LIST>
<LEDGERNAME>U.S.T. CANTEEN</LEDGERNAME>
<AMOUNT>-2678.9985</AMOUNT>
</ALLLEDGERENTRIES.LIST>
<ALLLEDGERENTRIES.LIST>
<LEDGERNAME>U.S.T. CANTEEN</LEDGERNAME>
<AMOUNT>-2678.9985</AMOUNT>
</ALLLEDGERENTRIES.LIST>
</VOUCHER>
</Collection>
I'm saving voucher details in 1 table, ALLLEDGERENTRIES.LIST details in another table.
Both tables have relation on VoucherID. For a particular VoucherID the related x3 values should be stored. In my stored procedure I'm using openxml().
Piece of my SP:
INSERT INTO SalesVoucher(AbsID,VoucherNumber,VoucherTypeName,Narration,VoucherDate)
SELECT #AID,VOUCHERNUMBER,VOUCHERTYPENAME,NARRATION,CAST(DATE AS DATETIME)
FROM OPENXML(#XMLHandle,'ENVELOPE/BODY/DATA/COLLECTION/VOUCHER',3)
WITH (
VOUCHERNUMBER nVarchar(200),VOUCHERTYPENAME varchar(100),NARRATION varchar(500),DATE DATETIME
)
SELECT #VID=##IDENTITY
INSERT INTO SalesLedger(VoucherID,LedgerName,Amount)
SELECT #VID,LEDGERNAME,AMOUNT
FROM OPENXML(#XMLHandle,'ENVELOPE/BODY/DATA/COLLECTION/VOUCHER/ALLLEDGERENTRIES.LIST',3)
WITH(
LEDGERNAME varchar(200),AMOUNT decimal(18,0)
)
All values are storing in DB but the column VoucherID in SalesLedger table is same for all the rows (it should not..) as I used ##IDENTITY it is returning last identity value only.
Please someone help me how to store related voucherID in SalesLedger table using openxml() in sql...
I would probably use the native XQuery capabilities of SQL Server to do this. First, grab the items you need for your SalesVoucher table and insert those.
When you come to insert the details, your "parent" info is already stored in the SalesVoucher table - so go grab the necessary info from there.
Your code would be something like this (assuming your XML data is in a SQL variable called #input of type XML):
-- Insert the "parent" info into SalesVoucher
INSERT INTO dbo.SalesVoucher(VoucherNumber, VoucherTypeName, Narration, VoucherDate)
SELECT
v.value('(VOUCHERNUMBER)[1]', 'NVARCHAR(200)'),
v.value('(VOUCHERTYPENAME)[1]', 'VARCHAR(100)'),
v.value('(NARRATION)[1]', 'VARCHAR(500)'),
v.value('(DATE)[1]', 'DATETIME')
FROM
#input.nodes('/Collection/VOUCHER') AS Coll(V)
This inserts the basic info in your SalesVoucher table.
When you want to parse the details, you need to make a reference back to the VoucherNumber of the parent - with that info, you can retrieve the AbsID from SalesVoucher and insert the appropriate value into SalesLedger:
INSERT INTO #SalesLedger (VoucherID, LedgerName, Amount)
SELECT
sv.AbsID,
AL.LS.value('(LEDGERNAME)[1]', 'VARCHAR(200)'),
AL.LS.value('(AMOUNT)[1]', 'DECIMAL(18,4)')
FROM
#input.nodes('/Collection/VOUCHER') AS Coll(V)
INNER JOIN
dbo.SalesVoucher sv
ON sv.VoucherNumber = v.value('(VOUCHERNUMBER)[1]', 'NVARCHAR(200)')
CROSS APPLY
Coll.V.nodes('.//ALLLEDGERENTRIES.LIST') AS AL(LS)
The CROSS APPLY gets the details for that one particular node, and thus "connects" the details to the "parent" info for the VoucherNumber in the XML above.
As as PS: a datatype of DECIMAL(18,0) is not suitable for values like -2678.9985. DECIMAL(18,0) will store a maximum of 18 digits, but 0 of which after the decimal point - so this value would be stored as -2679. I've changed this to a more useful datatype of DECIMAL(18,4) - 18 digits max, 4 of which after the decimal point.