Get Last values using Select Substring in SQL Query - sql

I have a table with following Column
Name
----------------------
test10/20000020
test1/test2 / 20000001
test3/test4 / 20000002
test5/20000017
test5/test6 / 20000004
test5/20000007
I need to select the last value
I.e
20000020
20000001
20000002
20000017
20000004
20000007
I have tried with
(SUBSTRING(Name,0,(CHARINDEX('/',Name,0))))
But I am getting First Value
i.e. test1
test2
test3

Using XML:
declare #xml xml;
declare #str nvarchar(max) = '';
with data as
(
select 'test1/test2/test3/344495' as r
union ALL
select 'test1/344556' as r
)
select #str = #str + '<r><v>' + replace(r,'/','</v><v>') + '</v></r>'
from data;
-- obtain xml
set #xml = cast(#str as xml);
-- select last value from each row
select v.value('(v/text())[last()]', 'nvarchar(50)')
from #xml.nodes('/r') as r(v)
Same idea but without variables:
;with data as
(
select 'test1/test2/test3/344495' as r
union ALL
select 'test1/344556' as r
),
xmlRows AS
(
select cast('<r><v>' + replace(r,'/','</v><v>') + '</v></r>' as xml) as r
from data
)
select v.value('(v/text())[last()]', 'nvarchar(50)') as lastValue
from xmlRows xr
cross APPLY
r.nodes('/r') as r(v)

SELECT SUBSTRING( Name, LEN(Name) - CHARINDEX('/',REVERSE(Name)) + 2 , LEN(Name) ) FROM SAMPLE
worked for me

SUBSTRING ( expression ,start , length )
Instead of that
(SUBSTRING(Name,0,(CHARINDEX('/',Name,0))))
You should use
(SUBSTRING(Name,(CHARINDEX('/',Name,0)+1),8))
if string size is fixed.

You could solve your problem with the following query:
SELECT (SUBSTRING(Name,(CHARINDEX('/',Name,0)),LEN(Name)))

Related

SQL Server split string and access different parts of it

