remove string from variable based on matched rows - tsql

I have a small problem and I know a few solutions for it, but I don't know the best way (or less dirty spaghetti way) to go.
I have a string variable, and I need to use it in a like expression.
So:
declare #a varchar(100) = 'my string to use as a join from'
select *
from table
where
column like '%' + #a + '%'
But I don't want any rows from table that contains the #a variable, I want any rows from table that are contained in the #a variable, so:
select *
from table
where
#a like '%' + column + '%'
Result:
'my string'
'as a join from'
But now I need to remove the matched rows from the #a variable. How can I do that?
(edit)
expected result:
#a = ' to use '
'my string'
'as a join from'

You can alter the value of #a with each matched row in the select:
select #a = replace(#a, MyColumn, '')
from MyTable
where #a like '%' + MyColumn + '%'
Demo: http://www.sqlfiddle.com/#!3/187d6/5 (result: #a = ' to use ')
If you want to get a result set as well, use a temp table to store matching rows:
select MyColumn
into #Temp
from MyTable
where #a like '%' + MyColumn + '%'
-- #a = ' to use '
select #a = replace(#a, MyColumn, '') from #Temp
-- returns result set of words that matched
select * from #Temp
drop table #Temp
Demo: http://www.sqlfiddle.com/#!3/187d6/13

Related

T-SQL: Get a tab-separated list of column names in a table - What's wrong with my example?

This SQL returns a comma-separated list of column names for the table 'MyTable'
DECLARE #colnames VARCHAR(max);
SELECT
#colnames = COALESCE(#colnames + ',', '') + column_name
FROM
CORP_MLR_Rebates.INFORMATION_SCHEMA.COLUMNS
WHERE
table_name = 'MyTable';
SELECT #colnames;
Why doesn't the following give me a tab-separated list the same columns? Instead, it is space-separated.
DECLARE #colnames VARCHAR(max);
SELECT
#colnames = COALESCE(#colnames + char(9), '') + column_name
FROM
CORP_MLR_Rebates.INFORMATION_SCHEMA.COLUMNS
WHERE
table_name = 'MyTable';
SELECT #colnames;
In my testing it does work, I guess it just depends on how you retrieve the results (I have to output the result to a file to get the correct results)
You will likely get spaces using the SSMS GUI. Returning the results as text or to a file will give you tabs; e.g. CHAR(9). Consider these three queries that all do the job:
;--== 1. Updatable Variable
DECLARE #colnames VARCHAR(max);
SELECT #colnames = COALESCE(#colnames + CHAR(9), '') + column_name
FROM INFORMATION_SCHEMA.COLUMNS AS c;
SELECT ColNames = #colnames;
;--== 2. Using STRING_AGG
SELECT ColNames = STRING_AGG(c.COLUMN_NAME , CHAR(9))
FROM INFORMATION_SCHEMA.COLUMNS AS c;
;--== 3. Using FOR XML PATH
SELECT ColNames =
(SELECT c.COLUMN_NAME+CHAR(9) FROM INFORMATION_SCHEMA.COLUMNS AS c FOR XML PATH(''));
Results:
If we switch to Results as Text:
Now we get (note the tabs):

Dynamic SQL with EXECUTE and nested format()

