The below code is a common way of splitting a delimited varchar in TSQL. My question is about
the syntax in the last 2 line. Why are we select N.Value() and giving xml.nodes the alias T(N).
I have not come across this T(N) syntax, i'm unsure what it means and what the N in N.Value is
referencing. I've tried to google this but have found it hard to get an answer, would anyone be
able to help me? Thank you
DECLARE #xml as xml,#str as varchar(100),#delimiter as varchar(10)
SET #str='A,B,C,D,E'
SET #delimiter =','
SET #xml = cast(('<X>'+replace(#str,#delimiter ,'</X><X>')+'</X>') as xml)
SELECT N.value('.', 'varchar(10)') as value
FROM #xml.nodes('X') as T(N)
MSFT documentation on XML nodes() Method can be found here.
The basic syntax of the nodes method is:
nodes (XQuery) as Table(Column)
It transforms the xml data type to relational data, a value per row.
As Panagiotis already pointed out, there is a faster route. If you are using a database with compatibility level of 130 and higher, go can use the STRING_SPLIT table-valued function.
DECLARE #str as varchar(100),#delimiter as varchar(10)
SET #str='A,B,C,D,E'
SET #delimiter =','
Select [value] FROM STRING_SPLIT(#str, #delimiter)
Hope this clarifies things for you.
Related
I am using MS SQL server to get a search result in json format there is only ever 1 row returned in my use case but they designed this as a search tool so you can return more than one value hence the array. The issue I am having is extracting the id value from the array that is returned.
json #response (Array):
{"hits":[{"id":1320172,"email":"xyz#domain.eu","first_name":"IMA","last_name":"TESTERTOO","created":"2018-12-12T11:52:58+00:00","roles":["Learner"],"status":true}],"total":1}
I have tried a number of things but I can't seem to get the path right.
SET #MyUserid = JSON_QUERY(#Reponse, '$.hits[0].id')
SET #MyUserid =JSON_VALUE(#Reponse,'$.hits[0].id')
SET #MyUserid = JSON_QUERY(#Reponse, '$.id')
On most examples I have found the json is not a single line array so I feel like I am missing something there. I'm inexperienced with working with json so any help would be greatly appreciated.
You can try this
DECLARE #json NVARCHAR(MAX)=
N'{"hits":[{"id":1320172,"email":"xyz#domain.eu","first_name":"IMA","last_name":"TESTERTOO","created":"2018-12-12T11:52:58+00:00","roles":["Learner"],"status":true}],"total":1}';
--This will return just one selected value
SELECT JSON_VALUE(#json,'$.hits[0].id')
--This will return the whole everything:
SELECT A.total
,B.*
FROM OPENJSON(#json)
WITH(hits nvarchar(max) AS JSON, total int) A
CROSS APPLY OPENJSON(A.hits)
WITH(id int
,email nvarchar(max)
,first_name nvarchar(max)
,last_name nvarchar(max)
,created nvarchar(max)
,roles nvarchar(max) AS JSON
,[status] bit) B
CREATE TABLE XMLTABLE(id int IDENTITY PRIMARY KEY,XML_DATA XML,DATE DATETIME);
go
INSERT INTO XMLTABLE(XML_DATA,DATE)
SELECT CONVERT(XML,BULKCOLUMN)AS DATA,getdate()
FROM OPENROWSET(BULK 'c:\Demo.xml',SINGLE_BLOB)AS x
go
DECLARE #XML AS XML
DECLARE #OUPT AS INT
DECLARE #SQL NVARCHAR (MAX)
SELECT #XML= XML_DATA FROM XMLTABLE
EXEC sp_xml_preparedocument #OUPT OUTPUT,#XML,'<root xmlns:d="http://abc" xmlns:ns2="http://def" />'
SELECT EMAILR
FROM OPENXML(#OUPT,'d:ns2:FORM/ns2:Form1/ns2:Part/ns2:Part1/ns2:Ba')
WITH
(EMAILR [VARCHAR](100) 'ns2:EmailAddress')
EXEC sp_xml_removedocument #OUPT
go
i.e Demo.xml contains>>
<ns2:FORM xmlns="http://abc" xmlns:ns2="http://def">
<ns2:Form1>
<ns2:Part>
<ns2:Part1>
<ns2:Ba>
<ns2:EmailA>Hello#YAHOO.COM</ns2:EmailA> ...
Error:Msg 6603, Level 16, State 2, Line 6 XML parsing error: Expected
token 'eof' found ':'.
d:ns2-->:<--FORM/ns2:Form1/ns2:Part/ns2:Part1/ns2:Ba
The approach with sp_xml_... methods and FROM OPENXML is outdated!
You should better use the current XML methods .nodes(), .value(), query() and .modify().
Your XML example is not complete, neither is is valid, had to change it a bit to make it working. You'll probably have to adapt the XPath (at least Part1 is missing).
DECLARE #xml XML=
'<ns2:FORM xmlns="http://abc" xmlns:ns2="http://def">
<ns2:Form1>
<ns2:Part>
<ns2:Ba>
<ns2:EmailA>Hello#YAHOO.COM</ns2:EmailA>
</ns2:Ba>
</ns2:Part>
</ns2:Form1>
</ns2:FORM> ';
This is the secure way with namespaces and full path
WITH XMLNAMESPACES(DEFAULT 'http://abc'
,'http://def' AS ns2)
SELECT #xml.value('(/ns2:FORM/ns2:Form1/ns2:Part/ns2:Ba/ns2:EmailA)[1]','nvarchar(max)');
And this is the lazy approach
SELECT #xml.value('(//*:EmailA)[1]','nvarchar(max)')
You should - however - prefer the full approach. The more you give, the better and fast you get...
It is asked many times, but not this way.
I am on SQL Server 2008, and there is no STRING_SPLIT function (like in 2016).
A query returns with the following row, see below a single example row. What you see below in bald is a single field actually, so one varchar column has it altogether:
Appple|10|admin|845687|Apr|26|11:32:29|2016|AwesomeApplication.zip
which I'd like to be split by the pipe | character.
I cannot write a CTE for this, or a custom function.
I have to extract the individual pipe delimited elements, into different columns, within one select statement using the built in string functions like CHARINDEX, PATINDEX.
Does anybody have any idea?
DECLARE #Result Table(Value varchar(50))
DECLARE #x XML
SELECT #X = CAST('<A>' + REPLACE(#StringList, '|', '</A><A>') + '</A>' AS XML)
INSERT INTO #Result
SELECT t.value('.', 'varchar(50)') as inVal
FROM #X.nodes('/A') AS x(t)
This will create a table with one column (Value). Each split value from your pipe-delimited string will create a new record in this table. Then you can join to it however you'd like. Please let me know if this is unclear or if it doesn't work on SQL 2008.
You can increase the size of the varchar, if needed - and you can modify the query to split on different values (comma-delimited, etc.).
ALTER FUNCTION [dbo].[split]
(
#string varchar(max),
#separator varchar(1) -- longer separator is also possible
)
RETURNS
#result TABLE (keyword varchar(max) )
AS
BEGIN
declare #pos int=0, #res varchar(100)
while len(#string)>0
begin
set #pos=CHARINDEX(#separator, #string+#separator,0)
select #res=left(#string,#pos),#string=SUBSTRING(#string,#pos+1,100)
insert #result values(#res)
end
RETURN
END
It's been a long while since this question was asked, and although the OP wanted a non-function solution, this is the only post where the answer pointed me in the right direction for me to write my code, so I thought I'd share my solution here.
What it does
This code, checks the master db compatibility mode and creates an appropriate _my_string_split function. If string_split exists, this is just a wrapper.
If not it will use the method Stan proposed in the accepted answer
So now, regardless of where I'm running my code, after creating the function, all I need to do is use master.dbo._my_string_split, for example:
SELECT * FROM master.dbo._my_string_split('Hello|World!','|')
The Code
USE master
IF OBJECT_ID('dbo._my_string_split') IS NOT NULL DROP FUNCTION [dbo].[_my_string_split]
GO
DECLARE #sqlCode NVARCHAR(MAX);
IF 130 <= (SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME())
SET #sqlCode = '
--- 130+ string_split exists
CREATE FUNCTION [dbo].[_my_string_split]
(
#string nvarchar(max),
#separator nvarchar(1)
)
RETURNS
#result TABLE ([Value] nvarchar(max) )
BEGIN
INSERT INTO #result
SELECT [Value] FROM string_split(#string,#separator)
RETURN
END
'
ELSE
SET #sqlCode = '
--- before 130: string_split does not exists
CREATE FUNCTION [dbo].[_my_string_split]
(
#string nvarchar(max),
#separator nvarchar(1)
)
RETURNS
#result TABLE ([Value] nvarchar(max) )
BEGIN
INSERT INTO #result
SELECT [Value]=t.value(''.'', ''varchar(max)'')
FROM (SELECT x=(CAST((''<A>'' + REPLACE(#string, #separator, ''</A><A>'') + ''</A>'') AS XML))) a
CROSS APPLY a.x.nodes(''/A'') AS x(t)
RETURN
END
'
PRINT #sqlCode
EXEC sp_sqlexec #sqlCode
Hope others will find this useful.
I'm working on a problem even my colleagues say is impossible. There is XML already in our database and I want to be able to take single attributes from the XML and set them equal to variables to work with. This is my code so far (that doesn't work).
declare #MyText varchar(10)
declare #MyXML XML
set #MyXML = '
<ns0:TestXML xmlns:ns0="http://test.com">
<TestValue>11</TestValue>
</ns0:TestXML>'
set #MyText =
(
;with XMLNAMESPACES ('http://test.com' as ns0)
set #MyText =
(
select
a.bo.value('(/ns0:TestXML2[1]/TestXML3)[1]','INT') as [Number]
from #MyXML.nodes('ns0:TestXML') a(bo)
)
)
Any help would be greatly appreciated, thank you. Also if my logic is completely off about how SQL works with XML, please don't hesitate to educate me.
How's this, it returns 11 from the provided XML which is what you want I assume:
declare #MyText varchar(10)
declare #MyXML XML
set #MyXML = '
<ns0:TestXML xmlns:ns0="http://test.com">
<TestValue>11</TestValue>
</ns0:TestXML>'
;with XMLNAMESPACES ('http://test.com' as ns0)
select #MyText =
a.bo.value('(/ns0:TestXML)[1]','INT')
from #MyXML.nodes('ns0:TestXML') a(bo)
select #MyText as [TheImpossibleValue]
I have a xml document which contains some custom fields which i wont know the names of. i want to generate a select statement which will list the contents in a name value style.
All examples I have found sofar require me to know the names of the nodes.
i.e.
declare #idoc int
declare #doc nvarchar(max); set
#doc = '<user>
<additionalfields>
<Account__Manager>Fred Dibner</Account__Manager>
<First__Aider>St Johns Ambulance</First__Aider>
</additionalfields>
</user>'
EXEC sp_xml_preparedocument #idoc OUTPUT, #doc;
SELECT * FROM OPENXML (#idoc, 'user/additionalfields/',1)
is it possible to achieve this?
well i found the answer after a fair amount more experimenting.(incidentally the double underscore replace is due to the output format of some of the database field names.)
SELECT replace(name,'__',' ') as name, value
FROM OPENXML (#idoc, '/user/additionalfields/*',1)
WITH (
Name nvarchar(4000) '#mp:localname',
value nvarchar(4000) './text()'
)