Replace strings between two characters using T-SQL - sql

I need to update a string to amend any aliases - which can be 'H1.', 'H2.', 'H3.'... etc - to all be 'S.' and am struggling to work out the logic.
For example I have this:
'H1.HUB_CUST_ID, H2.HUB_SALE_ID, H3.HUB_LOC_ID'
But I want this:
'S.HUB_CUST_ID, S.HUB_SALE_ID, S.HUB_LOC_ID'
If you could use wildcards in REPLACE, I'd do something like this REPLACE(#string, 'H%.H', 'S.H').
Theoretically, there is no limit to how many H# aliases there could be. In practice there will almost definitely be less than 10.
Is there a better way than a nested replace of H1 - H10 separately, which both looks messy in a script and carries a small risk if more tables are joined in future?

SQL Server doesn't support pattern replacement. You are better off using a different language, that does support pattern/REGEX replacement or implementing a CLR function.
That said, however, considering you said that the value would always be below 10 you could brute force it, but it's not "pretty".
SELECT REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(YourString,'H1.','S.'),'H2.','S.'),'H3.','S.'),'H4.','S.'),'H5.','S.'),'H6.','S.'),'H7.','S.'),'H8.','S.'),'H9.','S.')
FROM YourTable ...

You can convert your string to XML and then convert it into simple table:
DECLARE #txt nvarchar(max) = N'H1.HUB_CUST_ID, H2.HUB_SALE_ID, H3.HUB_LOC_ID',
#x xml
SELECT #x = '<a al="' + REPLACE(REPLACE(#txt,', ','</a><a al="'),'.','">')+ '</a>'
SELECT t.c.value('#al', 'nvarchar(max)') as alias_name,
t.c.value('.','nvarchar(max)') as col_name
FROM #x.nodes('/a') t(c)
Output:
alias_name col_name
H1 HUB_CUST_ID
H2 HUB_SALE_ID
H3 HUB_LOC_ID
You can put results into temp table, amend them using LIKE 'some basic pattern' and then build new string.