I tried doing this with just a normal CTE (no EXECUTE ... FORMAT) and I was able to do it.
I basically have a table that has some 5 columns and some data in each column that I wanted to concatenate and such to manifest/generate some data in a new column.
I can do something like this and it works:
WITH cte AS (SELECT *, case
when var1 = '' then ''
when var2 = '' then ''
else '' end, 'adding_dummy_text_column'
FROM some_other_table sot
WHERE sot.type = 'java')
INSERT INTO my_new_table
SELECT *, 'This is the new column I want to make with some data from my CTE' || c.type
FROM cte c;
So this works as I said. I'll end up getting a new table that has an extra column which a hardcoded, concatenated string This is the new column I want to make with some data from my CTE Java
Of course, whatever is in the c.type column for the corresponding row in the CTE as it loads the SELECT is what gets concatenated to that string.
The problem is, as soon as I start using the EXECUTE...FORMAT to make it cleaner and have more power to concatenate/combined different data pieces from my different columns (I have data kind of scattered around in bad formats and I'm populating a fresh new table), it's as if the FORMAT arguments or the variables cannot detect the CTE table.
This is how I'm doing it
EXECUTE FORMAT ('WITH cte AS (SELECT *, case
when var1 = %L then %L
when var2 = '' '' then '' ''
else '' '' end, ''adding_dummy_text_column''
FROM some_other_table sot
WHERE sot.type = ''java'')
INSERT INTO my_new_table
SELECT *, ''This is the new column I want to make with some data from my CTE %I''
FROM cte c', 'word1', 'word2', c.type
);
OK, so I know I used the empty string '' '' in this example and the %L but i just wanted to show I had no issues with any of that. Its when I try to reference my CTE columns, so you can see I'm trying to do the same concatenation but by leveraging the EXECUTE...FORMAT and using the %I identifiers. So, the first 2 args are just fine, its the c.type that just no matter what column I try, doesn't work. Also, I removed the c alias and didn't get any better luck. It's 100% anytime I reference the columns on the CTE though, as I have removed all that code and it runs just fine without that.
But yeah, is there any work around? I really want to transform some data and now have to do the || for concatenation.
This should do it:
EXECUTE format($f$WITH cte AS (SELECT *, CASE
WHEN var1 = %L THEN %L
WHEN var2 = ' ' THEN ' '
ELSE ' ' END, 'adding_dummy_text_column'
FROM some_other_table sot
WHERE sot.type = 'java')
INSERT INTO my_new_table
SELECT *, format('This is the new column I want to make with some data from my CTE %%I', c.type)
FROM cte c$f$
, 'word1', 'word2');
There are two levels. You need a second format() that's executed by the dynamic SQL string that's been concatenated by the first format().
I simplified with dollar-quoting. See:
Insert text with single quotes in PostgreSQL
The nested % character must be escaped by doubling it up: %%I. See:
What does %% in PL/pgSQL mean?
Generates and executes this SQL statement:
WITH cte AS (SELECT *, CASE
WHEN var1 = 'word1' THEN 'word2'
WHEN var2 = ' ' THEN ' '
ELSE ' ' END, 'adding_dummy_text_column'
FROM some_other_table sot
WHERE sot.type = 'java')
INSERT INTO my_new_table
SELECT *, format('This is the new column I want to make with some data from my CTE %I', c.type)
FROM cte c
Which could be simplified and improved to this equivalent:
INSERT INTO my_new_table(co1, col2, ...) -- provide target column list!
SELECT *
, CASE WHEN var1 = 'word1' THEN 'word2'
WHEN var2 = ' ' THEN ' '
ELSE ' ' END
, 'adding_dummy_text_column'
, format('This is the new column I want to make with some data from my CTE %I', sot.type)
FROM some_other_table sot
WHERE sot.type = 'java'
About the missing target column list:
Cannot create stored procedure to insert data: type mismatch for serial column
You can one command, create and insert into new table!
create table my_new_table as
select *
, CASE WHEN var1 = 'hello' THEN 'word2'
WHEN var2 = ' ' THEN 'var2 is empty'
ELSE ' ' END adding_dummy_text_column
, format(
'This is the new column I want to make with some data from my CTE %I', sot.type)
from some_other_table sot
where sot.type ='java';

PostgresQL: Find array length of output from ARRAY_AGG()

