How to write recursive TSQL replace query? - tsql

I am using SSMS 2008 and am trying to write a recursive replace statement. I have a good start on this, but it is not working fully yet. I want to replace every occurrence of XML tags occurring in one column with empty string. So I want to replace the whole range from "<" to ">" for each record. Here is what I have:
DECLARE #I INTEGER
SET #I = 3
while
#I > 0
--(select [note_text] from #TEMP_PN where [note_text] LIKE '%<%')
BEGIN
UPDATE #TEMP_PN
SET [note_text] = replace([note_text],substring([note_text],CHARINDEX('<',[note_text]),CHARINDEX('>',[note_text])),'')
from #TEMP_PN
where [note_text] LIKE '%Microsoft-com%'
SET #I = #I - 1
END
SELECT * FROM #TEMP_PN
The problem with this code is I hardcoded #I to be 3. However, I want to make it continue replacing from "<" to ">" with empty string for each record until there are no more "<" chars. So I tried the commented out line above but this gives me an error on more than one record / subquery. How can I achieve this recursive functionality? Also, my Replace statement above only replaced "<" chars for some records, strangely enough.
I tried your sample code, but it still does not replace all instances of this text per record and for some records it does not replace any text although there is "<" in these records. Here is a record where your script does not replace any substrings. Maybe this is a special character problem?
<DIV class=gc-message-sms-row><SPAN class=gc-message-sms-from>TLS: </SPAN><SPAN class=gc-message-sms-text>Hi Reggie... I'm on my way to Lynn.. see you soon</SPAN> <SPAN class=gc-message-sms-time>3:09 PM </SPAN></DIV>

You were pretty close... the problem is that the SUBSTRING's third parameter is a length, not the position to stop at.
DECLARE #RowsUpdated INT
SET #RowsUpdated = 1
WHILE (#RowsUpdated > 0)
BEGIN
UPDATE #TEMP_PN
SET [note_text] =
REPLACE(
[note_text],
substring(
[note_text],
CHARINDEX('<',[note_text]),
CHARINDEX(
'>',
SUBSTRING([note_text], CHARINDEX('<',[note_text]), 1 + LEN([note_text]) - CHARINDEX('<',[note_text]))
)
),
'')
from #TEMP_PN
where [note_text] LIKE '%Microsoft-com%' and [note_text] like '%<%>%'
SET #RowsUpdated = ##ROWCOUNT
END
SELECT * FROM #TEMP_PN
SECOND EDIT:
OK, I've updated both queries; this code should now handle the leading > before the first tag... which I think could have been the issue.
DECLARE #TestString VARCHAR(MAX)
SELECT #TestString = '><DIV class=gc-message-sms-row><SPAN class=gc-message-sms-from>TLS: </SPAN><SPAN class=gc-message-sms-text>Hi Reggie... I''m on my way to Lynn.. see you soon</SPAN> <SPAN class=gc-message-sms-time>3:09 PM </SPAN></DIV>'
DECLARE #RowsUpdated INT
SET #RowsUpdated = 1
WHILE (#RowsUpdated > 0)
BEGIN
SELECT
#TestString =
REPLACE(
#TestString,
substring(
#TestString,
CHARINDEX('<',#TestString),
CHARINDEX(
'>',
SUBSTRING(#TestString, CHARINDEX('<',#TestString), 1 + LEN(#TestString) - CHARINDEX('<',#TestString))
)
),
'')
WHERE #TestString LIKE '%<%>%'
SET #RowsUpdated = ##ROWCOUNT
END
SELECT #TestString
Could it be because that note doesn't meet the [note_text] LIKE '%Microsoft-com%' criteria?

Related

Trying to format INT in Money, but getting error: FORMAT is not a recognized built-in function

I'm trying to format the amount such as sums like 1588000 look like $1,588,000.
I've tried:
CASE
WHEN nb.AmountDollars IS NOT NULL THEN
CAST(FORMAT(nb.AmountDollars, '##,##0') AS VARCHAR(50))
ELSE ''
END
But I'm getting an error: 'FORMAT' is not a recognized built-in function name.
Yet I see many answers in stackoverflow people using FORMAT.
Thanks for helping
Here's a quick and dirty scalar function to split up the value for you.
CREATE FUNCTION [dbo].[fn_FormatDollars]
(
#RawValue INT
)
RETURNS VARCHAR(20)
AS
BEGIN
DECLARE #FormattedValue VARCHAR(20)
IF LEN(#RawValue) > 6
BEGIN
SELECT
#FormattedValue = '$' +
LEFT(CAST(#RawValue AS VARCHAR),1) + ',' +
SUBSTRING(CAST(#RawValue AS VARCHAR),2,3) + ',' +
RIGHT(CAST(#RawValue AS VARCHAR),3)
END
ELSE
BEGIN
SELECT
#FormattedValue ='$' +
SUBSTRING(CAST(#RawValue AS VARCHAR),1,3) + ',' +
RIGHT(CAST(#RawValue AS VARCHAR),3)
END
RETURN #FormattedValue
END
GO
Then use it like this with your field:
SELECT [dbo].[fn_FormatDollars](nb.[AmountDollars])
Example:
SELECT [dbo].[fn_FormatDollars](1588000)
Result:
I wrote this against a SQL 2005 instance just to play save.

Using patterns in REPLACE

I need to find and replace an expression within a dynamic query. I have a subset of a where condition in string type like
'fieldA=23 OR field_1=300 OR fieldB=4'
What I need is to find a way to detect expression field_1=300 within the string and replace it while retaining the expression field_1=300.
I can do the detection part using CHARINDEX or PATINDEX but I'm not able to figure out how to use the patterns in the REPLACE function and how to get the value of the field_1 parameter.
Thanks in advance.
I'm not entirely clear on what you're trying to acheieve (e.g. what are you wanting to replace "field_1=300" with, and is it the exact string "field_1=300" that you're looking for, or just the field name, i.e. "field_1"?).
Also, could you paste in the code you've written so far?
Here's a simple script which will extract the current value of a given field name:
DECLARE #str VARCHAR(100),
#str_tmp VARCHAR(100),
#field_pattern VARCHAR(10),
#field_val INT;
SET #str = 'fieldA=23 OR field_1=300 OR fieldB=4';
SET #field_pattern = 'field_1='
-- This part will extract the current value assigned to the "#field_pattern" field
IF CHARINDEX(#field_pattern, #str) > 0
BEGIN
SELECT #str_tmp = SUBSTRING(#str,
CHARINDEX(#field_pattern, #str) + LEN(#field_pattern),
LEN(#str)
);
SELECT #field_val = CAST(SUBSTRING(#str_tmp, 1, CHARINDEX(' ', #str_tmp)-1) AS INT);
END
PRINT #field_val
If you want to replace the value itself (e.g. replacing "300" in this case with "600"), then you could add something like this:
DECLARE #new_val INT;
SET #new_val = 600;
SET #str = REPLACE(#str, (#field_pattern+CAST(#field_val AS VARCHAR)), (#field_pattern+CAST(#new_val AS VARCHAR)));
PRINT #str;
Which would give you "fieldA=23 OR field_1=600 OR fieldB=4".
Cheers,
Dave

IsDate in TSQL Issue

I have a bug on our app and it points that if there is a date format like this:
SELECT IsDate('4:27:01') -- ok
SELECT IsDate('25:01') ---> not ok
SELECT IsDate('23:01') ---> ok
In our data, sometimes the format only 5 characters which means it's only for minutes and seconds.
What is best way to handle this?
Easiest way might be to add "00:" when the length is 5 characters.
e.g. :
declare #str varchar(100)
set #str = '25:01'
-- if length is 5, interpret as 00:mi:ss
set #str = (CASE WHEN LEN(#str)=5 THEN '00:' + #str ELSE #str END)
select ISDATE(#str) -- shows 1
select cast(#str as time) -- shows 00:25:01.000

DESCENDING/ASCENDING Parameter to a stored procedure

I have the following SP
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = #webRegionID)
ORDER BY
CASE UPPER(#sortBy)
when 'CASE_NO' then case_no
when 'AREA' then area
when 'FURNISHED' then furnished
when 'TYPE' then [type]
when 'SQUAREFEETS' then squarefeets
when 'BEDROOMS' then bedrooms
when 'LIVINGROOMS' then livingrooms
when 'BATHROOMS' then bathrooms
when 'LEASE_FROM' then lease_from
when 'RENT' then rent
else case_no
END
END
GO
Now everything in that SP works but I want to be able to choose whether I want to sort ASCENDING or DESCENDING.
I really can't fint no solution for that using SQL and can't find anything in google.
As you can see I have the parameter sortDirection and I have tried using it in multiple ways but always with errors... Tried Case Statements, IF statements and so on but it is complicated by the fact that I want to insert a keyword.
Help will be very much appriciated, I have tried must of the things that comes into mind but haven't been able to get it right.
You could use two order by fields:
CASE #sortDir WHEN 'ASC' THEN
CASE UPPER(#sortBy)
...
END
END ASC,
CASE #sortDir WHEN 'DESC' THEN
CASE UPPER(#sortBy)
...
END
END DESC
A CASE will evaluate as NULL if none of the WHEN clauses match, so that causes one of the two fields to evaluate to NULL for every row (not affecting the sort order) and the other has the appropriate direction.
One drawback, though, is that you'd need to duplicate your #sortBy CASE statement. You could achieve the same thing using dynamic SQL with sp_executesql and writing a 'ASC' or 'DESC' literal depending on the parameter.
That code is going to get very unmanageable very quickly as you'll need to double nest your CASE WHEN's... one set for the Column to order by, and nested set for whethers it's ASC or DESC
Might be better to consider using Dynamic SQL here...
DECLARE #sql nvarchar(max)
SET #sql = '
Select
tbl_houses.*
from tbl_houses
where
postal in (select zipcode from crm_zipcodes where web_region_id = ' + #webRegionID + ') ORDER BY '
SET #sql = #sql + ' ' + #sortBy + ' ' + #sortDirection
EXEC (#sql)
You could do it with some dynamic SQL and calling it with an EXEC. Beware SQL injection though if the user has any control over the parameters.
CREATE PROCEDURE GetAllHouses
set #webRegionID = 2
set #sortBy = 'case_no'
set #sortDirection = 'ASC'
AS
BEGIN
DECLARE #dynamicSQL NVARCHAR(MAX)
SET #dynamicSQL =
'
SELECT
tbl_houses.*
FROM
tbl_houses
WHERE
postal
IN
(
SELECT
zipcode
FROM
crm_zipcodes
WHERE
web_region_id = ' + CONVERT(nvarchar(10), #webRegionID) + '
)
ORDER BY
' + #sortBy + ' ' + #sortDirection
EXEC(#dynamicSQL)
END
GO

Extract the first word of a string in a SQL Server query

What's the best way to extract the first word of a string in sql server query?
SELECT CASE CHARINDEX(' ', #Foo, 1)
WHEN 0 THEN #Foo -- empty or single word
ELSE SUBSTRING(#Foo, 1, CHARINDEX(' ', #Foo, 1) - 1) -- multi-word
END
You could perhaps use this in a UDF:
CREATE FUNCTION [dbo].[FirstWord] (#value varchar(max))
RETURNS varchar(max)
AS
BEGIN
RETURN CASE CHARINDEX(' ', #value, 1)
WHEN 0 THEN #value
ELSE SUBSTRING(#value, 1, CHARINDEX(' ', #value, 1) - 1) END
END
GO -- test:
SELECT dbo.FirstWord(NULL)
SELECT dbo.FirstWord('')
SELECT dbo.FirstWord('abc')
SELECT dbo.FirstWord('abc def')
SELECT dbo.FirstWord('abc def ghi')
I wanted to do something like this without making a separate function, and came up with this simple one-line approach:
DECLARE #test NVARCHAR(255)
SET #test = 'First Second'
SELECT SUBSTRING(#test,1,(CHARINDEX(' ',#test + ' ')-1))
This would return the result "First"
It's short, just not as robust, as it assumes your string doesn't start with a space. It will handle one-word inputs, multi-word inputs, and empty string inputs.
Enhancement of Ben Brandt's answer to compensate even if the string starts with space by applying LTRIM(). Tried to edit his answer but rejected, so I am now posting it here separately.
DECLARE #test NVARCHAR(255)
SET #test = 'First Second'
SELECT SUBSTRING(LTRIM(#test),1,(CHARINDEX(' ',LTRIM(#test) + ' ')-1))
Adding the following before the RETURN statement would solve for the cases where a leading space was included in the field:
SET #Value = LTRIM(RTRIM(#Value))
Marc's answer got me most of the way to what I needed, but I had to go with patIndex rather than charIndex because sometimes characters other than spaces mark the ends of my data's words. Here I'm using '%[ /-]%' to look for space, slash, or dash.
Select race_id, race_description
, Case patIndex ('%[ /-]%', LTrim (race_description))
When 0 Then LTrim (race_description)
Else substring (LTrim (race_description), 1, patIndex ('%[ /-]%', LTrim (race_description)) - 1)
End race_abbreviation
from tbl_races
Results...
race_id race_description race_abbreviation
------- ------------------------- -----------------
1 White White
2 Black or African American Black
3 Hispanic/Latino Hispanic
Caveat: this is for a small data set (US federal race reporting categories); I don't know what would happen to performance when scaled up to huge numbers.
DECLARE #string NVARCHAR(50)
SET #string = 'CUT STRING'
SELECT LEFT(#string,(PATINDEX('% %',#string)))
Extract the first word from the indicated field:
SELECT SUBSTRING(field1, 1, CHARINDEX(' ', field1)) FROM table1;
Extract the second and successive words from the indicated field:
SELECT SUBSTRING(field1, CHARINDEX(' ', field1)+1, LEN (field1)-CHARINDEX(' ', field1)) FROM table1;
A slight tweak to the function returns the next word from a start point in the entry
CREATE FUNCTION [dbo].[GetWord]
(
#value varchar(max)
, #startLocation int
)
RETURNS varchar(max)
AS
BEGIN
SET #value = LTRIM(RTRIM(#Value))
SELECT #startLocation =
CASE
WHEN #startLocation > Len(#value) THEN LEN(#value)
ELSE #startLocation
END
SELECT #value =
CASE
WHEN #startLocation > 1
THEN LTRIM(RTRIM(RIGHT(#value, LEN(#value) - #startLocation)))
ELSE #value
END
RETURN CASE CHARINDEX(' ', #value, 1)
WHEN 0 THEN #value
ELSE SUBSTRING(#value, 1, CHARINDEX(' ', #value, 1) - 1)
END
END
GO
SELECT dbo.GetWord(NULL, 1)
SELECT dbo.GetWord('', 1)
SELECT dbo.GetWord('abc', 1)
SELECT dbo.GetWord('abc def', 4)
SELECT dbo.GetWord('abc def ghi', 20)
Try This:
Select race_id, race_description
, Case patIndex ('%[ /-]%', LTrim (race_description))
When 0 Then LTrim (race_description)
Else substring (LTrim (race_description), 1, patIndex ('%[ /-]%', LTrim (race_description)) - 1)
End race_abbreviation
from tbl_races