If you don't care about the result order, you can unaggregate and reaggregate:
select t.*, v.new_val
from t cross apply
(select string_agg(concat('S1', stuff(s.value, 1, charindex('.'), '') - 1, ',') within group (order by (select null) as newval
from string_split(t.col, ',') s
) s;
Note: This assumes that all values start with the prefix you want to replace -- as your sample data suggests. A case expression can be used if there are exceptions.
You can actually get the original ordering -- assuming no duplicates -- using charindex():
select t.*, v.new_val
from t cross apply
(select string_agg(concat('S1', stuff(s.value, 1, charindex('.'), '') - 1, ',')
within group (order by charindex(s.value, t.col)
) as newval
from string_split(t.col, ',') s
) s;

Related

Replace a recurring word and the character before it

I am using SQL Server trying to replace each recurring "[BACKSPACE]" in a string and the character that came before the word [BACKSPACE] to mimic what a backspace would do.
Here is my current string:
"This is a string that I would like to d[BACKSPACE]correct and see if I could make it %[BACKSPACE] cleaner by removing the word and $[BACKSPACE] character before the backspace."
Here is what I want it to say:
"This is a string that I would like to correct and see if I could make it cleaner by removing the word and character before the backspace."
Let me make this clearer. In the above example string, the $ and % signs were just used as examples of characters that would need to be removed since they are before the [BACKSPACE] word that I want to replace.
Here is another before example:
The dog likq[BACKSPACE]es it's owner
I want to edit it to read:
The dog likes it's owner
One last before example is:
I am frequesn[BACKSPACE][BACKSPACE]nlt[BACKSPACE][BACKSPACE]tly surprised
I want to edit it to read:
I am frequently surprised
Without a CLR function that provides Regex replacement the only way you'll be able to do this is with iteration in T-SQL. Note, however, that the below solution does not give you the results you ask for, but does the logic you ask. You state that you want to remove the string and the character before, but in 2 of your scenarios that isn't true. For the last 2 strings you remove ' %[BACKSPACE]' and ' $[BACKSPACE]' respectively (notice the leading whitespace).
This leading whitespace is left in this solution. I am not entertaining fixing that, as the real solution is don't use T-SQL for this, use something that supports Regex.
I also assume this string is coming from a column in a table, and said table has multiple rows (with a distinct value for the string on each).
Anyway, the solution:
WITH rCTE AS(
SELECT V.YourColumn,
STUFF(V.YourColumn,CHARINDEX('[BACKSPACE]',V.YourColumn)-1,LEN('[BACKSPACE]')+1,'') AS ReplacedColumn,
1 AS Iteration
FROM (VALUES('"This is a string that I would like to d[BACKSPACE]correct and see if I could make it %[BACKSPACE] cleaner by removing the word and $[BACKSPACE] character before the backspace."'))V(YourColumn)
UNION ALL
SELECT r.YourColumn,
STUFF(r.ReplacedColumn,CHARINDEX('[BACKSPACE]',r.ReplacedColumn)-1,LEN('[BACKSPACE]')+1,''),
r.Iteration + 1
FROM rCTE r
WHERE CHARINDEX('[BACKSPACE]',r.ReplacedColumn) > 0)
SELECT TOP (1) WITH TIES
r.YourColumn,
r.ReplacedColumn
FROM rCTE r
ORDER BY ROW_NUMBER() OVER (PARTITION BY r.YourColumn ORDER BY r.Iteration DESC);
dB<>fiddle
I've had a crack to see if I can get this to work using the traditional tally-table method without any recursion.
I think I have something that works - however the recursive cte version is definitely a cleaner solution and probably better performing, however throwing this in as just an alternative non-recursive way.
/* tally table for use below */
select top 1000 N=Identity(int, 1, 1)
into dbo.Digits
from master.dbo.syscolumns a cross join master.dbo.syscolumns
with w as (
select seq = Row_Number() over (order by t.N),
part = Replace(Substring(#string, t.N, CharIndex(Left(#delimiter,1), #string + #delimiter, t.N) - t.N),Stuff(#delimiter,1,1,''),'')
from Digits t
where t.N <= DataLength(#string)+1 and Substring(Left(#delimiter,1) + #string, t.N, 1) = Left(#delimiter,1)
),
p as (
select seq,Iif(Iif(Lead(part) over(order by seq)='' and lag(part) over(order by seq)='',1,0 )=1 ,'', Iif( seq<Max(seq) over() and part !='',Left(part,Len(part)-1),part)) part
from w
)
select result=(
select ''+ part
from p
where part!=''
order by seq
for xml path('')
)
Here's a simple RegEx pattern that should work:
/.\[BACKSPACE\]/g
EDIT
I have no way to test this right now on my chromebook, but this seems like it should work for T-SQL in the LIKE clause
LIKE '_\[BACKSPACE]' ESCAPE '\'

How to fetch only a part of string

I have a column which has inconsistent data. The column named ID and it can have values such as
0897546321
ABC,0876455321
ABC,XYZ,0873647773
ABC,
99756
test only
The SQL query should fetch only Ids which are of 10 digit in length, should begin with a 08 , should be not null and should not contain all characters. And for those values, which have both digits and characters such as ABC,XYZ,0873647773, it should only fetch the 0873647773 . In these kind of values, nothing is fixed, in place of ABC, XYZ , it can be anything and can be of any length.
The column Id is of varchar type.
My try: I tried the following query
select id
from table
where id is not null
and id not like '%[^0-9]%'
and id like '[08]%[0-9]'
and len(id)=10
I am still not sure how should I deal with values like ABC,XYZ,0873647773
P.S - I have no control over the database. I can't change its values.
SQL Server generally has poor support regular expressions, but in this case a judicious use of PATINDEX is viable:
SELECT SUBSTRING(id, PATINDEX('%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%', ',' + id + ','), 10) AS number
FROM yourTable
WHERE ',' + id + ',' LIKE '%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%';
Demo
If you normalise your data, and split the delimited data into parts, you can achieve this some what more easily:
SELECT SS.value
FROM dbo.YourTable YT
CROSS APPLY STRING_SPLIT(YT.YourColumn,',') SS
WHERE LEN(SS.value) = 10
AND SS.value NOT LIKE '%[^0-9]%';
If you're on an older version of SQL Server, you'll have to use an alternative String Splitter method (such as a XML splitter or user defined inline table-value function); there are plenty of examples on these already on Stack Overflow.
db<>fiddle

select and concatenate everything before and after a certain character

I've got a string like AAAA.BBB.CCCC.DDDD.01.A and I'm looking to manipulate this and end up with AAAA-BBB
I've achieved this by writing this debatable piece of code
declare #string varchar(100) = 'AAAA.BBB.CCCC.DDDD.01.A'
select replace(substring(#string,0,charindex('.',#string)) + substring(#string,charindex('.',#string,CHARINDEX('.',#string)),charindex('.',#string,CHARINDEX('.',#string)+1)-charindex('.',#string)),'.','-')
Is there any other way to achieve this which is more elegant and readable ?
I was looking at some string_split operations, but can't wrap my head around it.
If you are open to some JSON transformations, the following approach is an option. You need to transform the text into a valid JSON array (AAAA.BBB.CCCC.DDDD.01.A is transformed into ["AAAA","BBB","CCCC","DDDD","01","A"]) and get the required items from this array using JSON_VALUE():
Statement:
DECLARE #string varchar(100) = 'AAAA.BBB.CCCC.DDDD.01.A'
SET #string = CONCAT('["', REPLACE(#string, '.', '","'), '"]')
SELECT CONCAT(JSON_VALUE(#string, '$[0]'), '-', JSON_VALUE(#string, '$[1]'))
Result:
AAAA-BBB
Notes: With this approach you can easily access all parts from the input string by index (0-based).
I think this is a little cleaner:
declare #string varchar(100) = 'AAAA.BBB.CCCC.DDDD.01.A'
select
replace( -- replace '.' with '-' (A)
substring(#string, 1 -- in the substring of #string starting at 1
,charindex('.', #string -- and going through 1 before the index of '.'(B)
,charindex('.',#string)+1) -- that is after the first index of the first '.'
-1) -- (B)
,'.','-') -- (A)
Depending on what is in your string you might be able to abuse PARSENAME into doing it. Intended for breaking up names like adventureworks.dbo.mytable.mycolumn it works like this:
DECLARE #x as VARCHAR(100) = 'aaaa.bbb.cccc.ddddd'
SELECT CONCAT( PARSENAME(#x,4), '-', PARSENAME(#x,3) )
You could also look at a mix of STUFF to delete the first '.' and replace with '-' then LEFT the result by the index of the next '.' but it's unlikely to be neater than this or Kevin's proposal
Using string split would likely be as unwieldy:
SELECT CONCAT(MAX(CASE WHEN rn = 1 THEN v END), '-', MAX(CASE WHEN rn = 2 THEN v END))
FROM (
SELECT row_number () over (order by (select 0)) rn, value as v
FROM string_split(#x,'.')
) y WHERE rn IN (1,2)
Because the string is split to rows which then need to be numbered in order to filter and pull the parts you want. This also relies on the strings coming out of string split in the order they were in the original string, which MS do not guarantee will be the case

Replace first occurrence of '.' in sql String

Let's say we saved inside a table the following values on Column as String:
Select ValuesT from TableT;
ValuesT
-9.827.08
-9.657.40
-80.000.00
-8.700.00
-8.542.43
-8.403.00
How could be replaced with nothing only the first occurrence of '.' (dot) from the string?
Ex: for -9.827.08 should be -9827.08
I tried with stuff function but this won't work for -80.000.00
select stuff( ValuesT ,3,1,'') from TableT
Use STUFF function
Find the first occurance of . using CHARINDEX and remove it using STUFF
SELECT STUFF(valuesT, CHARINDEX('.', valuesT), 1, '')
FROM TableT
Another way.
WITH sampleData AS
(
SELECT val FROM (VALUES
('-9.827.08'), ('-9.657.40'), ('-80.000.00'), ('-8.700.00'),
('-8.542.43'),('-8.403.00')) x(val)
)
SELECT SUBSTRING(val, 1, d1.d-1)+SUBSTRING(val, d1.d+1, 100)
FROM sampleData
CROSS APPLY (VALUES (CHARINDEX('.',val))) d1(d);
Its a little more code but just as efficient. There's a lot more you can do with this technique.

SQL Server - Select column that contains query string and split values into anothers 'columns'

I need to do a select in a column that contains a query string like:
user_id=300&company_id=201503&status=WAITING OPERATION&count=1
I want to perform a select and break each value in a new column, something like:
user_id | company_id | status | count
300 | 201503 | WAITING OPERATION | 1
How can i do it in SQL Server without use procs?
I've tried a function:
CREATE FUNCTION [xpto].[SplitGriswold]
(
#List NVARCHAR(MAX),
#Delim1 NCHAR(1),
#Delim2 NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
SELECT
Val1 = PARSENAME(Value,2),
Val2 = PARSENAME(Value,1)
FROM
(
SELECT REPLACE(Value, #Delim2, '&') FROM
(
SELECT LTRIM(RTRIM(SUBSTRING(#List, [Number],
CHARINDEX(#Delim1, #List + #Delim1, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(#List)
AND SUBSTRING(#Delim1 + #List, [Number], LEN(#Delim1)) = #Delim1
) AS y(Value)
) AS z(Value)
);
GO
Execution:
select QueryString
from User.Log
CROSS APPLY notifier.SplitGriswold(REPLACE(QueryString, ' ', N'ŏ'), N'ŏ', '&') AS t;
But it returns me only one column with all inside:
QueryString
user_id=300&company_id=201503&status=WAITING OPERATION&count=1
Thanks in advance.
I've had to do this many times before, and you're in luck! Since you only have 3 delimiters per string, and that number is fixed, you can use SQL Server's PARSENAME function to do it. That's far less ugly than the best alternative (using the XML parsing stuff). Try this (untested) query (replace TABLE_NAME and COLUMN_NAME with the appropriate names):
SELECT
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),1) AS 'User',
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),2) AS 'Company_ID',
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),3) AS 'Status',
PARSENAME(REPLACE(COLUMN_NAME,'&','.'),4) AS 'Count',
FROM TABLE_NAME
That'll get you the results in the form "user_id=300", which is far and away the hard part of what you want. I'll leave it to you to do the easy part (drop the stuff before the "=" sign).
NOTE: I can't remember if PARSENAME will freak out over the illegal name character (the "=" sign). If it does, simply nest another REPLACE in there to turn it into something else, like an underscore.
You need to use SQL SUBSTRING as part of your select statement. You would first need to build the first row, then use a UNION to return the second row.