How do I count the number of distinct elements in an array object, created by ARRAY_AGG() in PostgresQL? Here's a toy example for discussion purposes:
SELECT ARRAY_AGG (first_name || ' ' || last_name) actors
FROM film
I have tried ARRAY_LENGTH(), LENGTH(), etc., like so:
SELECT ARRAY_LENGTH(a.actors)
FROM (SELECT ARRAY_AGG (first_name || ' ' || last_name) actors
FROM film) a;
But I get an error:
function array_length(integer[]) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 208
So I tried (2):
SELECT ARRAY_LENGTH( CAST(COALESCE(a.actors, '0') AS integer) )
FROM (SELECT ARRAY_AGG (first_name || ' ' || last_name) actors
FROM film) a;
but I get the error:
malformed array literal: "0"
Detail: Array value must start with "{" or dimension information.
Position: 119
the function array_length(anyarray, int) require two elements, array and dimension for example:
Select array_length(array[1,2,3], 1);
Result:
3
If you are only dealing with a single dimension array, cardinality() is easier to use:
SELECT cardinality(ARRAY_LENGTH(a.actors))
FROM (
SELECT ARRAY_AGG (first_name || ' ' || last_name) actors
FROM film
) a;

How to add single quotes around a list of columns names?

I have a table which has few columns. I got the name of columns by getting it as an array_agg and then array_to_string. Like below:
"hospitalaccountrecord,locationname,patientkey,inpatientadmitdatetime,readmission,no_null_days_btw_admissions,cohort_assignment,admit_mon_feb,admit_mon_mar,admit_mon_apr,admit_mon_may,admit_mon_june,admit_mon_july,admit_mon_aug,admit_mon_sep,admit_mon_oct,a (...)"
The code I used was this:
select array_to_string(array_agg(column_name::text), ',')
from
(
select column_name
from
information_schema.columns
where table_schema='a' and
table_name = 'b'
order by columns.ordinal_position
) as v;
What I am looking for is the same thing but each column name should be enclosed in ''. Like
'hospitalaccountrecord','locationname','patientkey'
and so on.
Don't need two SELECTs, only
select string_agg(''''|| your_column_name ||'''', ',')
should do the job.
select string_agg(quoted_column_name, ',')
from
(
select '''' || column_name || '''' as quoted_column_name
from
information_schema.columns
where table_schema='a' and
table_name = 'b'
order by columns.ordinal_position
) as v;

Select query to remove non-numeric characters

I've got dirty data in a column with variable alpha length. I just want to strip out anything that is not 0-9.
I do not want to run a function or proc. I have a script that is similar that just grabs the numeric value after text, it looks like this:
Update TableName
set ColumntoUpdate=cast(replace(Columnofdirtydata,'Alpha #','') as int)
where Columnofdirtydata like 'Alpha #%'
And ColumntoUpdate is Null
I thought it would work pretty good until I found that some of the data fields I thought would just be in the format Alpha # 12345789 are not.
Examples of data that needs to be stripped
AB ABCDE # 123
ABCDE# 123
AB: ABC# 123
I just want the 123. It is true that all data fields do have the # prior to the number.
I tried substring and PatIndex, but I'm not quite getting the syntax correct or something. Anyone have any advice on the best way to address this?
See this blog post on extracting numbers from strings in SQL Server. Below is a sample using a string in your example:
DECLARE #textval NVARCHAR(30)
SET #textval = 'AB ABCDE # 123'
SELECT LEFT(SUBSTRING(#textval, PATINDEX('%[0-9.-]%', #textval), 8000),
PATINDEX('%[^0-9.-]%', SUBSTRING(#textval, PATINDEX('%[0-9.-]%', #textval), 8000) + 'X') -1)
Here is an elegant solution if your server supports the TRANSLATE function (on sql server it's available on sql server 2017+ and also sql azure).
First, it replaces any non numeric characters with a # character.
Then, it removes all # characters.
You may need to add additional characters that you know may be present in the second parameter of the TRANSLATE call.
select REPLACE(TRANSLATE([Col], 'abcdefghijklmnopqrstuvwxyz+()- ,#+', '##################################'), '#', '')
You can use stuff and patindex.
stuff(Col, 1, patindex('%[0-9]%', Col)-1, '')
SQL Fiddle
This works well for me:
CREATE FUNCTION [dbo].[StripNonNumerics]
(
#Temp varchar(255)
)
RETURNS varchar(255)
AS
Begin
Declare #KeepValues as varchar(50)
Set #KeepValues = '%[^0-9]%'
While PatIndex(#KeepValues, #Temp) > 0
Set #Temp = Stuff(#Temp, PatIndex(#KeepValues, #Temp), 1, '')
Return #Temp
End
Then call the function like so to see the original something next to the sanitized something:
SELECT Something, dbo.StripNonNumerics(Something) FROM TableA
In case if there are some characters possible between digits (e.g. thousands separators), you may try following:
declare #table table (DirtyCol varchar(100))
insert into #table values
('AB ABCDE # 123')
,('ABCDE# 123')
,('AB: ABC# 123')
,('AB#')
,('AB # 1 000 000')
,('AB # 1`234`567')
,('AB # (9)(876)(543)')
;with tally as (select top (100) N=row_number() over (order by ##spid) from sys.all_columns),
data as (
select DirtyCol, Col
from #table
cross apply (
select (select C + ''
from (select N, substring(DirtyCol, N, 1) C from tally where N<=datalength(DirtyCol)) [1]
where C between '0' and '9'
order by N
for xml path(''))
) p (Col)
where p.Col is not NULL
)
select DirtyCol, cast(Col as int) IntCol
from data
Output is:
DirtyCol IntCol
--------------------- -------
AB ABCDE # 123 123
ABCDE# 123 123
AB: ABC# 123 123
AB # 1 000 000 1000000
AB # 1`234`567 1234567
AB # (9)(876)(543) 9876543
For update, add ColToUpdate to select list of the data cte:
;with num as (...),
data as (
select ColToUpdate, /*DirtyCol, */Col
from ...
)
update data
set ColToUpdate = cast(Col as int)
CREATE FUNCTION FN_RemoveNonNumeric (#Input NVARCHAR(512))
RETURNS NVARCHAR(512)
AS
BEGIN
DECLARE #Trimmed NVARCHAR(512)
SELECT #Trimmed = #Input
WHILE PATINDEX('%[^0-9]%', #Trimmed) > 0
SELECT #Trimmed = REPLACE(#Trimmed, SUBSTRING(#Trimmed, PATINDEX('%[^0-9]%', #Trimmed), 1), '')
RETURN #Trimmed
END
GO
SELECT dbo.FN_RemoveNonNumeric('ABCDE# 123')
Pretty late to the party, I found the following which I though worked brilliantialy.. if anyone is still looking
SELECT
(SELECT CAST(CAST((
SELECT SUBSTRING(FieldToStrip, Number, 1)
FROM master..spt_values
WHERE Type='p' AND Number <= LEN(FieldToStrip) AND
SUBSTRING(FieldToStrip, Number, 1) LIKE '[0-9]' FOR XML Path(''))
AS xml) AS varchar(MAX)))
FROM
SourceTable
Here's a version which pulls all digits from a string; i.e. given I'm 35 years old; I was born in 1982. The average family has 2.4 children. this would return 35198224. i.e. it's good where you've got numeric data which may have been formatted as a code (e.g. #123,456,789 / 123-00005), but isn't appropriate if you're looking to pull out specific numbers (i.e. as opposed to digits / just the numeric characters) from the text. Also it only handles digits; so won't return negative signs (-) or periods .).
declare #table table (id bigint not null identity (1,1), data nvarchar(max))
insert #table (data)
values ('hello 123 its 45613 then') --outputs: 12345613
,('1 some other string 98 example 4') --outputs: 1984
,('AB ABCDE # 123') --outputs: 123
,('ABCDE# 123') --outputs: 123
,('AB: ABC# 123') --outputs: 123
; with NonNumerics as (
select id
, data original
--the below line replaces all digits with blanks
, replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(data,'0',''),'1',''),'2',''),'3',''),'4',''),'5',''),'6',''),'7',''),'8',''),'9','') nonNumeric
from #table
)
--each iteration of the below CTE removes another non-numeric character from the original string, putting the result into the numerics column
, Numerics as (
select id
, replace(original, substring(nonNumeric,1,1), '') numerics
, replace(nonNumeric, substring(nonNumeric,1,1), '') charsToreplace
, len(replace(nonNumeric, substring(nonNumeric,1,1), '')) charsRemaining
from NonNumerics
union all
select id
, replace(numerics, substring(charsToreplace,1,1), '') numerics
, replace(charsToreplace, substring(charsToreplace,1,1), '') charsToreplace
, len(replace(charsToreplace, substring(charsToreplace,1,1), '')) charsRemaining
from Numerics
where charsRemaining > 0
)
--we select only those strings with `charsRemaining=0`; i.e. the rows for which all non-numeric characters have been removed; there should be 1 row returned for every 1 row in the original data set.
select * from Numerics where charsRemaining = 0
This code works by removing all the digits (i.e. the characters we want) from a the given strings by replacing them with blanks. Then it goes through the original string (which includes the digits) removing all of the characters that were left (i.e. the non-numeric characters), thus leaving only the digits.
The reason we do this in 2 steps, rather than just removing all non-numeric characters in the first place is there are only 10 digits, whilst there are a huge number of possible characters; so replacing that small list is relatively fast; then gives us a list of those non-numeric characters which actually exist in the string, so we can then replace that small set.
The method makes use of recursive SQL, using common table expressions (CTEs).
To add on to Ken's answer, this handles commas and spaces and parentheses
--Handles parentheses, commas, spaces, hyphens..
declare #table table (c varchar(256))
insert into #table
values
('This is a test 111-222-3344'),
('Some Sample Text (111)-222-3344'),
('Hello there 111222 3344 / How are you?'),
('Hello there 111 222 3344 ? How are you?'),
('Hello there 111 222 3344. How are you?')
select
replace(LEFT(SUBSTRING(replace(replace(replace(replace(replace(c,'(',''),')',''),'-',''),' ',''),',',''), PATINDEX('%[0-9.-]%', replace(replace(replace(replace(replace(c,'(',''),')',''),'-',''),' ',''),',','')), 8000),
PATINDEX('%[^0-9.-]%', SUBSTRING(replace(replace(replace(replace(replace(c,'(',''),')',''),'-',''),' ',''),',',''), PATINDEX('%[0-9.-]%', replace(replace(replace(replace(replace(c,'(',''),')',''),'-',''),' ',''),',','')), 8000) + 'X') -1),'.','')
from #table
Create function fn_GetNumbersOnly(#pn varchar(100))
Returns varchar(max)
AS
BEGIN
Declare #r varchar(max) ='', #len int ,#c char(1), #x int = 0
Select #len = len(#pn)
while #x <= #len
begin
Select #c = SUBSTRING(#pn,#x,1)
if ISNUMERIC(#c) = 1 and #c <> '-'
Select #r = #r + #c
Select #x = #x +1
end
return #r
End
In your case It seems like the # will always be after teh # symbol so using CHARINDEX() with LTRIM() and RTRIM() would probably perform the best. But here is an interesting method of getting rid of ANY non digit. It utilizes a tally table and table of digits to limit which characters are accepted then XML technique to concatenate back to a single string without the non-numeric characters. The neat thing about this technique is it could be expanded to included ANY Allowed characters and strip out anything that is not allowed.
DECLARE #ExampleData AS TABLE (Col VARCHAR(100))
INSERT INTO #ExampleData (Col) VALUES ('AB ABCDE # 123'),('ABCDE# 123'),('AB: ABC# 123')
DECLARE #Digits AS TABLE (D CHAR(1))
INSERT INTO #Digits (D) VALUES ('0'),('1'),('2'),('3'),('4'),('5'),('6'),('7'),('8'),('9')
;WITH cteTally AS (
SELECT
I = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#Digits d10
CROSS APPLY #Digits d100
--add more cross applies to cover longer fields this handles 100
)
SELECT *
FROM
#ExampleData e
OUTER APPLY (
SELECT CleansedPhone = CAST((
SELECT TOP 100
SUBSTRING(e.Col,t.I,1)
FROM
cteTally t
INNER JOIN #Digits d
ON SUBSTRING(e.Col,t.I,1) = d.D
WHERE
I <= LEN(e.Col)
ORDER BY
t.I
FOR XML PATH('')) AS VARCHAR(100))) o
Declare #MainTable table(id int identity(1,1),TextField varchar(100))
INSERT INTO #MainTable (TextField)
VALUES
('6B32E')
declare #i int=1
Declare #originalWord varchar(100)=''
WHile #i<=(Select count(*) from #MainTable)
BEGIN
Select #originalWord=TextField from #MainTable where id=#i
Declare #r varchar(max) ='', #len int ,#c char(1), #x int = 0
Select #len = len(#originalWord)
declare #pn varchar(100)=#originalWord
while #x <= #len
begin
Select #c = SUBSTRING(#pn,#x,1)
if(#c!='')
BEGIN
if ISNUMERIC(#c) = 0 and #c <> '-'
BEGIN
Select #r = cast(#r as varchar) + cast(replace((SELECT ASCII(#c)-64),'-','') as varchar)
end
ELSE
BEGIN
Select #r = #r + #c
END
END
Select #x = #x +1
END
Select #r
Set #i=#i+1
END
I have created a function for this
Create FUNCTION RemoveCharacters (#text varchar(30))
RETURNS VARCHAR(30)
AS
BEGIN
declare #index as int
declare #newtexval as varchar(30)
set #index = (select PATINDEX('%[A-Z.-/?]%', #text))
if (#index =0)
begin
return #text
end
else
begin
set #newtexval = (select STUFF ( #text , #index , 1 , '' ))
return dbo.RemoveCharacters(#newtexval)
end
return 0
END
GO
Here is the answer:
DECLARE #t TABLE (tVal VARCHAR(100))
INSERT INTO #t VALUES('123')
INSERT INTO #t VALUES('123S')
INSERT INTO #t VALUES('A123,123')
INSERT INTO #t VALUES('a123..A123')
;WITH cte (original, tVal, n)
AS
(
SELECT t.tVal AS original,
LOWER(t.tVal) AS tVal,
65 AS n
FROM #t AS t
UNION ALL
SELECT tVal AS original,
CAST(REPLACE(LOWER(tVal), LOWER(CHAR(n)), '') AS VARCHAR(100)),
n + 1
FROM cte
WHERE n <= 90
)
SELECT t1.tVal AS OldVal,
t.tval AS NewVal
FROM (
SELECT original,
tVal,
ROW_NUMBER() OVER(PARTITION BY tVal + original ORDER BY original) AS Sl
FROM cte
WHERE PATINDEX('%[a-z]%', tVal) = 0
) t
INNER JOIN #t t1
ON t.original = t1.tVal
WHERE t.sl = 1
You can create SQL CLR scalar function in order to be able to use regular expressions like replace patterns.
Here you can find example of how to create such function.
Having such function will solve the issue with just the following lines:
SELECT [dbo].[fn_Utils_RegexReplace] ('AB ABCDE # 123', '[^0-9]', '');
SELECT [dbo].[fn_Utils_RegexReplace] ('ABCDE# 123', '[^0-9]', '');
SELECT [dbo].[fn_Utils_RegexReplace] ('AB: ABC# 123', '[^0-9]', '');
More important, you will be able to solve more complex issues as the regular expressions will bring a whole new world of options directly in your T-SQL statements.
Use this:
REPLACE(TRANSLATE(SomeString, REPLACE(TRANSLATE(SomeString, '0123456789', '##########'), '#', ''), REPLICATE('#', LEN(REPLACE(TRANSLATE(SomeString, '0123456789', '##########'), '#', '') + 'x') - 1)), '#', '')
Demo:
DROP TABLE IF EXISTS #MyTempTable;
CREATE TABLE #MyTempTable (SomeString VARCHAR(255));
INSERT INTO #MyTempTable
VALUES ('ssss123ssg99d362sdg')
, ('hey 62q&*^(n43')
, (NULL)
, ('')
, ('hi')
, ('123');
SELECT SomeString
, REPLACE(TRANSLATE(SomeString, REPLACE(TRANSLATE(SomeString, '0123456789', '##########'), '#', ''), REPLICATE('#', LEN(REPLACE(TRANSLATE(SomeString, '0123456789', '##########'), '#', '') + 'x') - 1)), '#', '')
FROM #MyTempTable;
DROP TABLE IF EXISTS #MyTempTable;
Results:
SomeString
(No column name)
ssss123ssg99d362sdg
12399362
hey62q&*^(n43
6243
NULL
NULL
hi
123
123
While the OP wanted to "strip out anything that is not 0-9", the post is also tagged with "substring" and "patindex", and the OP mentioned the concern "not quite getting the syntax correct or something". To address that the requirements note that "all data fields do have the # prior to the number" and to provide an answer that addresses the challenges with substring/patindex, consider the following:
/* A sample select */
;WITH SampleValues AS
( SELECT 'AB ABCDE # 123' [Columnofdirtydata]
UNION ALL SELECT 'AB2: ABC# 123')
SELECT
s.Columnofdirtydata,
f1.pos1,
'['+ f2.substr +']' [InspectOutput]
FROM
SampleValues s
CROSS APPLY (SELECT PATINDEX('%# %',s.Columnofdirtydata) [pos1]) f1
CROSS APPLY (SELECT SUBSTRING(s.Columnofdirtydata, f1.pos1 + LEN('#-'),LEN(s.Columnofdirtydata)) [substr]) f2
/* Using update scenario from OP */
UPDATE t1
SET t1.Columntoupdate = CAST(f2.substr AS INT)
FROM
TableName t1
CROSS APPLY (SELECT PATINDEX('%# %',t1.Columnofdirtydata) [pos1]) f1
CROSS APPLY (SELECT SUBSTRING(t1.Columnofdirtydata, f1.pos1 + LEN('#-'),LEN(t1.Columnofdirtydata)) [substr]) f2
Note that my syntax advice for patindex/substring, is to:
consider using APPLY as a way to temporarily alias results from one function for use as parameters in the next. It's not uncommon to (in ETL, for example) need to parse out parameter/position-based substrings in an updatable column of a staging table. If you need to "debug" and potentially fix some parsing logic, this style will help.
consider using LEN('PatternSample') in your substring logic, to account for reusing this pattern or adjusting it when your source data changes (instead of "+ 1"
SUBSTRING() requires a length parameter, but it can be greater than the length of the string. Therefore, if you are getting "the rest of the string" after the pattern, you can just use "The source length"
DECLARE #STR VARCHAR(400)
DECLARE #specialchars VARCHAR(50) = '%[~,#,#,$,%,&,*,(,),!^?:]%'
SET #STR = '1, 45 4,3 68.00-'
WHILE PATINDEX( #specialchars, #STR ) > 0
---Remove special characters using Replace function
SET #STR = Replace(Replace(REPLACE( #STR, SUBSTRING( #STR, PATINDEX( #specialchars, #STR ), 1 ),''),'-',''), ' ','')
SELECT #STR
SELECT REGEXP_REPLACE( col, '[^[:digit:]]', '' ) AS new_col FROM my_table