Querying XML Rows For New Non-XML Rows - sql-server

Note: See this spelled out example on SQL Fiddle, or look at the code below:, as the DECLARE #XML variable examples are confusing the syntax when trying to actually obtain XML data from an XML column and intended for non-XML rows:
CREATE TABLE ##xml (
ID TINYINT IDENTITY(1,1),
Value XML
)
INSERT INTO ##xml (Value)
VALUES ('<Animals key="zoo" fish="22" dogs="0" birds="4" />')
, ('<Animals key="house" fish="0" dogs="1" birds="2" />')
, ('<Animals key="business" fish="0" dogs="0" birds="12" />')
SELECT *
FROM ##xml
SELECT nodes.child.value('key[1]', 'VARCHAR(50)')
FROM ##xml.Value.nodes('Animal') AS nodes(child)
-- Errors here, though the syntax looks correct
DROP TABLE ##xml
I'm getting the error, "Invalid column name '##xml'. The XMLDT method 'nodes' can only be invoked on columns of type xml" even though I'm trying to query the nodes of XML to produce a result like the below:
Key | Fish | Dogs | Birds
Zoo 22 0 4
House 0 1 2
Business 0 0 12
Note, if I change the syntax of the FROM statement to point to the specific column (Value), I receive other errors about Value not being a recognized built-in function name.

Do it like this:
SELECT nodes.child.value('#key','varchar(100)')
FROM ##XML a
CROSS APPLY a.value.nodes('/Animals') AS nodes(child)

Related

Group by based on specific column contains value from list of values

