Find if a list contains several values - SQL Server Xquery - sql-server

I am storing a XML data into a table called BikeTable. The XML data is coming from an object that is being serialized using .Net serializer.
BikeTable would look like this :
Id - UniqueIdentifier
XmlData - XML
The XML stored in the XmlData column looks like this :
Record 1 :
<Bike>
<Material>
<Cage>EIECH</Cage>
<Mpn>B258-C436-B001</
</Material>
<Roles>
<string>Race</string>
<string>Mountain</string>
<string>City</string>
</Roles>
</Bike>
Record 2 :
<Bike>
<Material>
<Cage>ABCDE</Cage>
<Mpn>B258-C436-B001</Mpn>
</Material>
<Roles>
<string>Race</string>
</Roles>
</Bike>
I want to be able to find the records in my table that will contain for example Race and Mountain.
Example if I want the Ids of the record that contains 'Road'and 'Mountain" the only way I found is like this :
select Id
from BikeTable
where XmlData.exist('/Bike/Roles/string[contains(., "Road")]') = 1
or XmlData.exist('/Bike/Roles/string[contains(., "Mountain")]') = 1
I don't like this option because it forces me to generate the query if I want to find records that would match one or several roles.
Roles can contains unlimited number of values and I need to be able to find the records that will one or more values.
Ex : records containing Race, records containing Race or Montain, records containing City, records containing City and Mountain etc.
Is there any way to know if a list contains several values?

