Finding the position of a character from a string - sql

I tried finding the position of a string with charindex function but when there are two letters in the same string, I'm not able to get the code.
My question is:
Find the postion of S from 'SyedSohail'
The output should be
Position
1
5
Could you please help me with the above
I'm writing the code in Tsql

You can paste the following query right inside your SQL Editor:
DECLARE #test AS varchar(100);
DECLARE #ctr as int;
DECLARE #testlength as int;
DECLARE #charToTest as char(1);
DECLARE #positions as varchar(MAX);
DECLARE #findChar as char(1);
SET #test = 'Syed Summers';
SET #ctr = 1;
SET #testlength = LEN(#test) + 1;
SET #positions = '';
SET #findChar = 'S';
WHILE #ctr < (#testlength)
BEGIN
SET #charToTest = SUBSTRING(#test, #ctr, 1)
IF (UPPER(#charToTest) = #findChar)
BEGIN
SET #positions = #positions + ',' + CONVERT(VARCHAR(10), #ctr)
END
SET #ctr = #ctr + 1
END
SELECT RIGHT(#positions, (LEN(#positions) - 1));
Explanation:
#test - will contain the string which you want to search
#ctr - counter to iterate through all the characters in your string #test
#testLength - length of your #test string
#findChar - the character which you want to count the instance in the string
#charToTest - the string being tested whether it equals your #findChar value
#positions - will be an enumerated string of the position where the #findChar was found in the #test string.
Note that I provided a simplistic answer to help guide the understanding for the person who posted the question.

Try to create a function like below,
CREATE FUNCTION dbo.FindPatternLocation
(
#string NVARCHAR(MAX),
#term NVARCHAR(255)
)
RETURNS TABLE
AS
RETURN
(
SELECT pos = Number - LEN(#term)
FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(#string, Number,
CHARINDEX(#term, #string + #term, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number > 1 AND Number <= CONVERT(INT, LEN(#string)+1)
AND SUBSTRING(#term + #string, Number, LEN(#term)) = #term
) AS y);
Usage:
DECLARE #str VARCHAR(MAX) = 'SyedSohail'
SELECT pos FROM dbo.FindPatternLocation(#str, 's');
Source

If I understand correctly, you could use an rCTE to iterate through the string:
DECLARE #YourString varchar(12) = 'SyedSohail';
DECLARE #Char char(1) = 'S';
WITH rCTE AS(
SELECT V.YourString,
CI.I
FROM (VALUES(#YourString))V(YourString)
CROSS APPLY (VALUES(CHARINDEX(#Char,V.YourString))) CI(I)
WHERE CI.I > 0
UNION ALL
SELECT r.YourString,
CI.I
FROM rCTe r
CROSS APPLY (VALUES(CHARINDEX(#Char,r.YourString,r.I+1))) CI(I)
WHERE CI.I > 0)
SELECT R.I
FROM rCTE r;
However, I suspect (know for a fact), a Tally would be quicker with a dataset and/or longer values:
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP(SELECT MAX(LEN(V.YourString)) FROM (VALUES(#YourString))V(YourString)) --Should be from your tablt
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2, N N3, N N4) --10,000 rows
SELECT T.I
FROM Tally T
CROSS JOIN (VALUES(#YourString))V(YourString)
WHERE SUBSTRING(V.YourString,T.I,1) = #Char;

Related

Need help in string conversion based on multiple rules

I have a func_name column. I have set of rules to be forced on this func_name column to create a func_short_name.
The desired logic for func_short_name is:
use whatever is to the right of '>'; preceded by whatever comes after each
'+' or '#' from FUNC_NAME field
Each time '+' or '#' appears, append it to the func_short_name
Example:
func_name: toolbox/matlab/cefclient/+matlab/+internal/getOpenPort.p>getOpenPort
func_short_name : matlab.internal.getOpenPort
The above example appends 'matlab' with 'internal' since they are followed by a '+' and the 'getOpenPort' since it is to the right of '>'
How do I take in account for each occurence of '+' or 'a'. Could someone help me construct a SQL or stored proc? Thanks!
I have tried implementing the rules separately but I am unable to do a recursive append of each occurence of '+' or '#'
select substring(FUNC_NAME,charindex('a',FUNC_NAME)+1,100)
FROM table
select FUNC_NAME,
charindex('#',FUNC_NAME)+1,
charindex('/',FUNC_NAME)-1
from table
select concat(substring(FUNC_NAME,charindex('#',FUNC_NAME)+1,charindex('/',FUNC_NAME)-1),'.',substring(FUNC_NAME,charindex('>',FUNC_NAME)+1,100))
FROM table
func_name: toolbox/matlab/cefclient/+matlab/+internal/getOpenPort.p>getOpenPort
func_short_name : matlab.internal.getOpenPort
Another example:
func name:
toolbox/symbolic/symbolic/#sym/#aem/diag.m>diag
func_short_name:
sym.aem.diag
This should do it regardless of the version of SQL Server.
DECLARE #func_name VARCHAR(200);
DECLARE #func_short_name VARCHAR(100) = '';
DECLARE #i INT = 1;
DECLARE #func_name_length INT;
DECLARE #start_position INT = 0;
DECLARE #end_position INT = 0;
DECLARE #gt_position INT = 0;
SET #func_name = 'toolbox/matlab/cefclient/+matlab/+internal/getOpenPort.p>getOpenPort';
--SET #func_name = 'toolbox/symbolic/symbolic/#sym/#aem/diag.m>diag';
SET #i = 1;
SET #func_name_length = LEN(#func_name);
-- loop through string character by character
WHILE #i <= #func_name_length
BEGIN
IF (SUBSTRING(#func_name, #i, 1)) IN ('+', '#')
BEGIN
SET #start_position = #i;
END;
-- ending character found after starting character has been found
IF (SUBSTRING(#func_name, #i, 1)) = '/'
AND #start_position > 0
BEGIN
SET #end_position = #i;
SET #func_short_name = #func_short_name
+ SUBSTRING(#func_name, #start_position + 1, (#end_position - 1) - #start_position)
+ '.';
SET #start_position = 0;
END;
SET #i += 1;
END;
-- find greater than character
SET #gt_position = CHARINDEX('>', #func_name);
SET #func_short_name = #func_short_name + SUBSTRING(#func_name, #gt_position + 1, #func_name_length - #gt_position);
SELECT #func_name AS [FUNC NAME], #func_short_name AS [FUNC SHORT NAME];
Only if it's SQL Server 2017+
Initialization:
DECLARE #Table TABLE (Func_Name NVARCHAR(MAX));
INSERT INTO #Table (Func_Name)VALUES
('toolbox/matlab/cefclient/+matlab/+internal/getOpenPort.p>getOpenPort')
,('toolbox/symbolic/symbolic/#sym/#aem/diag.m>diag')
;
The Code:
SELECT STRING_AGG(REPLACE(REPLACE(a.[value],'+',''),'#',''),'.')
WITHIN GROUP (ORDER BY rn DESC) AS [Result]
FROM (
SELECT b.ID,j.[value],ROW_NUMBER()OVER(PARTITION BY b.ID ORDER BY j.[Key] DESC) AS [rn]
FROM (
SELECT '["' + REPLACE(REPLACE(t.Func_Name,'/','","'),'>','","') + '"]' AS [value]
,ROW_NUMBER()OVER(ORDER BY (SELECT 1)) AS [ID]
FROM #Table t
) b
CROSS APPLY OPENJSON(b.[value]) j
) a
WHERE (a.[value] LIKE '[+]%' OR a.[value] LIKE '[#]%' OR a.rn = 1 /*last piece*/)
GROUP BY a.ID
;
This solution based on a recursive common table expression works with any SQL server version as of 2005 but is not really strict when it comes to the separating characters. The pattern '%[#+>]%' looks for any one of the characters: #, + or >. It does return the desired result though: matlab.internal.getOpenPort
declare #f varchar(255);
set #f='toolbox/matlab/cefclient/+matlab/+internal/getOpenPort.p>getOpenPort'+'/';
With rcte as (
select 0 n,#f str, patindex('%[#+>]%',#f) p union all
select p, substring(str, p+1,255),
patindex('%[#+>]%',substring(str, p+1,255))
from rcte where p>0
)
SELECT STUFF((SELECT '.' +LEFT(str,charindex('/',str)-1)
FROM rcte
WHERE n>0
FOR XML PATH('')), 1, 1, '') short
#f is the variable containing the long function name (with an added / at the end) that needs to be converted.
See here for a demo: https://rextester.com/MFVO10768

Display Sring in vertical manner [duplicate]

This question already has answers here:
T-SQL Split Word into characters
(7 answers)
Closed 4 years ago.
I have String like as follows
Declare #string ='welcome'
and i want output like this
w
e
l
c
o
m
e
You can use a tally table for this, usually faster than looping, but you would have to test for yourself:
declare #s varchar(200)
set #s = 'Welcome'
;with t as (
select v
from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t(v)
),
n as (
select row_number() over(order by (select null)) rn
from t t1 cross join t t2
)
select substring(#s, rn, 1)
from n
where rn <= len(#s)
DECLARE #string VARCHAR(256) = 'welcome'
DECLARE #cnt INT = 0;
WHILE #cnt < len(#string)
BEGIN
SET #cnt = #cnt + 1;
PRINT SUBSTRING ( #string ,#cnt , 1 )
END;
In essence you loop through the length of the string.
You print the character at the location of the index of the loop and print that.
You ca use recursive CTE.
Declare #string varchar(10) ='welcome'
;with cte as
(
select 1 as i,substring(#string,1,1) as single_char
union all
select i+1 as i,convert(varchar(1),substring(#string,i+1,i+1)) as single_char from cte where len(convert(varchar(1),substring(#string,i+1,i+1)))=1
)
select single_char From cte

Count numeric chars in string

Using tsql I want to count a numeric chars in string. For example i've got 'kick0my234ass' string and i wanna count how many (4 in that example) numbers are in that string. I can't use regex, just plain tslq.
You COULD do this I suppose:
declare #c varchar(30)
set #c = 'kick0my234ass'
select #c, len(replace(#c,' ','')) - len(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(#c,'0',''),'1',''),'2',''),'3',''),'4',''),'5',''),'6',''),'7',''),'8',''),'9',''),' ',''))
You'll first have to split the character string in its individual characters, evaluate which are numeric, and finally count those that are. This will do the trick:
DECLARE #test TABLE (Example NVARCHAR(255))
INSERT #test
VALUES ('kick0my234ass')
SELECT COUNT(1)
FROM #test AS T
INNER JOIN master..spt_values v
ON v.type = 'P'
AND v.number < len(T.Example)
WHERE SUBSTRING(T.Example, v.number + 1, 1) LIKE '[0-9]'
You could try this solution with regular expressions (if you'd allow them):
it uses recursive CTE, at every recursive step, one digit is removed from given string and the condition is to stop, when there are no digits in string. The rows are also numbered with consecutive ids, so the last id is the amount of removed digits from string.
declare #str varchar(100) = 'kick0my123ass';
with cte as (
select 1 [id], stuff(#str,PATINDEX('%[0-9]%', #str),1,'') [col]
union all
select [id] + 1, stuff([col],PATINDEX('%[0-9]%', [col]),1,'') from cte
where col like '%[0-9]%'
)
--this will give you number of digits in string
select top 1 id from cte order by id desc
Use a WHILE loop to each each character is a numeric or not.
Query
declare #text as varchar(max) = 'kick0my234ass';
declare #len as int;
select #len = len(#text);
if(#len > 0)
begin
declare #i as int = 1;
declare #count as int = 0;
while(#i <= #len)
begin
if(substring(#text, #i, 1) like '[0-9]')
set #count += 1;
set #i += 1;
end
print 'Count of Numerics in ' + #text + ' : ' + cast(#count as varchar(100));
end
else
print 'Empty string';
If simplicity & performance are important I suggest a purely set-based solution. Grab a copy of DigitsOnlyEE which will remove all non-numeric characters. Then use LEN against the output.
DECLARE #string varchar(100) = '123xxx45ff678';
SELECT string = #string, digitsOnly, DigitCount = LEN(digitsOnly)
FROM dbo.DigitsOnlyEE(#string);
Results
string digitsOnly DigitCount
------------------ ----------- ------------
123xxx45ff678 12345678 8
using a Tally Table created by an rCTE:
CREATE TABLE #Sample (S varchar(100));
INSERT INTO #Sample
VALUES ('kick0my234 ass');
GO
WITH Tally AS(
SELECT 1 AS N
UNION ALL
SELECT N + 1
FROM Tally
WHERE N + 1 <= 100)
SELECT S.S, SUM(CASE WHEN SUBSTRING(S,T.N, 1) LIKE '[0-9]' THEN 1 ELSE 0 END) AS Numbers
FROM #Sample S
JOIN Tally T ON LEN(S.S) >= T.N
GROUP BY S.S;
For future reference, also post your owns attempts please. We aren't here (really) to do your work for you.

How to convert Hex to String in Sql server

Here is my hex input
0x3c0x3c0x5bIMG0x5d0x5bSIZE0x5dHALF0x5b0x2fSIZE0x5d0x5bID0x5d540x5b0x2fID0x5d0x5b0x2fIMG0x5d0x3e0x3e
Expected output is :
<<[IMG][SIZE]HALF[/SIZE][ID]54[/ID][/IMG]>>
Your string is mixing hex and char data, so you need to parse it with a code. A tricky part is converting 0xCC substring to a char it represents. First pretend it's binary and then cast to char. Using recursion to iterate over all 0xCC substrings
declare #imp nvarchar(max) = '0x3c0x3c0x5bIMG0x5d0x5bSIZE0x5dHALF0x5b0x2fSIZE0x5d0x5bID0x5d540x5b0x2fID0x5d0x5b0x2fIMG0x5d0x3e0x3e';
with cte as (
select replace(col, val, cast(convert(binary(2), val, 1) as char(1))) as col
from (
-- sample table
select #imp as col
) tbl
cross apply (select patindex('%0x__%',tbl.col) pos) p
cross apply (select substring(col,pos,4) val) v
union all
select replace(col, val, cast(convert(binary(2), val, 1) as char(1))) as col
from cte
cross apply (select patindex('%0x__%',col) pos) p
cross apply (select substring(col,pos,4) val) v
where pos > 0
)
select *
from cte
where patindex('%0x__%',col) = 0;
Returns
col
<<[IMG][SIZE]HALF[/SIZE][ID]54[/ID][/IMG]>>
If it's for only a small set of ascii codes that always need replacement in a variable, then you can also replace them like this:
declare #string varchar(max) = '0x3c0x3c0x5bIMG0x5d0x5bSIZE0x5dHALF0x5b0x2fSIZE0x5d0x5bID0x5d540x5b0x2fID0x5d0x5b0x2fIMG0x5d0x3e0x3e';
select #string = replace(#string,hex,chr)
from (values
('0x3c','<'),
('0x3e','>'),
('0x5b','['),
('0x5d',']'),
('0x2f','/')
) hexes(hex,chr);
select #string as string;
Returns:
string
------
<<[IMG][SIZE]HALF[/SIZE][ID]54[/ID][/IMG]>>
If there are more characters, or hardcoding is frowned upon?
Then looping a replacement will also get that result:
declare #string varchar(max) = '0x3c0x3c0x5bIMG0x5d0x5bSIZE0x5dHALF0x5b0x2fSIZE0x5d0x5bID0x5d540x5b0x2fID0x5d0x5b0x2fIMG0x5d0x3e0x3e';
declare #loopcount int = 0;
declare #hex char(4);
while (patindex('%0x[0-9][a-f0-9]%',#string)>0
and #loopcount < 128) -- just safety measure to avoid infinit loop
begin
set #hex = substring(#string,patindex('%0x[0-9][a-f0-9]%',#string),4);
set #string = replace(#string, #hex, convert(char(1),convert(binary(2), #hex, 1)));
set #loopcount = #loopcount + 1;
end;
select #string as string;
If you would wrap it in a UDF then you can even use it in a query.

Complicated SQL while loop

I am trying to create a while loop in SQL and it seems kind of complex. Here's what I need it to achieve:
Iterate through a single VARCHAR string (ex. '123')
If the nth character is in an even position in the string (ex. 2nd, 4th .... letter in the string), it must be added(SUM) to a base variable (Let's assume #z)
If the nth character is in an odd position in the string (ex. 1st, 3rd .... letter in the string), it must be multiplied by 2. If this newly generated value (Let's assume #y) is less than 10, it must be added(SUM) to the base variable (Still the same assumed #z). If #y is greater than 10, we need to subtract 9 from #y before adding(SUM) it to #z
After iterating through the entire string, this should return a numeric value generated by the above process.
Here is what I've done so far, but I'm stuck now (Needless to say, this code does not work yet, but I think I'm heading in the right direction):
DECLARE #x varchar(20) = '12345'
DECLARE #p int = len(#x)
WHILE #p > 0
SELECT #x =
stuff(#x, #p, 1,
case when CONVERT(INT,substring(#x, #p, 1)) % 2 = 0 then CONVERT(INT, #x) + CONVERT(INT,substring(#x, #p, 1))
end), #p -= 1
RETURN #x;
PS. The input will always be 100% numeric values, but it is formatted as VARCHAR when I recieve it.
UPDATE
The expected result for the sample string is 15
You can do this without using a loop. Here is a solution using Tally Table:
DECLARE #x VARCHAR(20) = '12345'
DECLARE #z INT = 0 -- base value
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
CteTally(N) AS(
SELECT TOP(LEN(#x)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E4
),
CteChars(N, num) AS(
SELECT
t.N, CAST(SUBSTRING(#x, t.N, 1) AS INT)
FROM CteTally t
WHERE t.N <= LEN(#x)
)
SELECT
SUM(
CASE
WHEN N % 2 = 0 THEN num
WHEN num * 2 < 10 THEN num * 2
ELSE (num * 2) - 9
END
) + #z
FROM CteChars
The CTEs up to CteTally generates a list of number from 1 to LEN(#x). CteChars breaks #x character by character into separate rows. Then the final SELECT does a SUM based on the conditions.
OUTPUT : 15
Check below if it helps you
DECLARE #x varchar(20) = '12345'
DECLARE #p int = 1
DECLARE #result bigint=0;
DECLARE #tempval int =0;
WHILE #p <= len(#x)
BEGIN
SET #tempval = CONVERT(INT,substring(#x, #p, 1));
if(#p%2 = 1)
BEGIN
SET #tempval = #tempval * 2;
IF(#tempval >= 10) SET #tempval = #tempval - 9;
END
SET #result = #result + #tempval;
SET #p = #p + 1;
END;
PRINT #result;--This is the result
RETURN #x;
DECLARE #x INT = 12345
DECLARE #p int = len(#x)
DECLARE #z INT =0
PRINT #p%2
SET #x=#x/10
PRINT #x
WHILE #p > 0
BEGIN
IF(#p%2 = 0)
BEGIN
SET #z=#z+#x%10
SET #p=#p-1
SET #x=#x/10
END
ELSE
BEGIN
SET #z=#z+(2*(#x%10))
SET #p=#p-1
SET #x=#x/10
IF(#x>=10)
BEGIN
SET #x=(#x/10+#x%10)
END
END
END
SELECT #z
The while loop does not seem necessary here.
This can be achieved with a CTE that will split the string and a case statement:
DECLARE #x varchar(20) = '12345';
with split(id, v) as (
select 0, cast(0 as tinyint)
union all
select id+1, cast(SUBSTRING(#x, id+1, 1) as tinyint)
From split
Where id+1 <= len(#x)
)
Select Result = SUM(
Case When id % 2 = 0 then v
When v < 5 then v*2
Else (v*2)-9
End
)
From split
output = 15