I've one table myTable
ID
Content
1
Hello, this is the test content
2
Hi, test content.
I have one list having different values = ["Hello","Hi","Yes","content"]
Now I have to find occurrence of value in myTable-> content column & resultant table have value & count of that value in myTable-> content column (one row of myTable table can have more than one values & use case-insensitive search).
Output be like:
Value
Count
Hello
1
Hi
1
Yes
0
content
2
I want to make optimal SQL server query.
Assuming you are using SQL Server 2016 or above, you could try converting your list to a table like structure, and perform a left join and count on your table.
For instance :
CREATE TABLE MyTable (
ID INT CONSTRAINT PK_MyTable PRIMARY KEY,
Content NVARCHAR(MAX)
);
INSERT INTO MyTable (ID,CONTENT) VALUES
(1,'Hello, this is the test content'),
(2,'Hi, test content.');
DECLARE #MyList NVARCHAR(MAX)
SET #MyList='["Hello","Hi","Yes","content"]';
SELECT
List.Value,
COUNT(MyTable.Content) Count
FROM OPENJSON(#MyList) List --Convert the list to a json
LEFT JOIN MyTable ON '.' + UPPER(MyTable.Content) + '.' LIKE '%[^a-z]' + UPPER(List.Value) +'[^a-z]%'
GROUP BY List.Value;
You can try it on this fiddle.
Please do note that there is margin for improvement, such as full text index instead of this ugly regular expression clause.
See also :
Search for “whole word match” with SQL Server LIKE pattern

Avoid double XML INSERT to SQL

I need to import XML data into SQL Server 2012. The import works correctly, but I would want to avoid double import. I already tried with WHERE NOT EXISTS but it didn't work.
The import:
INSERT INTO dbo.tXMLImport(cText)
SELECT cast(CONVERT(XML,x.BulkColumn,2) AS varchar(max))
FROM OPENROWSET (BULK 'D:\XML\Data.xml', SINGLE_BLOB) AS x
EXL file content:
<?xml version="1.0" encoding="UTF-8"?>
<tOrder>
<cName>Name1</cName>
<cID>100</cID>
</tOrder>
Now, it should be checked if cID value 100 from XML file already exist in
dbo.tOrder row cOrderNumber
cOrderNumber
1 100
2 101
3 102
Following extention does not wokr:
WHERE NOT EXISTS(SELECT *
FROM dbo.tOrder
WHERE x.value('(/tOrder/cID)') = dbo.tOrder.CorderNumber)
If yes, no Import to be done. Maybe some one can support me with?
Thanks in advance.
I'm not sure if I really get this... If the same cOrderNumber exists already, wouldn't you try to update the existing row? Something like you'd do with MERGE?
But It might be something like this what you are looking for:
WHERE NOT EXISTS(SELECT 1 FROM dbo.tOrder
WHERE x.exist(N'/tOrder[cID/text()=sql:column("cOrderNumber")])')=1)
(Untested air code)
This looks if there is any record within tOrder where the XML column x has any occurance of a node <tOrder><CID> with a value like the current cOrderNumber's value.
T-SQL adds the sql:column() method to XQuery, which allows to use the value of a row within the query. There's sql:variable() too.
The xml's method .exist() checks the XML for any existance of a given condition and returns with 0 or 1.
UPDATE
After reading your question once again, I'm not sure if I got this correctly... Please check the following. If this doesn't help, please use my code to set up a stand-alone sample to reprodcue your issue:
A dummy table with some orders
DECLARE #YourTable TABLE(cOrderNumber INT, OrderName VARCHAR(100));
INSERT INTO #YourTable VALUES
(100,'Order 100')
,(200,'Order 200')
,(300,'Order 300')
--Try to insert an XML with the existing OrderNumber=100
DECLARE #xml100 XML=
'<tOrder>
<cName>Name1</cName>
<cID>100</cID>
</tOrder>';
INSERT INTO #YourTable(cOrderNumber,OrderName)
SELECT #xml100.value('(/tOrder/cID/text())[1]','int')
,#xml100.value('(/tOrder/cName/text())[1]','varchar(100)')
WHERE NOT EXISTS(SELECT 1 FROM #YourTable AS t2
WHERE t2.cOrderNumber=#xml100.value('(/tOrder/cID/text())[1]','int'));
--Same code as above, but the order number is now a not existing number
DECLARE #xml101 XML=
'<tOrder>
<cName>Name1</cName>
<cID>101</cID>
</tOrder>';
INSERT INTO #YourTable(cOrderNumber,OrderName)
SELECT #xml101.value('(/tOrder/cID/text())[1]','int')
,#xml101.value('(/tOrder/cName/text())[1]','varchar(100)')
WHERE NOT EXISTS(SELECT 1 FROM #YourTable AS t2
WHERE t2.cOrderNumber=#xml101.value('(/tOrder/cID/text())[1]','int'));
--check the result
SELECT *
FROM #YourTable;
nr name
-------------
100 Order 100
200 Order 200
300 Order 300
101 Name1

use variable in select - tsql

Hi I have procedure which have parameter(#identFormat)
Example
"GUID"
"LotID|FeatureID"
And now I have Select query which should split this and use as columns.
Moreover result should be back combined.
Example:
Table:
Id LotID FeatureID
2 1 4
3 4 5
4 2 1
and if my #identFormat = "LotID|FeatureID" then it should return
Table:
1|4
4|5
2|1
Actually I have ncharchar #columns 'LotId + "|" + FeatureId'
Is it possible to use this like this:
Select #columns from Table ?
or using dynamic sql
EDIT:
Unfortunately combination of columns can be different. My purpose is send column names to procedure and select this columns from specific table. This is procedure to save data , but if something went wrong I must save this unique combination of columns in second table.
Unfortunatelly, it is not possible. You need to select separately and format the output

Referential integrity issue with Untyped XML in TSQL

I am going to start off by displaying my table structures:
Numbers Table:
Id AccountId MobileNr FirstName LastName AttributeKeyValues Labels
--- ---------- ----------- ---------- ----------- ------------------- -------
490 2000046 2XXXXXXXXXX Eon du Plessis <attrs /> <lbls>
<lbl>Meep11e</lbl>
<lbl>43210</lbl>
<lbl>1234</lbl>
<lbl>Label 5</lbl>
<lbl>Label 6 (edit)</lbl>
</lbls>
-----------------------------------------------------------------------------
Labels Table:
Id AccountId Label RGB LastAssigned LastMessage
----------- ----------- ----------------- ------ ----------------------- ------------
91 2000046 Meep11e 000000 2013-04-15 13:42:06.660 NULL
-------------------------------------------------------------------------------------
This is the issue
Every number can have multiple labels assigned to it and is stored as untyped XML. In Numbers.Labels //lbls/lbl/text() you will notice that the text there will match the text in Labels.Label
This is the stored procedure which updates the Numbers.Labels column, and is run by an external application I am busy writing. The XML structure is generated by this external application, depending on which rows are read in the Labels.Label table
CREATE PROCEDURE [dbo].[UpdateLabels]
#Id INT,
#Labels XML
AS
BEGIN
UPDATE
Numbers
SET
Labels = #Labels
WHERE
Id = #Id
UPDATE
Labels
SET
LastAssigned = GETDATE()
WHERE
label
IN
(SELECT #Labels.value('(//lbls/lbl)[1]', 'VARCHAR(100)'))
END
The issue here is if 2 people log onto the same account, both with their own session, and User 1 tries to run this update stored procedure, but just before the button is pressed to do this update, user 2 deletes 1 of the labels in the Labels.label table which was included in User 1's update session, it will cause the XML to include the "Deleted" row, and can be problematic when I try to query the numbers again (The RGB column gets queried when I display the number since the label is marked up in jQuery to have a hexidecimal colored background)
My thought approach went to checking if the rows included in the built up XML exists before committing the update. How can I achieve this in TSQL? Or can any better way be recommended?
EDIT
Our table structure is intentionally denormalized, there are no foreign key constraints.
EDIT 2
Ok, it would seem my question is a bit hard, or that I brained too hard and got the dumb :). I will try and simplify.
In the Labels column in Numbers, every <lbl> element must exist within the Labels table
When updating the Labels column in Numbers, if a Label in the XML is found which does not exist in the Labels table, an error must be raised.
The XML is pre-formed in my application, meaning, every time the update is run, the old XML in the Labels column in Numbers will be REPLACED with the new XML generated by my application
This is where I need to check whether there are label nodes in my XML which no longer exists within the Labels table
I would check to see if there are rows in your xml that are not in the real table (in the database) before trying anything. And if you find something, exit out early.
Here is a Northwind example.
Use Northwind
GO
DECLARE #data XML;
SET #data =
N'
<root>
<Order>
<OrderId>10248</OrderId>
<CustomerId>VINET</CustomerId>
</Order>
<Order>
<OrderId>-9999</OrderId>
<CustomerId>CHOPS</CustomerId>
</Order>
</root>';
/* select * from dbo.Orders */
declare #Holder table ( OrderId int, CustomerId nchar(5) )
Insert Into #Holder (OrderId , CustomerId )
SELECT
T.myAlias.value('(./OrderId)[1]', 'int') AS OrderId
, T.myAlias.value('(./CustomerId)[1]', 'nchar(5)') AS CustomerId
FROM
#data.nodes('//root/Order') AS T(myAlias);
if exists (select null from #Holder h where not exists (select null from dbo.Orders realTable where realTable.OrderID = h.OrderId ))
BEGIN
print 'you have rows in your xml that are not in the real table. raise an error here'
END
Else
BEGIN
print 'Using the data'
Update dbo.Orders Set CustomerID = h.CustomerId
From dbo.Orders o , #Holder h
Where o.OrderID = h.OrderId
END

"Error converting data type varchar to numeric." - What column?

I have a huge INSERT-statement with 200 columns and suddendly I get the dreaded Error converting data type varchar to numeric. Is there somewhere I can see the actual column that contains the "varchar" value? I know I can remove one of the columns at a time until the error disappears, but it's very tedious.
Unfortunately, this error is a serious pain and there's no easy way to troubleshoot it. When I've encountered it in the past, I've always just had to comment out groups of columns until I find the culprit.
Another approach might be to use the ISNUMERIC() function in in T-SQL to try and find the culprit. Assuming every column in your destination table is numeric (adjust accordingly if it's not), you could try this:
SELECT *
FROM SourceTable
WHERE ISNUMERIC(Column1) = 0
OR ISNUMERIC(Column2) = 0
OR ISNUMERIC(Column3) = 0
OR ISNUMERIC(Column4) = 0
...
This will expose the row that contains your non-numeric value, and should make it pretty clear which column it's in. I know it's tedious, but at least it helps you hunt down the actual value, in addition to the column that's causing trouble.
You don't specify SQL Server Version or number of rows.
For SQL2005+ adding the OUTPUT clause to the INSERT might help identify the rogue row in that it will output the inserted rows until it encounters an error so the next row is the one with the problem
DECLARE #Source TABLE
(
Col1 VARCHAR(10),
Col2 VARCHAR(10)
)
INSERT INTO #Source
SELECT '1','1' UNION ALL
SELECT '2','2' UNION ALL
SELECT '3','3' UNION ALL
SELECT '4A','4' UNION ALL
SELECT '5','5'
DECLARE #Destination TABLE
(
Col1 INT,
Col2 VARCHAR(10)
)
INSERT INTO #Destination
OUTPUT inserted.*
SELECT *
FROM #Source
Returns
(5 row(s) affected)
Col1 Col2
----------- ----------
1 1
2 2
3 3
Msg 245, Level 16, State 1, Line 23
Conversion failed when converting the varchar value '4A' to data type int.
Well, this is just a hunch but what about inserting the data to a temporary table and the using the GUI to migrate the data to the other table? If it still generates an error, you should at least be able to get more feedback on that non-numerical column...
If it doesn't work, consider trying this.
Cheers!

Resources