Yes, you can. This is a bit of a guess though, as you say you want to do a SELECT *; something that is impossible to provide any data for without the DDL of the table. Thus, instead, I've returned the Cage and Mpn of the Bike:
CREATE TABLE BikeTable (xmlData xml);
--The Close tag for Mpn was missing in your sample data, I assume it wasn't mean to be
INSERT INTO BikeTable
VALUES('<Bike>
<Material>
<Cage>EIECH</Cage>
<Mpn>B258-C436-B001</Mpn>
</Material>
<Roles>
<string>Race</string>
<string>Mountain</string>
<string>City</string>
</Roles>
</Bike>')
GO
WITH Bikes AS (
SELECT B.Material.value('(Cage/text())[1]','varchar(15)') AS Cage, --Data Type guessed
B.Material.value('(Mpn/text())[1]','varchar(15)') AS Mpn, --Data Type guessed
BR.String.value('(./text())[1]','varchar(15)') AS String --Data Type guessed
FROM BikeTable BT
CROSS APPLY BT.xmlData.nodes('/Bike/Material') B(Material)
CROSS APPLY BT.xmlData.nodes('/Bike/Roles/string') BR(String))
SELECT Cage, Mpn
FROM Bikes
GROUP BY Cage, Mpn
HAVING COUNT(String) > 1;
GO
DROP TABLE BikeTable;

Related

What's the cleanest way in SQL to break a table into two tables?

I have a table that I want to break into 2 tables. I want to pull some data out of table A, put it into a new table B, and then point each record in A to the corresponding record in the new table.
It's easy enough to populate the new table with an INSERT INTO B blah blah SELECT blah blah FROM A. But the catch is, when I create the new records in B, I want to write the ID of the B record back into A.
I've thought of two ways to do this:
Create a cursor, loop through A a record at a time, create the record in B and post the new ID back to A.
Create a temporary table with the extracted data, an ID for the new record, and the ID of A. Then use this temporary table to populate B and also to post the ID back to A.
Both methods seem cumbersome with a lot of copying all the data back and forth. Is there a clean, simple way to do this or should I just knuckle down and do it the hard way?
Oh, I'm using Microsoft SQL Server, if your answer depends on non-standard features of SQL.
Someone asks for an example. Yes, I should have included something concrete to make it clear. The real example is a bunch of data, but let me give a simplified example of what I mean.
Let's say I have a Customer table with customer_id, name, and city. I want to break city out into a separate table.
So for example:
Customer
ID Name City
17 Al Detroit
22 Betty Baltimore
39 Charles Cleveland
I want to convert this to:
Customer
ID Name City_ID
17 Al 1
22 Betty 2
39 Charles 3
City
ID Name
1 Detroit
2 Baltimore
3 Cleveland
The exact ID values don't matter.
So easy enough to create the City table and the reference ...
create table city (id int identity primary key, name varchar(50))
alter table customer add city_id int references city
And then populate the city table ...
insert into city (name)
select city from customer
The trick is how to get those city IDs back into the Customer table.
(And yes, in this simplified example, the effort may appear pointless. In real life we have many tables with addresses and I want to pull all those fields out of all the other tables and put them into a single address table, so we can standardize the declarations and processing of addresses.)
(Note: I haven't tested the sample code above. Excuse me if there's a typo or something in there.)
You can use the output clause to capture your new ID values.
without any sample data or examples of what you are doing the following is just a guide.
Create a #table to hold the new ID values, then insert the newly inserted Id identity values along with a correlating value from the inserted virtual table. You can then update the original table with the new IDs by joining on this correlating value.
create table #NewIds (TableBId int, TableAId int)
insert into TableB (column list)
output inserted.Id, inserted.TableAId into #NewIds
select column list
from TableA
update a
set a.TableBId=Id
from #NewIds n join TableA a on a.Id=n.TableAId

Querying selection of values in XML in SQL Server, on table of mixed values

I have a table with people who have bought tickets for a charity evening event, and the table contains details of registration event, and the XML will show guests they are bringing with them, but also details of any dietary requirements, and the occasional person who might be disabled. This is supposed to be pushed to our CRM system but this is not currently working.
I'm trying to extract some values out of some XML which is in a column in our import table.
I've seen plenty of examples of querying ordinary chunks of XML, but not when the XML is inside a table with other normal INT and VARCHAR values.
We are using SQL Server 2014. I've spent hours googling but haven't the faintest idea on making a query that combined the two together. Or even if I'm supposed to push the XML stuff into a temp table which I could then do a join with.
Declare #xmlstring xml = '<field_import_admin_event_tickets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<und is_array="true">
<item>
<value>8463</value>
<revision_id>4763</revision_id>
</item>
</und>
</field_import_admin_event_tickets>'
select
MainDataCenter.Col.value('(value)[1]', 'varchar(max)') as Name,
MainDataCenter.Col.value('(revision_id)[1]', 'varchar(max)') as Value
from
#xmlstring.nodes('/field_import_admin_event_tickets/und/item') as MainDataCenter(Col)
^ this will work
but I need to query it along with this:-
SELECT *
FROM [importtickets].[bcc].[entityform]
WHERE type LIKE '%show%'
AND createdDATETIME > '2019-03-14'
AND LEN(CAST(field_import_admin_event_tickets AS VARCHAR(MAX)) ) >1
-- bodging a way of seeing if XML code exists or not, doesn't seem to work with IS NOT NULL
AND Jobstatus = 'completed'
The only way I can crudely get values out of the XML is CAST it to a VARCHAR and use lots of REPLACE commands to strip out the XML tags to get it down to the values. There may be 2 to 18 numeric values in each lump of XML
This is my first post on StackOverflow and I've spent days searching on this, so please be gentle with me. Thanks.
2019-07-10 Hey, so I didn't make this fully clear.
each column of XML (a few are nulls) contains 2 - 34 separate numbers in. I dd some crude manipulation of data by CASTing this into VARCHAR and running lots of replace commands to understand it better.
this is the largest example here of some XML, 34 integer values, 17 are 'value' and 17 are 'revision_id'
So I then pushed this all into a new table using lots of SUBSTRING. This is crude but effective, but assumes each value is five digits long (it is so far) my boss is not keen on this solution though.
crudely shredded XML using CAST to VARCHAR and tags manually stripped out
I just need each sets of values extracted in each row so I can then do a JOIN or subquery to them, with a row or something identifiable. The numbers will refer to a guest who is coming to some charity events which will have some attributes such as dietary requirements or disability.
I don't know, if this is the very best approach for your issue, but I hope that I got your question correctly, that you want to combine the working query against an isolated XML with the tabular query, where the XML is the content of a column:
First of all I create a mockup with two rows
DECLARE #mockupTable TABLE(ID INT IDENTITY,SomeOtherValue VARCHAR(100),YourXml XML);
INSERT INTO #mockupTable(SomeOtherValue,YourXml) VALUES
('This is some value in row 1'
,'<field_import_admin_event_tickets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<und is_array="true">
<item>
<value>8463</value>
<revision_id>4763</revision_id>
</item>
</und>
</field_import_admin_event_tickets>')
,('This is some value in row 2'
,'<field_import_admin_event_tickets xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<und is_array="true">
<item>
<value>999</value>
<revision_id>888</revision_id>
</item>
</und>
</field_import_admin_event_tickets>');
--The query
SELECT t.ID
,t.SomeOtherValue
,MainDataCenter.Col.value('(value)[1]', 'varchar(max)') as Name
,MainDataCenter.Col.value('(revision_id)[1]', 'varchar(max)') as Value
FROM #mockupTable t
CROSS APPLY t.YourXml.nodes('/field_import_admin_event_tickets/und/item') as MainDataCenter(Col);
The result
ID SomeOtherValue Name Value
1 This is some value in row 1 8463 4763
2 This is some value in row 2 999 888
The idea in short:
APPLY allows to call a table-valued function row-wise. In this case we hand in the content of a column (in your case the XML) into the built-in function .nodes().
Similar to a JOIN we get a joined set, which adds columns (and rows) to the final set. We can use the .value() method to retrieve the actual values from the XML.
If this is the best approach? I don't know...
Your sample above shows just one single <item>. .nodes() would be needed to return several <item> elements in a derived set. With just one <item> this could be done more easily using .value() directly...

Looping through SQL OpenXML to get Field Names

I have a stored procedure that will receive an xml document I will be processing. Depending on the different processes that will call this procedure, I will be inserting data into a temp table from this openxml prepared document that will be from one group or another.
Ex: It could be:
FROM OPENXML(#idoc, '/data/BeneInfoGroup/BeneInfo', 1)
OR it could be:
FROM OPENXML(#idoc, '/data/PersonalInfoGroup/PersonalInfo', 1)
Only one group will come through. Depending on this group - I want to create an SQL string (to be EXECUTED) that inserts into a temp table (already created) and contains field names from the openxml doc.
So my code (in the 2 examples above) could look like:
strSQL = N'INSERT INTO #tmpTbl (BeneID, BeneSSN, BeneDate)
SELECT BeneID, BeneSSN, BeneDate
FROM OPENXML(#idoc, ''/data/BeneInfoGroup/BeneInfo'', 1)
WITH (BeneID nvarchar(50) ''BeneID'', BeneSSN nvarchar(50) ''BeneSSN'', BeneDate nvarchar(50) ''BeneDate'')'
Or it could look like:
strSQL = N'INSERT INTO #tmpTbl (PersonID, PersonSSN, PersonDate)
SELECT PersonID, PersonSSN, PersonDate
FROM OPENXML(#idoc, ''/data/PersonInfoGroup/PersonInfo'', 1)
WITH (PersonID nvarchar(50) ''PersonID'', PersonSSN nvarchar(50) ''PersonSSN'', PersonDate nvarchar(50) ''PersonDate'')'
The 2 examples above are minimal. It won't be "Person" vs "Bene" headers. The fields could be named anything in either group. So I'd like to loop through the openxml document and have the fieldnames go into my string where appropriate. I can take care of the data type details, etc. I just need to pull the field names from the openxml doc I've prepared.
I do NOT need an alternative method using something other than OPENXML, please.
The basic question is - how do I pull the field name from the document in a way that's not hard coded depending on the xml doc I receive?
I don't want to create a table of the field names.
I just want to go through a loop and reference every fieldname that exists for that particular group in my openxml.
No need to worry about #tmpTbl - I have created this with all possible fields that might be inserted into it.
Thanks in advance! Daniel

SQL Server 2005 - searching for value in XML field

I'm trying to query a particular value in an XML field. I've seen lots of examples, but they don't seem to be what I'm looking for
Supposing my xml field is called XMLAttributes and table TableName, and the complete xml value is like the below:
<Attribute name="First2Digits" value="12" />
<Attribute name="PurchaseXXXUniqueID" value="U4RV123456762MBE79" />
(although the xml field will frequently have other attributes, not just PurchaseXXXUniqueID)
If I'm looking for a specific value in the PurchaseXXXUniqueID attribute name - say U4RV123456762MBE79 - how would I write the query? I believe it would be something like:
select *
from TableName
where XMLAttributes.value('(/path/to/tag)[1]', 'varchar(100)') = '5FTZP2QT8Z3E2MAV2D'
... but it's the path/to/tag that I need to figure out.
Or probably there's other ways of getting the values I want.
To summarize - I need to get all the records in a table where the value of a particular attribute in the xml field matches a value I'll pass to the query.
thanks for the help!
Sylvia
edit: I was trying to make this simpler, but in case it makes a difference - ultimately I'll have a temporary table of 50 or so potential values for the PurchaseXXXUniqueID field. For these, I want to get all the matching records from the table with the XML field.
This ought to work:
SELECT
(fields from base table),
Nodes.Attr.value('(#name)[1]', 'varchar(100)'),
Nodes.Attr.value('(#value)[1]', 'varchar(100)')
FROM
dbo.TableName
CROSS APPLY
XMLAttributes.nodes('/Attribute') AS Nodes(Attr)
WHERE
Nodes.Attr.value('(#name)[1]', 'varchar(100)') = 'PurchaseXXXUniqueID'
AND Nodes.Attr.value('(#value)[1]', 'varchar(100)') = 'U4RV123456762MBE79'
You basically need to join the base table's row against one "pseudo-row" for each of the <Attribute> nodes inside the XML column, and the pick out the individual attribute values from the <Attribute> node to select what you're looking for.
Something like that?
declare #PurchaseXXXUniqueID varchar(max)
set #PurchaseXXXUniqueID = 'U4RV123456762MBE79';
select * from TableName t
where XMLAttributes.exist('//Attribute/#value = sql:variable("#PurchaseXXXUniqueID")') = 1

Sql Server - Capture an XML query and save it to a table?

I would like to run a (extensive) query that produces one line of XML. This XML represents about 12 tables worth of relational data. When I "delete" a row from the top level table I would like to "capture" the state of the data at that moment (in XML), save it to an archive table, then delete all the child table data and finally mark the top level table/row as "isDeleted=1". We have other data hanging off of the parent table that CANNOT be deleted and cannot lose the relation to the top table.
I have most of the XML worked out - see my post here on that can of worms.
Now, how can I capture that into an archive table?
CREATE TABLE CampaignArchive(CampaignID int, XmlData XML)
INSERT INTO CampaignArchive(CampaignID, XmlData)
SELECT CampaignID, (really long list of columns) FROM (all my tables) FOR XML PATH ...
This just does not work. :)
Any suggestions?
TIA
You need a subquery to wrap all that XML creation into one single scalar that goes into column XmlData. Again, use TYPE to create a scalar of type XML not a string:
INSERT INTO CampaignArchive(CampaignID, XmlData)
SELECT CampaignID, (
select (really long list of columns)
FROM (all my tables) FOR XML PATH ..., type);

Resources