SQL obtain values between second and third forward slash - sql-server

In SQL Server, I am trying to obtain the values between the second and third forward slash (/) character. The length of the numbers can vary so substring(column, 8, 10) wouldn't work.
123/123/123456789/12
What I am trying to get in the current example is: 123456789

With 4 parts to your data as shown you can abuse the parsename function:
declare #string varchar(50) = '123/123/123456789/12';
select ParseName(Replace(#string,'/','.'),2);

Please try the following solution based on tokenization.
This method is generic regardless how many tokens are in place.
It will work starting from SQL Server 2012 onwards.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, tokens VARCHAR(100));
INSERT #tbl (tokens) VALUES
('123/123/123456789/12'),
('123/123/9876543210/12');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = '/';
SELECT t.*
, ThirdToken = c.value('(/root/r[position() eq 3]/text())[1]', 'VARCHAR(100)')
FROM #tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(tokens, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c);
Output
ID
tokens
ThirdToken
1
123/123/123456789/12
123456789
2
123/123/9876543210/12
9876543210

Since you are on 2016, consider the following
Example
Declare #YourTable table (ID int,SomeCol varchar(50))
Insert Into #YourTable values
(1,'123/123/123456789/12')
Select A.ID
,Pos3 = JSON_VALUE(JS,'$[2]')
From #YourTable A
Cross Apply (values ('["'+replace(SomeCol,'/','","')+'"]') ) B(JS)
Results
ID Pos3
1 123456789
If you only need the single value, there is no need for the CROSS APPLY
Select A.ID
,Pos3 = JSON_VALUE('["'+replace(SomeCol,'/','","')+'"]','$[2]')
From #YourTable A

Related

How can I get all of them after the second "_" separation in a string data in mssql?

I have data like this in a string column in a table: [Product] -> "LA100_Runner_35C924_D". From this data I want to get the data after the second _, so I want to get 35C924_D.
How do I do that?
I tried WHERE [Product] LIKE '%_%' escape '' but I couldn't quite get it working. I can't think of what I want with the LIKE operation.
One option is to apply the combination of SUBSTRING + PATINDEX twice on the same strings, while splitting on the first underscore symbol as follows:
WITH cte AS (
SELECT SUBSTRING(string, PATINDEX('%[_]%', string)+1, LEN(string)) AS string
FROM tab
)
SELECT SUBSTRING(string, PATINDEX('%[_]%', string)+1, LEN(string))
FROM cte
Check the demo here.
You can also do the same using RIGHT + PATINDEX in a very similar fashion:
WITH cte AS (
SELECT RIGHT(string, LEN(string) - PATINDEX('%[_]%', string)) AS string
FROM tab
)
SELECT RIGHT(string, LEN(string) - PATINDEX('%[_]%', string)) AS string
FROM cte
Check the demo here.
Here is another method that is using tokenization via XML and XQuery.
The XPath predicate [position() ge 3] is asking to get all tokens starting from the 3rd.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, Product VARCHAR(100));
INSERT #tbl (Product) VALUES
('LA100_Runner_35C924_D'),
('MA200_Runner_77C924_D');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = '_';
SELECT t.*
, Result = REPLACE(c.query('data(/root/r[position() ge 3])')
.value('text()[1]', 'VARCHAR(100)'), SPACE(1), #separator)
FROM #tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(Product, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c);
Output
ID
Product
Result
1
LA100_Runner_35C924_D
35C924_D
2
MA200_Runner_77C924_D
77C924_D

Substring is slow with while loop in SQL Server

One of my table column stores ~650,000 characters (each value of the column contains entire table). I know its bad design however, Client will not be able to change it.
I am tasked to convert the column into multiple columns.
I chose to use dbo.DelimitedSplit8K function
Unfortunately, it can only handle 8k characters at max.
So I decided to split the column into 81 8k batches using while loop and store the same in a variable table (temp or normal table made no improvement)
DECLARE #tab1 table ( serialnumber int, etext nvarchar(1000))
declare #scriptquan int = (select MAX(len (errortext)/8000) from mytable)
DECLARE #Counter INT
DECLARE #A bigint = 1
DECLARE #B bigint = 8000
SET #Counter=1
WHILE ( #Counter <= #scriptquan + 1)
BEGIN
insert into #tab1 select ItemNumber, Item from dbo.mytable cross apply dbo.DelimitedSplit8K(substring(errortext, #A, #B), CHAR(13)+CHAR(10))
SET #A = #A + 8000
SET #B = #B + 8000
SET #Counter = #Counter + 1
END
This followed by using below code
declare #tab2 table (Item nvarchar(max),itemnumber int, Colseq varchar(10)) -- declare table variable
;with cte as (
select [etext] ,ItemNumber, Item from #tab1 -- insert table name
cross apply dbo.DelimitedSplit8K(etext,' ')) -- insert table columns name that contains text
insert into #tab2 Select Item,itemnumber, 'a'+ cast (ItemNumber as varchar) colseq
from cte -- insert values to table variable
;WITH Tbl(item, colseq) AS(
select item, colseq from #tab2
),
CteRn AS(
SELECT item, colseq,
Rn = ROW_NUMBER() OVER(PARTITION BY colseq ORDER BY colseq)
FROM Tbl
)
SELECT
a1 Time,a2 Number,a3 Type,a4 Remarks
FROM CteRn r
PIVOT(
MAX(item)
FOR colseq IN(a1,a2,a3,a4)
)p
where a3 = 'error'
gives the desired output. However, just the loop takes 15 minutes to complete and overall query completes by 27 minutes. Is there any way I can make it faster? Total row count in my table is 2. So I don't think Index can help.
Client uses Azure SQL Database so I can't choose PowerShell or Python to accomplish this either.
Please let me know if more information is needed. I tried my best to mention everything I could.

Extract a substring in a column in the 5th place - SQL Server

How can I extract a string that appears after 4 ';' in a column in a table using a select query?
For example - if U have the following value in the column:
1234;0000;567;655;0541234567;777;777
I would like to fetch the value in the 5th place; in this example it's
0541234567
Thanks!
You can use a bit of JSON
Example
Declare #YourTable table (SomeCol varchar(100))
Insert into #YourTable values ('1234;0000;567;655;0541234567;777;777')
Select A.SomeCol
,Val5 = JSON_VALUE(S,'$[4]')
from #YourTable A
Cross Apply ( values ( '["'+replace(SomeCol,';','","')+'"]' ) ) B(S)
Returns
SomeCol Val5
1234;0000;567;655;0541234567;777;777 0541234567
EDIT -- CROSS APPLY not necessary if only after ONE value
Select A.SomeCol
,Val5 = JSON_VALUE('["'+replace(SomeCol,';','","')+'"]','$[4]')
from #YourTable A

How to obtain a string equals to each first letter of words in a sentence IN SQL

I have words separated with a space in a column like
apple orange banana I need the first letters as the result will be something like :
aob
First, split your text. I recommend some function:
CREATE FUNCTION Split(#text nvarchar(MAX),#separator nvarchar(MAX))
RETURNS TABLE AS RETURN
WITH Indexed AS
(
SELECT 1 N, CAST(1 AS bigint) S, CHARINDEX(#separator, #text, 1) E WHERE #text IS NOT NULL
UNION ALL
SELECT N+1, E+DATALENGTH(#separator)/2, CHARINDEX(#separator, #text, E+DATALENGTH(#separator)/2) FROM Indexed WHERE E>S
), Token AS
(
SELECT N, SUBSTRING(#text, S, CASE WHEN E=0 THEN DATALENGTH(#text)/2 ELSE E-S END) T FROM Indexed
)
SELECT * FROM Token
If you are using SQL 2016 and greater, use STRING_SPLIT instead.
Then, you can select first character of every word and join. See following example:
DECLARE #Sample TABLE (T nvarchar(100));
INSERT #Sample VALUES (N'apple orange banana'),(N'dog cat');
SELECT (SELECT SUBSTRING(T,1,1) [*] FROM Split(T,N' ') FOR XML PATH(''))
FROM #Sample
Result:
(no column name)
------
aob
dc
If you declare REGEX function in your DB (not native with SQL SERVER).
Using regexp_replace
select regexp_replace('apple orange banana','(\\w)(\\w* ?)','$1')
return
aob
I think the shortest will be this:
Here a mockup-table with two rows to simulate your issue:
DECLARE #mockup TABLE(ID INT IDENTITY,YourWords VARCHAR(100));
INSERT INTO #mockup VALUES('apple orange banana'),('one two three');
--That is the query:
SELECT m.ID
,REPLACE(Casted.query('for $w in /x return substring($w,1,1)').value('.','varchar(max)'),' ','')
FROM #mockup m
CROSS APPLY(SELECT CAST('<x>' + REPLACE(m.YourWords,' ','</x><x>') + '</x>' AS XML)) A(Casted);
The idea behind:
The string apple orange banana is tranformed to <x>apple</x><x>orange</x><x>banana</x> and is casted to XML, which allows to use XQuery.
Now we use .query() on the XML with a simple FLWOR statement. It tells the engine: run through each value of /x and return just the first letter. Calling value() on this with a . as XPath will return the values in one.
We need a final REPLACE() to get rid of blanks, which would otherwise appear as a o b instead of aob.
Just another option using a little XML. You could also use ParseName() provided you trap any periods in the string.
Example
Declare #YourTable table(ID int,LastName varchar(50),FirstName varchar(50))
Insert Into #YourTable values
(1,'Waston','Mary Jane')
Select A.ID
,NewValue = upper(
concat(
xmlData.value('/x[1]','varchar(1)')
,xmlData.value('/x[2]','varchar(1)')
,xmlData.value('/x[3]','varchar(1)')
,xmlData.value('/x[4]','varchar(1)')
,'.'
,LastName
)
)
From #YourTable A
Cross Apply ( values (convert(xml,'<x>' + replace(A.FirstName,' ','</x><x>')+'</x>' )) ) B(xmlData)
Returns
ID NewValue
1 MJ.WASTON
EDIT - Added ParseName() option
Select A.ID
,NewValue = upper(concat(Pos1,Pos2,Pos3,Pos4,'.',LastName))
From #YourTable A
Cross Apply (
Select Pos1 = left(parsename(tStr,4),1)
,Pos2 = left(parsename(tStr,3),1)
,Pos3 = left(parsename(tStr,2),1)
,Pos4 = left(parsename(tStr,1),1)
From ( values(replace(FirstName,' ','.'))) B1(tStr)
) B

SQL Server Conversion failed varchar to int

I have a table (no.1) which has 10 columns. One of them clm01 is integer and not allowed with null values.
There is a second table (no.2) which has many columns. One of them is string type clm02. An example of this column data is 1,2,3.
I'd like to make a query like:
select *
from table1 t1, table2 t2
where t1.clm01 not in (t2.clm2)
For example in table1 I have 5 records with values in clm01 1,2,3,4,5 and in table2 I've got 1 record with value in clm02 = 1,2,3
So I would like with the query to return only the record with the value 4 and 5 in the clm01.
Instead I get:
Conversion failed when converting the varchar value '1,2,3' to data type int
Any ideas?
Use STRING_SPLIT() function to split the comma separated values, if you are using SQL Server 2016.
SELECT *
FROM table1 t1
WHERE t1.clm1 NOT IN (SELECT Value FROM table2 t2
CROSS APPLY STRING_SPLIT(t2.clm2,','))
If you are using any lower versions of SQL server write a UDF to split string and use the function in CROSS APPLY clause.
CREATE FUNCTION [dbo].[SplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(Value NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (Value)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
I decided to give you a couple of options but this really is a duplicate question I see pretty often.
There are two main ways of going about the problem.
1) Use LIKE to and compare the strings but you actually have to build strings a little oddly to do it:
SELECT *
FROM
#Table1 t1
WHERE
NOT EXISTS (SELECT *
FROM #Table2 t2
WHERE ',' + t2.clm02 + ',' LIKE '%,' + CAST(t1.clm01 AS VARCHAR(15)) + ',%')
What you see is ,1,2,3, is like %,clm01value,% you must add the delimiter to the strings for this to work properly and you have to cast/convert clm01 to a char datatype. There are drawbacks to this solution but if your data sets are straight forward it could work for you.
2) Split the comma delimited string to rows and then use a left join, not exists, or not in. here is a method to convert your csv to xml and then split
;WITH cteClm02Split AS (
SELECT
clm02
FROM
(SELECT
CAST('<X>' + REPLACE(clm02,',','</X><X>') + '</X>' AS XML) as xclm02
FROM
#Table2) t
CROSS APPLY (SELECT t.n.value('.','INT') clm02
FROM
t.xclm02.nodes('X') as t(n)) ca
)
SELECT t1.*
FROM
#Table1 t1
LEFT JOIN cteClm02Split t2
ON t1.clm01 = t2.clm02
WHERE
t2.clm02 IS NULL
OR use NOT EXISTS with same cte
SELECT t1.*
FROM
#Table1 t1
WHERE
NOT EXISTS (SELECT * FROM cteClm02Split t2 WHERE t1.clm01 = t2.clm02)
There are dozens of other ways to split delimited strings and you can choose whatever way works for you.
Note: I am not showing IN/NOT IN as an answer because I don't recommend the use of it. If you do use it make sure that you are never comparing a NULL in the select etc. Here is another good post concerning performance etc. NOT IN vs NOT EXISTS
here are the table variables that were used:
DECLARE #Table1 AS TABLE (clm01 INT)
DECLARE #Table2 AS TABLE (clm02 VARCHAR(15))
INSERT INTO #Table1 VALUES (1),(2),(3),(4),(5)
INSERT INTO #Table2 VALUES ('1,2,3')

Resources