Related
input:
string
'abc_def_ghk_lmn'
output:
dgl
You can try this (or even create a function):
DECLARE #str varchar(250) = 'abc_def_ghk_lmn'
DECLARE #result varchar(250)='';
WHILE(charindex('_',#str)!=0)
BEGIN
DECLARE #position int = charindex('_',#str)
SET #result += substring(#str,#position+1,1)
SET #str = substring(#str,#position+1,len(#str))
END
SELECT #result
You can use a recursive CTE:
IF OBJECT_ID('tempdb..#t') is not null drop table #t
SELECT * into #t from (values (N'abc_def_ghk_lmn'), (N'a_f_k_n'), (null), ('____'), ('asasas'), ('a_sasas'), ('asas_')) T(val);
;WITH CTE AS
(
SELECT
Cast(SUBSTRING(T.val, CHARINDEX('_',T.val,1) + 1, 1) as nvarchar(4000)) FC
,CHARINDEX('_', T.val, 1) CI
,val
,0 [level]
from #t T
where CHARINDEX('_', T.val, 1) > 0
union all
SELECT
Cast(T.FC + SUBSTRING(T.val, CHARINDEX('_',T.val,T.CI+1) + 1, 1) as nvarchar(4000)) FC
,CHARINDEX('_', T.val, T.CI+1) CI
,val
,t.[level] + 1
from CTE T
where CHARINDEX('_',T.val,T.CI+1) > 0
)
, Res AS
(
SELECT
*
,ROW_NUMBER() OVER (Partition by val order by [level] desc) RN
from CTE
)
SELECT * from Res where RN = 1
This uses Jeff Moden's DelimitedSplit8K Function. Firstly because i don't know what version of SQL Server you are using, and secondly, the inbuilt function STRING_SPLIT (available in SQL Server 2016 onwards) doesn't include an Item Number value (thus how does one exclude the first result?):
SELECT (SELECT LEFT(Item, 1)
FROM DelimitedSplit8K ('abc_def_ghk_lmn','_') DS
WHERE DS.ItemNumber > 1
FOR XML PATH(''));
Edit:
Example with a dataset:
WITH VTE AS(
SELECT *
FROM (VALUES ('asdgsad_sdfh_sadfh'),('_ashdf+ashd'),('jsda_sdkhfsdjf_654_asdfkhasd_567465413_kasbgdjkasdj')) V(S))
SELECT (SELECT LEFT(Item, 1)
FROM DelimitedSplit8K (S,'_') DS
WHERE DS.ItemNumber > 1
FOR XML PATH('')) AS FirstCharacters
FROM VTE;
Try this:
DECLARE #String VARCHAR(50)= 'abc_def_ghk_lmn',#Result VARCHAR(10)=''
WHILE CHARINDEX('_',#String)>0
BEGIN
SELECT #Result=#Result + SUBSTRING(#String,CHARINDEX('_',#String)+1,1)
SELECT #String=RIGHT(#String,LEN(#String)- CHARINDEX('_',#String))
END
SELECT #Result FinalResult
OUTPUT:
FinalResult
dgl
Please try this -...Always use SET BASED Approach
SOLUTION
DECLARE #x AS XML=''
DECLARE # AS VARCHAR(1000) = 'abc_def_ghk_lmn_'
SET #x = CAST('<A>'+ REPLACE(#,'_','</A><A>')+ '</A>' AS XML)
;WITH CTE AS
(
SELECT t.value('.', 'VARCHAR(10)') Value FROM #x.nodes('/A') AS x(t)
)
,CTE1 AS
(
SELECT * , ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) rnk FROM CTE
)
,CTE2 AS
(
SELECT SUBSTRING(Value,1,1) v , rnk FROM CTE1
)
SELECT CASE WHEN LEFT(#,1) <> '_' THEN MAX(SUBSTRING(u,2,LEN(u))) ELSE MAX(u) END finalstr from (
SELECT
(
SELECT '' + v
FROM CTE2 a
FOR XML PATH('')
)u
FROM CTE2 )x
OUTPUT
finalstr
---------------
dgl
(1 row affected)
Try this below
DECLARE #str TAble(String varchar(250))
INSERT INTO #str
SELECT 'abc_def_ghk_lmn_opq_rst_uvw_xyz'
SELECT STUFF((SELECT ''+LEFT(String,1) FROM
(
SELECT Split.a.value('.','Varchar(1000)') As String,
ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS Id FROM
(
SELECT CASt('<S>'+REPLACE(String,'_','</S><S>')+'</S>' AS XML )AS String
FROM #str
)as A
CROSS APPLY String.nodes ('S') AS Split(a)
)dt
WHERE dt.Id <>1
FOR XML PATH ('')),1,0,'') AS ExpectedColumn
Result
ExpectedColumn
--------------
dglorux
Use LEFT to get the left part and INSTR to find underscore and finally get your string:
SELECT LEFT(FIELD_1, CHARINDEX('_', FIELD_1) - 2) AS [ANY_ALIAS]
FROM TABLE_1;
EDIT: Now is MSSQL... ;D
-- i need data like this F11.20,F13.20,F14.10 in sql server
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
select left(#S, charindex(':', #S, charindex(':', #S)+2)-2);
Any Split/Parse Function would do the trick, but you would have to perform secondary logic to clean the parsed string. That said, I modified a parse function to accept any two non-like delimiters (start/end). In this case a , and :
Also, being a Table-Valued-Function, it is easy to incorporate into a CROSS APPLY or as a stand-alone as illustrated below.
Example
Select NewString = Stuff((Select ',' +RetVal
From [dbo].[udf-Str-Extract](#S,',',':')
For XML Path ('')),1,1,'')
Returns
F11.20,F13.20,F14.10
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (Select *,RetVal = Substring(#String, N, L) From cte4) A
Where charindex(#Delimiter2,RetVal)>1
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[udf-Str-Extract] (#String,'[[',']]')
*/
EDIT Just to Help with the Visualization
If you executed the TVF alone:
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
Select * From [dbo].[udf-Str-Extract](#S,',',':')
Returns
RetSeq RetPos RetVal
1 2 F11.20
2 10 F13.20
3 38 F14.10
EDIT 2 - Execute via Cross Apply
Declare #YourTable table (ID int,SomeString varchar(200))
Insert Into #YourTable values
(1,',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom'),
(2,',Z99.55:,Z25.10:Someother text')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select NewString = Stuff((Select ',' +RetVal
From [dbo].[udf-Str-Extract](A.SomeString,',',':')
For XML Path ('')),1,1,'')
) B
Returns
ID NewString
1 F11.20,F13.20,F14.10
2 Z99.55,Z25.10
If the text you're extracting is always in the form of (letter, number, number, ., number, number) and there's always 3 instances of that text, then you could do this:
WITH
s1(string, ci) AS (SELECT #S, CHARINDEX(':', #S)),
s2(ci) AS (SELECT CHARINDEX(':', #S, ci+1) FROM s1),
s3(ci) AS (SELECT CHARINDEX(':', #S, ci+1) FROM s2)
SELECT
SUBSTRING(string, s1.ci-6, 6)+','+
SUBSTRING(string, s2.ci-6, 6)+','+
SUBSTRING(string, s3.ci-6, 6)
FROM s1, s2, s3;
Execution Plan:
It doesn't get any more efficient then that.
If its always the 6 characters before any instance of ":" you can grab a copy of NGrams8K and do this:
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
SELECT NewString = STUFF
((SELECT ','+SUBSTRING(#S, position-6, 6)
FROM dbo.NGrams8k(#S, 1)
WHERE token = ':'
FOR XML PATH('')),1,1,'');
Another way using NGrams8K and a variable:
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
declare #newstring varchar(100)='';
declare #S varchar(200) = ',F11.20:,F13.20:Sedative, hypnotic o,F14.10:Cocaine abuse, uncom';
declare #newstring varchar(100)='';
SELECT
#newstring +=
CASE #newstring WHEN '' THEN '' ELSE ',' END +SUBSTRING(#S, position-6, 6)
FROM dbo.NGrams8k(#S, 1)
WHERE token = ':';
SELECT #newstring;
Table A
ID Name
1 Sachin
2 Rahul
3 Saurav
I want to display Names according to ID on UI.
IDs are 1,2,3,1/2,1/2/3
I have displayed Name for 1,2,3 but I am not able to fetch for id as 1/2 and 1/2/3 as sachin/rahul and sachin/rahul/saurav.
Fun with strings... The following will essentially do a global search and replace on the string of IDs.
Now, we can use a parse/split function if you need a more robust approach
Declare #YourTable table (ID int,Name varchar(25))
Insert Into #YourTable values
(1,'Sachin'),
(2,'Rahul'),
(3,'Saurav')
Declare #Fetch varchar(max) = '1,2,3,1/2,1/2/3'
Select #Fetch = Replace('|'+Replace(Replace(#Fetch,',','|,|'),'/','|/|')+'|',MapFrom,MapTo)
From (
Select MapFrom='|'+cast(ID as varchar(25))+'|'
,MapTo =Name
From #YourTable
) A
Select Replace(#Fetch,'|','')
Returns
Sachin,Rahul,Saurav,Sachin/Rahul,Sachin/Rahul/Saurav
EDIT- Just in case you need a TABLE Version
Declare #Names table (ID int,Name varchar(25))
Insert Into #Names values (1,'Sachin'),(2,'Rahul'),(3,'Saurav')
Declare #IDs table (ID int,IDList varchar(150))
Insert Into #IDs values (1,'1,2,3,1/2,1/2/3'),(2,'2,3,1/2/3')
;with cte as (
Select A.*
,Name = IIF(Charindex('/',B.RetVal)>0 and C.RetVal>1,'/','')+N.Name
,RN = Row_Number() over (Partition By A.ID Order By B.RetSeq,C.RetSeq)
From #IDs A
Cross Apply [dbo].[udf-Str-Parse](A.IDList,',') B
Cross Apply [dbo].[udf-Str-Parse](B.RetVal,'/') C
Join #Names N on N.ID=C.RetVal
)
Select Distinct
ID
,IDList
,NewString = Replace((Select Stuff((Select ',' +Name From cte Where ID=A.ID Order By RN For XML Path ('')),1,1,'') ),',/','/')
From cte A
Returns
ID IDList NewString
1 1,2,3,1/2,1/2/3 Sachin,Rahul,Saurav,Sachin/Rahul,Sachin/Rahul/Saurav
2 2,3,1/2/3 Rahul,Saurav,Sachin/Rahul/Saurav
The UDF if interested
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
--Performance On a 5,000 random sample -8K 77.8ms, -1M 79ms (+1.16), -- 91.66ms (+13.8)
Something like this?
declare #ids varchar(100) = '1/2/3'
,#names varchar(100) = ''
select #names += case when #names = '' then '' else '/' end + name
from mytable
where '/' + #ids +'/' like '%/' + cast(id as varchar(10)) + '/%'
order by id
select #names
Sachin/Rahul/Saurav
I am using CTE to convert xml to csv so that it can be exported to a file, however if I have an empty xml tag, this currently gets ignored.
Here is my initial solution courtesy of this previous very helpful post:
https://stackoverflow.com/a/23785202/6260721
Here is my sql:
CREATE TABLE EXPORT_TEST
(
DATA varchar(max)
)
INSERT INTO EXPORT_TEST (DATA)
VALUES ('<EXPORT_DATA><ID>ABC123</ID><PRICE_A>5.6</PRICE_A><PRICE_B></PRICE_B><PRICE_C>8.1</PRICE_C></EXPORT_DATA>')
DECLARE #commaSeparatedValues NVARCHAR(MAX)
DECLARE #xml XML = (SELECT TOP 1 CONVERT(xml,DATA) FROM EXPORT_TEST)
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
), cols AS (
SELECT
rownr,
Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('//text()') Tbl(Col)
)
INSERT INTO EXPORT_TEST(DATA)
SELECT DISTINCT
STUFF((
SELECT ',' + IIF(ISNUMERIC(value) = 1, Value, '''' + Value + '''')
FROM cols SSF WHERE SSF.rownr = S.rownr
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)'
), 1, 1, '') as DATA
FROM cols S
SELECT * FROM EXPORT_TEST
At the moment, it is returning:
'ABC123',5.6,8.1
But I don't want it to ignore PRICE_B, I want it to return an empty string:
'ABC123',5.6,,8.1 <--extra comma required where PRICE_B should be
How can I achieve this?
Besides the possibility to shredd the full XML and re-concatenate its values (there is an answer already), you might use FLWOR-XQuery:
DECLARE #xml XML=
'<EXPORT_DATA>
<ID>ABC123</ID>
<PRICE_A>5.6</PRICE_A>
<PRICE_B />
<PRICE_C>8.1</PRICE_C>
</EXPORT_DATA>';
EDIT better to read with a variable $txt instead of ($n/text())[1]
SELECT
STUFF
(
#xml.query('
let $r:=/EXPORT_DATA
for $n in $r/*
let $txt:=($n/text())[1]
return if(empty($txt) or not(empty(number($txt)))) then
concat(",",string($txt))
else concat(",''",string($txt),"''")
').value('.','nvarchar(max)'),1,1,'');
The result
'ABC123' ,5.6 , ,8.1
This code works on a mass of records using XQUERY.
I'm assuming char(10) (Line Feed) does not appear in your data.
I'm assuming the maximum length of the concatenated text is 1000 (I don't want to use varchar(max) for no good reason)
You can change both of these assumptions if you wish
declare #separator char(1) = char(10)
select substring
(
replace
(
cast
(
cast(DATA as xml).query
(
'for $i in //*
where not($i/*)
return concat
(
sql:variable("#separator")
,if(local-name($i) = "ID") then ('''''''') else ('''')
,($i/text())[1]
,if(local-name($i) = "ID") then ('''''''') else ('''')
)'
) as nvarchar(1000)
) ,' ' + #separator ,','
) ,2 ,1000
) as csv
from EXPORT_TEST
INSERT INTO EXPORT_TEST (DATA) VALUES
('<EXPORT_DATA><ID>ABC123</ID><PRICE_A>5.6</PRICE_A><PRICE_B></PRICE_B><PRICE_C>8.1</PRICE_C></EXPORT_DATA>')
,('<EXPORT_DATA><ID>DEF456</ID><PRICE_A>6.7</PRICE_A><PRICE_B>66.77</PRICE_B><PRICE_C>7.2</PRICE_C></EXPORT_DATA>')
,('<EXPORT_DATA><ID>GHI789</ID><PRICE_A></PRICE_A><PRICE_B>88.99</PRICE_B><PRICE_C></PRICE_C></EXPORT_DATA>')
csv
'ABC123',5.6,,8.1
'DEF456',6.7,66.77,7.2
'GHI789',,88.99,
What about this:
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
), cols AS (
SELECT
rownr,
Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('EXPORT_DATA/child::node()') Tbl(Col)
)
INSERT INTO EXPORT_TEST(DATA)
SELECT DISTINCT
STUFF((
SELECT ',' + IIF(ISNUMERIC(value) = 1 OR LEN(value) = 0, Value, '''' + Value + '''')
FROM cols SSF WHERE SSF.rownr = S.rownr
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)'
), 1, 1, '') as DATA
FROM cols S
Using cte.xml.nodes('EXPORT_DATA/child::node()') in the second CTE will give as all nodes:
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
)
SELECT
rownr
,Tbl.Col.query('.')
,Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('EXPORT_DATA/child::node()') Tbl(Col)
Then, in the concatenation we need to add check for empty string:
IIF(ISNUMERIC(value) = 1 OR LEN(value) = 0, Value, '''' + Value + '''')
I have code which selects the string between the first and second '/' in a typical string which may look like this:
2014-Ceilings/Ceilings/Repair/Asbestos/Supalux
The following code correctly returns the value 'Ceilings' in the above example.
SELECT
REPLACE (LEFT(SUBSTRING(ElementPath,CHARINDEX
('/',ElementPath)+1,LEN(ElementPath)),CHARINDEX
('/',SUBSTRING(ElementPath, CHARINDEX
('/',ElementPath)+1,LEN(ElementPath)))),'/','')
FROM K2_Master.dbo.tbElement
How can I amend it to select the string between the second and third '/' to return the value 'Repair'?
Check this little trick may work :
DECLARE #String VARCHAR(100)= '2014-Ceilings/Ceilings/Repair/Asbestos/Supalux'
SET #String = '<N>' + Replace(#String, '/', '</N><N>')
+ '</N>'
SELECT c.value('/N[1]', 'varchar(30)'),
c.value('/N[2]', 'varchar(30)'),
c.value('/N[3]', 'varchar(30)')
FROM (SELECT Cast(#String AS XML)) t(c)
SELECT c1.value('.', 'varchar(30)')
FROM (SELECT CAST(#String AS XML)) t(c)
CROSS APPLY c.nodes('/N') AS t1(c1)
SELECT value
FROM (SELECT c1.value('.', 'varchar(30)') AS value,
ROW_NUMBER()
OVER(
ORDER BY (SELECT 1)) rn
FROM (SELECT Cast(#String AS XML)) t(c)
CROSS APPLY c.nodes('/N') AS t1(c1)) temp
WHERE rn = 3
In your case
SELECT c.value('/N[1]', 'varchar(30)'),
c.value('/N[2]', 'varchar(30)'),
c.value('/N[3]', 'varchar(30)')
FROM (SELECT Cast ('<N>' + Replace( Replace (ElementPath, '/', '</N><N>'),'&','&')
+ '</N>' AS XML)
FROM K2_Master.dbo.tbElement) t(c)
I chased your question, and reached a lengthy result..
DECLARE #a VARCHAR(40) = '2014-Ceilings/Ceilings/Repair/Asbestos/Supalux'
SELECT #a
,LEFT(
SUBSTRING(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),CHARINDEX('/',SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),1)+1,LEN(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)))),
CHARINDEX('/',SUBSTRING(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),CHARINDEX('/',SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)),1)+1,LEN(SUBSTRING(#a,CHARINDEX('/',#a,1)+1,LEN(#a)))),1) - 1
)
Result:
Answer to you comment.
DECLARE #String VARCHAR(100)= '2014-Ceilings/Ceilings/Repair/Asbestos/Supalux',
#value INT = 2
DECLARE #itra INT = 1
WHILE #itra <= #value
BEGIN
SET #String = (SELECT SUBSTRING(#String,CHARINDEX('/',#String,1)+1,LEN(#String)))
--SELECT #String
SET #itra = #itra + 1
END
SELECT LEFT(#String, CHARINDEX('/',#String,1) - 1)
Give the position of '/' as #value and see the result. For the above sql, the result is Repair. If you give #value = 3, result is Asbestos