I need to update the url stored in a column.
My columns have values like this:
https://www.site.sharepoint.com/sites/test/AB-19-CALL
I want to update this URL to:
https://www.site.sharepoint.com/sites/test/CALL-AB-19
To get the last part which is AB-19-CALL, I used the below query
SELECT
SUBSTRING(urlcompte, LEN(urlcompte) - CHARINDEX('/', REVERSE(urlcompte)) + 2, LEN(urlcompte)),
UrlCompte
FROM
tblAccount
Now to split and reverse the AB-19-call I would again need to use the entire query above and then select substring using -
Is there an easier way to do this?
In C# we can do something like this:
urlCompte.Split('-')[2] + urlCompte.Split('-')[0] + urlCompte.Split('-')[1]
Is there a way in SQL Server to split the column and access different parts of it?
This would work to split the string parts -
DECLARE #txt NVARCHAR(500)= 'https://www.site.sharepoint.com/sites/test/AB-19-CALL';
SELECT value
FROM STRING_SPLIT(REVERSE(SUBSTRING(REVERSE(#txt), 1, CHARINDEX('/', REVERSE(#txt))-1)), '-');(substring(reverse(#txt),1,charindex('/',reverse(#txt))-1)),'-')
If you are so lucky to be using SQL Server 2017 you can use for string_agg function for concatenating splitted string pieced with SQL string_split function
Here is the script to change the order of last part in your url
declare #url varchar(100) = 'CALL-AB-19'
select
string_agg(value,'-') within group (order by rn desc)
from (
select
value, row_number() over (order by #url) as rn
from STRING_SPLIT( #url , '-' )
) t
If you want to apply this solution as a set based solution on your table rows with a single SELECT statement, you can execute following SQL
select
id, string_agg(value,'-') within group (order by rn desc)
from (
select
id, value, row_number() over (partition by id order by url) as rn
from urlList
cross apply STRING_SPLIT( url , '-' )
) t
group by id
I assume, in your table urlList, you have a PK field id
If you don't use SQL Server 2016 for split function and SQL Server 2017 for string aggregate function, you can search the web for split string function samples. And using FOR XML Path to concatenate string parts, you can build a solution with the help of CTE expression as follows
;with cte as (
select
urlList.id,
urlList.url,
s.id sid,
s.val
from urlList
cross apply dbo.split(url,'-' ) s
)
SELECT
distinct
id,
STUFF(
(
SELECT
'-' + u.val
FROM cte as u
where u.id = cte.id
Order By sid desc
FOR XML PATH('')
), 1, 1, ''
) As newurl
FROM cte
Please note that the referred split function returns an id named numeric field which shows the order of the splitted piece. So while concatenating I use an "order by" clause on the same field this time in descending order
If someone is using SQL server 2012 or older version then it can be possible by this way:
DECLARE #URL VARCHAR(100) = 'https://www.site.sharepoint.com/sites/test/AB-19-CALL'
SELECT TOP 1 dbo.[Reversedata](DATA, '-')
FROM (SELECT * FROM dbo.Splitter(#URL, '/') as t) as tt order by Id desc
In above code I have used two functions:
1) Splitter : To split the string (https://ole.michelsen.dk/blog/split-string-to-table-using-transact-sql.html)
2) Reversedata: To reverse the data (http://picnicerror.net/development/sql-server/reverse-order-words-string-sql-server-2012-01-16/)
Splitter:
CREATE FUNCTION [dbo].[Splitter]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
GO
Reverse:
CREATE FUNCTION [dbo].[udf_ReverseSequenceOrder] (
#Input nvarchar(200)
,#Delimiter nvarchar(5)
)
RETURNS nvarchar(200)
AS
BEGIN
DECLARE #Output nvarchar(200)
WHILE LEN(#Input) > 0
BEGIN
IF CHARINDEX(#Delimiter, #Input) > 0
BEGIN
SET #Output = SUBSTRING(#Input,0,CHARINDEX(#Delimiter, #Input)) + #Delimiter + ISNULL(#Output,'')
SET #Input = SUBSTRING(#Input,CHARINDEX(#Delimiter, #Input)+1,LEN(#Input))
END
ELSE
BEGIN
SET #Output = #Input + #Delimiter + ISNULL(#Output,'')
SET #Input = ''
END
END
RETURN SUBSTRING(#Output,0,LEN(#Output))
END
Well, this is the xml based solution. For SQL Server 2008 and above.
DECLARE #url VARCHAR(100) = 'AB-19-CALL'
SELECT MyUrl FROM
(
SELECT CAST('<Url><Part>' + REPLACE(#url,'-','</Part><Part>') + '</Part></Url>' AS XML) AS my_Xml
) t1
CROSS APPLY
(
SELECT
my_Data.D.value('Part[3]','varchar(50)') + '-' +
my_Data.D.value('Part[1]','varchar(50)') + '-' +
my_Data.D.value('Part[2]','varchar(50)') AS MyUrl
FROM t1.my_Xml.nodes('/Url') as my_Data(D)
) t2
Result
MyUrl
----------
CALL-AB-19

SQL Server convert xml to csv using CTE - nulls currently being ignored

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 + '''')

Inserting multiple value in table with String input

I am passing one string to store procedure : 1:20,2:30,4:50
It contains id and appropriate value for it.
how can I add value as shown in below table in database.
ID Value
1 20
2 30
4 50
I have already "stringSplit" function which works perfectly and gives out put in row value some think like this :
1:20
2:30
4:50
can anyone please help me to insert data into table with any solution.
i already try this solution
insert <table> (colname)
select y.item
from dbo.SplitString(#testString, ':') x
cross apply
dbo.SplitString(x.item, ',') y
but this will return duplicate value as more as id value.
my store procedure is
CREATE PROCEDURE [dbo].[temp_result_insert]
#dataString varchar(max)
AS
insert into tempTable(id,marks)
select x.Item,y.Item
from dbo.SplitStringVarcahr(#dataString, ':') x
cross apply
dbo.SplitStringVarcahr(x.Item,',') y
RETURN 0
As you already splitted into rows and you want insert into some table by splliting into two columns may be this works
CREATE TABLE #Test(ID INT,Val INT)
declare #t table (val varchar(50))
insert into #t (val)values ('1:20,2:30,4:50')
declare #str varchar(max)
;with cte as (
SELECT
Split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT
CAST ('<M>' + REPLACE([val], ',', '</M><M>') + '</M>' AS XML) AS String
FROM #t) AS A CROSS APPLY String.nodes ('/M') AS Split(a))
INSERT INTO #Test
select SUBSTRING(String,0,CHARINDEX(':',String)),REVERSE(SUBSTRING(reverse(String),0,CHARINDEX(':',reverse(String)))) from cte
select * from #test
You can also try XML.nodes() and string functions to spit the data. Something like this
DECLARE #var VARCHAR(100) = '1:20,2:30,4:50'
DECLARE #xml xml = CONVERT(xml, '<r>' + REPLACE(#var,',','</r><r>') + '</r>')
SELECT LEFT(val,cindex - 1) c1,RIGHT(val,LEN(val) - cindex) c2
FROM
(
SELECT CHARINDEX(':',c.value('text()[1]','VARCHAR(100)')) cindex,c.value('text()[1]','VARCHAR(100)') val
FROM #xml.nodes('r') as t(c))c
Use substring and Charindex:
SELECT Substring(col, 0, Charindex(col, ':') - 1) AS id,
Substring(col, Charindex(col, ':') + 1, Len(col)-Charindex(col, ':')) AS value
FROM splittedtable

Split the query string with repeatative special characters using SQL

This is my String
Declare #qstr as varchar(max)='hireteammember.aspx?empemail=kuldeep#asselsolutions.com&empid=376&empname=kuldeep&adminname=TMA1&term=5&teamid=161&contactid=614¥1&WP=100¥5¥Months&Amt=500&DueDay=5&StrDt=12/31/2013&MemCatg=Employees&StrTm=21:05&PlnHrs=5&WrkDays=true¥true¥true¥true¥true¥false¥false'
I want to extract the values of empid,empname,adminname,term,teamid,contactid,WP,Months,Dueday,StrDt,MemCatgmStrTm,PlnHrs,WrkDays and assign them to new variables
I have used
select ( SUBSTRING(#qstr,CHARINDEX('=',#qstr)+1,CHARINDEX('&',#qstr)-CHARINDEX('=',#qstr)-1)))
but only getting the 'empemail' , for the next occurance of special char '&' , not able to get the values of further terms , if i am using '&' in spite of '=' .
Help me to split the whole string
How about using XML to split the values into rows, and then splitting them into columns.
Something like
Declare #qstr as varchar(max)='hireteammember.aspx?empemail=kuldeep#asselsolutions.com&empid=376&empname=kuldeep&adminname=TMA1&term=5&teamid=161&contactid=614¥1&WP=100¥5¥Months&Amt=500&DueDay=5&StrDt=12/31/2013&MemCatg=Employees&StrTm=21:05&PlnHrs=5&WrkDays=true¥true¥true¥true¥true¥false¥false'
DECLARe #str VARCHAR(MAX) = SUBSTRING(#qstr,CHARINDEX('?',#qstr,0) + 1, LEN(#qstr)-CHARINDEX('?',#qstr,0))
DECLARE #xml XML
SELECT #xml = CAST('<d>' + REPLACE(#str, '&', '</d><d>') + '</d>' AS XML)
;WITH Vals AS (
SELECT T.split.value('.', 'nvarchar(max)') AS data
FROM #xml.nodes('/d') T(split)
)
SELECT LEFT(data,CHARINDEX('=',data,0) - 1),
RIGHT(data,LEN(data) - CHARINDEX('=',data,0))
FROM Vals
SQL Fiddle DEMO
CREATE FUNCTION dbo.SplitQueryString (#s varchar(8000))
RETURNS table
AS
RETURN (
WITH splitter_cte AS (
SELECT CHARINDEX('&', #s) as pos, 0 as lastPos
UNION ALL
SELECT CHARINDEX('&', #s, pos + 1), pos
FROM splitter_cte
WHERE pos > 0
),
pair_cte AS (
SELECT chunk,
CHARINDEX('=', chunk) as pos
FROM (
SELECT SUBSTRING(#s, lastPos + 1,
case when pos = 0 then 80000
else pos - lastPos -1 end) as chunk
FROM splitter_cte) as t1
)
SELECT substring(chunk, 0, pos) as keyName,
substring(chunk, pos+1, 8000) as keyValue
FROM pair_cte
)
GO
declare #queryString varchar(2048)
set #queryString = 'foo=bar&temp=baz&key=value';
SELECT *
FROM dbo.SplitQueryString(#queryString)
OPTION(MAXRECURSION 0);
when run produces the following output.
keyName keyValue
------- --------
foo bar
temp baz
key value
(3 row(s) affected)
I believe that this will do exactly what you are asking.
SQL FIDDLE DEMO
If the order of the values in the html string remains same i would suggest using the whole string name like
select ( SUBSTRING(#qstr,CHARINDEX('empemail=',#qstr)+1,CHARINDEX('&empid=',#qstr)-CHARINDEX('empemail=',#qstr)-1)))
If you are still looking for nth occurance then refer to this link
Declare #qstr as varchar(max)='hireteammember.aspx?empemail=kuldeep#asselsolutions.com&empid=376&empname=kuldeep&adminname=TMA1&term=5&teamid=161&contactid=614¥1&WP=100¥5¥Months&Amt=500&DueDay=5&StrDt=12/31/2013&MemCatg=Employees&StrTm=21:05&PlnHrs=5&WrkDays=true¥true¥true¥true¥true¥false¥false'
(select ( SUBSTRING(#qstr,CHARINDEX('&empname=',#qstr)+1,CHARINDEX('&adminname=',#qstr)-CHARINDEX('&empname=',#qstr)-1)))
(select ( SUBSTRING(#qstr,CHARINDEX('?empemail=',#qstr)+1,CHARINDEX('&empid=',#qstr)-CHARINDEX('?empemail=',#qstr)-1)))
like this i have splitted and updated The whole string. Thank you All for your answers, Your answers Helped me to solve this

Return Distinct Rows That Contain The Same Value/Character In SQL

I have a bit of a tricky situation. I have a column that contains a pipe delimited set of numbers in numerous rows in a table. For example:
Courses
-------------------
1|2
1|2|3
1|2|8
10
11
11|12
What I want to achieve is to return rows where the number only appears once in my output.
Ideally, I want to try and carry this out using SQL rather than having to carry out checks at a web application level. Carrying out a DISTINCT does not achieve what I want.
The desired output would be:
Courses
-------------------
1
2
3
8
10
11
12
I would appreciated if anyone can guide me in the right direction.
Thanks.
Please try:
declare #tbl as table(Courses nvarchar(max))
insert into #tbl values
('1|2'),
('1|2|3'),
('1|2|8'),
('10'),
('11'),
('11|12')
select * from #tbl
SELECT
DISTINCT CAST(Split.a.value('.', 'VARCHAR(100)') AS INT) AS CVS
FROM
(
SELECT CAST ('<M>' + REPLACE(Courses, '|', '</M><M>') + '</M>' AS XML) AS CVS
FROM #tbl
) AS A CROSS APPLY CVS.nodes ('/M') AS Split(a)
ORDER BY 1
Try this one -
SET NOCOUNT ON;
DECLARE #temp TABLE
(
string VARCHAR(500)
)
DECLARE #Separator CHAR(1)
SELECT #Separator = '|'
INSERT INTO #temp (string)
VALUES
('1|2'),
('1|2|3'),
('1|2|8'),
('10'),
('11'),
('11|12')
-- 1. XML
SELECT p.value('(./s)[1]', 'VARCHAR(500)')
FROM (
SELECT field = CAST('<r><s>' + REPLACE(t.string, #Separator, '</s></r><r><s>') + '</s></r>' AS XML)
FROM #temp t
) d
CROSS APPLY field.nodes('/r') t(p)
-- 2. CTE
;WITH a AS
(
SELECT
start_pos = 1
, end_pos = CHARINDEX(#Separator, t.string)
, t.string
FROM #temp t
UNION ALL
SELECT
end_pos + 1
, CHARINDEX(#Separator, string, end_pos + 1)
, string
FROM a
WHERE end_pos > 0
)
SELECT d.name
FROM (
SELECT
name = SUBSTRING(
string
, start_pos
, ABS(end_pos - start_pos)
)
FROM a
) d
WHERE d.name != ''
Try this :
create table course (courses varchar(100))
insert into course values('1|2')
insert into course values('1|2|3')
insert into course values('1|2|8')
insert into course values('10')
insert into course values('11')
insert into course values('11|12')
Declare #col varchar(200)
SELECT
#col=(
SELECT DISTINCT c.courses + '|'
FROM course c
FOR XML PATH('')
);
select * from course
;with demo as(
select cast(substring(#col,1,charindex('|',#col,1)-1) AS INT) cou,charindex('|',#col,1) pos
union all
select cast(substring(#col,pos+1,charindex('|',#col,pos+1)-pos-1)AS INT) cou,charindex('|',#col,pos+1) pos
from demo where pos<LEN(#col))
select distinct cou from demo
Could not manage without recursion :( Something like this could do the trich?
WITH splitNum(num, r)
AS
(
SELECT
SUBSTRING(<field>,1, CHARINDEX('|', <field>)-1) num,
SUBSTRING(<field>,CHARINDEX('|', <field>)+1, len(<field>)) r
FROM <yourtable> as a
UNION ALL
SELECT
SUBSTRING(r,1, CHARINDEX('|', r)-1) num,
SUBSTRING(r,CHARINDEX('|', r)+1, len(r)) r
FROM <yourtable> b
WHERE CHARINDEX('|', r) > 0
inner join splitNum as c on <whatevertheprimarykeyis>
)
SELECT distinct num FROM splitNum
Didn't make it run, but it should do the trick, just replace the and with the correct info
One way would be to use a recursive CTE:
with cte as
(select cast(case charindex('|',courses) when 0 then courses
else left(courses,charindex('|',courses)-1) end as int) course,
case charindex('|',courses) when 0 then ''
else right(courses,len(courses)-charindex('|',courses)) end courses
from courses
union all
select cast(case charindex('|',courses) when 0 then courses
else left(courses,charindex('|',courses)-1) end as int) course,
case charindex('|',courses) when 0 then ''
else right(courses,len(courses)-charindex('|',courses)) end courses
from cte
where len(courses)>0)
select distinct course from cte
SQLFiddle here.