Related
I have a varchar that always come into this format:
'PB' + multiple Leading 0 + Number + Non-Number Character(s).
For example: PB000013452S, PB000013452S3s2fss.
How do I parse the varchar value to get the "Number" (13452) in this case?
Use PATINDEX to find the position of the first number (that isn't 0) and then PATINDEX again to find the position of the first non-numerical character afterwards. Then use SUBSTRING to extract the number:
SELECT SUBSTRING(V.YourString,PI.I,PATINDEX('%[^0-9]%',STUFF(V.YourString,1,PI.I-1,''))-1)
FROM (VALUES('PB000013452S'),('PB000013452S3s2fss'))V(YourString)
CROSS APPLY (VALUES(PATINDEX('%[1-9]%',V.YourString)))PI(I)
I write an algorithme for your problem you can try it and i tested befor it works perfectly but i stored numbers in a table and if you want to concatenate them you can use cursor
declare #x varchar(30) = 'PB000013452S3s2fss' /*your string here*/
declare #_len int = len(#x) /*length of your string */
declare #array table (num varchar(30)) /*table for collecte number*/
declare #c int =1 /*counter*/
declare #_char varchar(1) /* to store one char from your string */
declare #result varchar(30)=''
while #_len>0
begin
set #_char = SUBSTRING(#x,#c,1)
if(#_char in ('1','2','3','4','5','6','7','8','9'))
begin
while #_len>0
begin
set #_char = SUBSTRING(#x,#c,1)
if(#_char in ('0','1','2','3','4','5','6','7','8','9'))
begin
insert into #array values (#_char)
set #c = #c+1
set #_len = #_len-1
end
else
set #_len = 0
end
end
set #c = #c+1
set #_len = #_len-1
end
select * from #array
I have a small report that needs to be ordered by postcode. How do I do this?
Using ORDER BY Postcode returns
SK1
SK11
SK13
SK2
How can I return
SK1
SK2
SK11
SK13
EDIT
I should really have added more to the question, I am working with postcodes for the whole of the UK, not just ones starting with SK. So some of these postcodes will start with only 1 letter, some with 2. Also, the second part of the postcode is in the column.
Assuming MSSQL, and that your Postcode field follows a consistent pattern of Char(2) + Number, then you could add a computed query column:
postcode_num = convert(int,substring(postcode,3,len(postcode)))
And then use it instead of Postcode for sorting:
order by postcode_num
Results as desired:
Create 2 columns:
1. a VARCHAR for the first part;
2. a TINYINT for the last (numeric) part.
ORDER BY postcode_prefix, postcode_suffix
Source: https://www.sitepoint.com/community/t/order-by-postcode/50042/9
The problem you are facing is that the column you are trying to ORDER BY is of type text and not numeric, therefore SQL will perform the ordering you're seeing. Instead, if you want SQL to order it as if it was a number then you would need to substring the "SK" part of the column, cast the number characters to numeric type and then order by that.
This is what #LONG replied to you in the first comment.
The way I would approach it is to create a couple of generic functions that will strip the alpha or numeric portions from the string before you sort.
In my example the functions are in the fn schema so change this as you require.
ORDER BY fn.StripToAlpha(PostCode), fn.StripToNumeric(PostCode)
There are plenty of examples of these types of functions around, probably more efficient than the ones I wrote but below is the code to produce the ones I use.
CREATE FUNCTION [fn].[StripToAlpha]
(
#inputString nvarchar(4000)
)
RETURNS varchar(4000)
AS
BEGIN
DECLARE #Counter as int
DECLARE #strReturnVal varchar(4000)
DECLARE #Len as int
DECLARE #ASCII as int
SET #Counter=0
SET #Len=LEN(#inputString)
SET #strReturnVal = ''
WHILE #Counter<=#Len
BEGIN
SET #Counter = #Counter +1
SET #ascii= ASCII(SUBSTRING(#inputString,#counter,1))
IF(#ascii BETWEEN 65 AND 90) OR (#ascii BETWEEN 97 AND 122)
BEGIN
SET #strReturnVal = #strReturnVal + (SUBSTRING(#inputString,#counter,1))
END
END
RETURN #strReturnVal
END
and
CREATE FUNCTION [fn].[StripToNumeric]
(
#inputString nvarchar(4000)
)
RETURNS Float
AS
BEGIN
DECLARE #Counter as int
DECLARE #strReturnVal varchar(4000)
DECLARE #ReturnVal Float
DECLARE #Len as int
DECLARE #ASCII as int
SET #Counter=0
SET #Len=LEN(#inputString)
SET #strReturnVal = ''
IF #inputString IS NULL
BEGIN
Return NULL
END
-- swap out comma for decimal
SET #inputString = REPLACE(#inputString, ',', '.')
IF #Len = 0 OR LEN(LTRIM(RTRIM(#inputString))) = 0
BEGIN
SET #ReturnVal=0
END
ELSE
BEGIN
WHILE #Counter<=#Len
BEGIN
SET #Counter = #Counter +1
SET #ascii= ASCII(SUBSTRING(#inputString,#counter,1))
IF(#ascii BETWEEN 48 AND 57) OR (#ascii IN (46,37))
BEGIN
SET #strReturnVal = #strReturnVal + (SUBSTRING(#inputString,#counter,1))
END
END
if RIGHT(#strReturnVal,1)='%'
BEGIN
SET #strReturnVal = LEFT(#strReturnVal,len(#strReturnVal)-1)
SET #strReturnVal = CAST((CAST(#strReturnVal AS FLOAT)/100) AS nvarchar(4000))
END
SET #ReturnVal = ISNULL(#strReturnVal,0)
END
RETURN #ReturnVal
END
Notes
This will not affect your current use but the StripToNumeric checks is a percentage sign is present and converts to a decimal so it you pass it 25% it will return 0.25.
This will not work if you use full postcodes such as SK1 1AB as it would sort by SKAB and then 11
It will work on postcodes with shorter prefixes such M34 (That's Denton if I remember correctly ! :) )
You didn't specify database you use; this is an Oracle example. Hopefully, you'll be able to "convert" it to something else.
The idea is: using regular expressions (which seem to be quite handy in such cases), split postcode to two parts: letters and numbers. As REGEXP_SUBSTR returns a string, I applied the TO_NUMBER function to a "numeric" part of the postcode in order to properly sort it.
SQL> with test (postcode) as
2 (select 'sk1' from dual union
3 select 'sk11' from dual union
4 select 'sk13' from dual union
5 select 'sk2' from dual
6 )
7 select postcode
8 from test
9 order by regexp_substr(postcode, '^[[:alpha:]]+'), --> letters
10 to_number(regexp_substr(postcode, '[[:digit:]]+$')); --> numbers
POST
----
sk1
sk2
sk11
sk13
SQL>
I am trying to extract a substring using the following query. I am not getting the expected result.
DECLARE #st1 varchar(10)
SET #st1 = 'Case # 00548295'
SELECT #st1,ltrim(SUBSTRING(#st1, CHARINDEX('# ', #st1) + 1, LEN(#st1)))
I am getting 005 as the answer. I am trying to extract the integer values after 'Case[space]#[Space]...". My result needs to be 00548295. What is wrong here
You define #st1 as varchar(10), but then try to assign 15 characters ('Case # 00548295') to it. SQL Server silently truncates the data in this case.
Simply increase the size:
DECLARE #st1 varchar(20)
SET #st1 = 'Case # 00548295'
SELECT #st1,ltrim(SUBSTRING(#st1, CHARINDEX('# ', #st1) + 1, LEN(#st1)))
As said by others, you need to increase your variable size. As for your query, instead of substringing out the numbers, you can simply get rid of 'Case # '
DECLARE #st1 varchar(20)
SET #st1 = 'Case # 00548295'
SELECT #st1, REPLACE(#st1, 'Case # ', '')
You did not give your varchar variable enough characters to hold the entire string to begin with.
DECLARE #st1 varchar(20)
SET #st1 = 'Case # 00548295'
SELECT #st1,ltrim(SUBSTRING(#st1, CHARINDEX('# ', #st1) + 1, LEN(#st1)))
-------------------- --------------------
Case # 00548295 00548295
(1 row(s) affected)
Your string value is varchar(10) which is at most 10 characters long. If you select out #st1, you will see it is truncated to 10 characters: 'Case # 005'. You will need to declare this as at least a varchar(15).
In addition to the varchar(15) issue already identified, you can also avoid the "ltrim" and parameterize the delimiter, as shown here:
DECLARE #st1 varchar(15)
declare #delimiter nvarchar(2) = '# ';
declare #delim_len int = DATALENGTH (#delimiter) / 2;
SET #st1 = 'Case # 00548295'
SELECT #st1, SUBSTRING(#st1, CHARINDEX(#delimiter, #st1) + #delim_len, LEN(#st1))
Or take it one step further, and make it into a SQL Function:
if object_id('UFN_STRINGAFTER') is not null
begin
drop function UFN_STRINGAFTER;
end
go
create function dbo.UFN_STRINGAFTER
(
#input nvarchar(max)
,#delimiter nvarchar(max) = '# '
)
returns nvarchar(max)
as
begin
declare #delim_len int = DATALENGTH (#delimiter) / 2;
return SUBSTRING(#input, CHARINDEX(#delimiter, #input) + #delim_len, LEN(#input));
end;
go
select dbo.UFN_STRINGAFTER('Case # 00548295',default);
I have used this site before for help with various things in the past, and in this instance, I couldn't find anything in the search box, so apologies if this exists elsewhere.
In sql server 2005, I have several stored procedures that change various bits of code, and recently we have created a function that adds spaces into a defined string. So in theory, I pass a string into it, and I get a result as blocks of 4. When I run this manually, and define the actual text, it splits fine (I get #### 0000 012 returned) but when I execute the function within the SP, I get #### 0012 0012. Is there any reason why?
I have set a print command to the string before it gets passed into my function, and it prints "####0000012 " and the print after is "#### 0012 0012"
Below is the function code, with no declares:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER function [dbo].[udf_addspaces](#string varchar(255),#lengthbetween int)
returns varchar(100)
as
BEGIN
declare #i int, #stringlen float, #output varchar(255), #outputofloop varchar(4)
set #stringlen = LEN(#string)/#lengthbetween
set #output =''
set #i = 0
while #i <= #stringlen
BEGIN
set #outputofloop = left(#string,#lengthbetween)
if #lengthbetween < LEN(#string)
BEGIN
set #string = right(#string,LEN(#string)-#lengthbetween)
END
set #output = #output + #outputofloop +' '
set #i = #i+1
END
return #output
END
Here is the bit of the SP that executes this:
set #Consignment2 = (#Consignment) + rtrim(#Check14)
print #Consignment2
set #Consignment2 = dbo.udf_addspaces(#Consignment2,4)
print #Consignment2
Here are the lines it prints: (Note: #### replaces a 4 digit number, removed for security reasons)
####0000012
#### 0012 0012
Regards,
Luke M
Even though you've defined stringlen as a float, it will be an integer value, because the two values you're dividing are ints.
There's a difference between a char(14) mentioned in your comments, to a varchar(14). The char(14) is guaranteed to be 14 characters long. The varchar may not be.
I think the body of your function could be more succinctly expressed as this...
declare #result varchar(500)
select #result = ''
select
#result = #result
+ substring(#string, number*#lengthBetween+1, #lengthBetween)
+ ' '
from master..spt_values
where type='p'
and number <= (len(#string)/#lengthBetween)
return rtrim(#result)
I need to replace a null character in a sql string, i cant seem to find the right command to achieve this. I have used replace (myString ,'\0', '') but this seems not to work, any help would be great
The trick that works is to COLLATE your value to Latin1_General_BIN before using REPLACE and also use nchar(0x00) COLLATE Latin1_General_BIN for string_pattern.
REPLACE ( string_expression , string_pattern , string_replacement )
select
[Terminated] = N'123' + nchar(0) + N'567'
,[Replaced with -] = REPLACE((N'123' + nchar(0) + N'567') COLLATE Latin1_General_BIN
, nchar(0x00) COLLATE Latin1_General_BIN
,'-')
,[Removed] = REPLACE((N'123' + nchar(0) + N'567') COLLATE Latin1_General_BIN
, nchar(0x00) COLLATE Latin1_General_BIN
,'')
Here is the result (use Output To Text):
Contains Replaced with - Removed
---------- ----------------- --------
123 567 123-567 123567
Use this:
REPLACE(myString, char(0), '')
These functions remove null characters from Unicode strings, at least in SQL Server 2008.
-- Remove all null characters
CREATE FUNCTION RemoveNulls(#s nvarchar(max))
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #r nvarchar(max);
SET #r = REPLACE(#s COLLATE Latin1_General_BIN, NCHAR(0), N'');
RETURN #r;
END
-- Remove all characters from the first null character
CREATE FUNCTION TrimNull(#s nvarchar(max))
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #r nvarchar(max);
DECLARE #i int = CHARINDEX(NCHAR(0), #s COLLATE Latin1_General_BIN);
IF #i = 0
SET #r = #s;
ELSE
SET #r = SUBSTRING(#s, 1, #i - 1);
RETURN #r;
END
-- Example usage
DECLARE #s nvarchar(10) = N'Test' + NCHAR(0) + N'!';
SELECT dbo.RemoveNulls(#s), dbo.TrimNull(#s);
--> Test!, Test
In my case, fields from ODBC were padded to 8000 characters with null and TrimNull was much faster than RemoveNulls.
For latin characters:
select REPLACE('Ho'+CHAR(0)+'mer' COLLATE SQL_Latin1_General_CP1_CS_AS, CHAR(0), '')
For russian characters:
select REPLACE(('Го'+CHAR(0)+'мер') COLLATE Cyrillic_General_BIN , CHAR(0), '')
If you Only have ASCII (Char/VarChar) strings then this will work as #DyingCactus suggests:
REPLACE(myString, Char(0x00), '')
However, if you are dealing with Null-Terminated Strings and are trying to fix or convert to something like XML, and your data is Unicode (nChar/nVarChar), then use this:
(CASE WHEN UNICODE(SUBSTRING(myString, LEN(myString), 1)) = 0x0000
THEN SUBSTRING(myString, 1, LEN(myString) - 1)
ELSE myString END)
This works for both ASCII (Char/VarChar) and Unicode (nChar/nVarChar).
Note
Using the Replace() function with Char(0) or nChar(0) will NOT work for Unicode (nChar/nVarChar).
It's a bug in the SQL Server Replace() function.
You could cast as VarChar, then use Replace(), but then you would lose any special Unicode/Non-ASCII characters you might have intended to keep.
Otherwise you wouldn't have used the Unicode datatype (that takes up twice as much space to store your data) in the first place.
If you have Null-Characters mixed in with your Unicode strings (and not only at the end), and, for the purposes of your query, maintaining Unicode-specific characters are unimportant, then as a last resort you could use this :
(CASE WHEN myString LIKE (N'%' + nCHAR(0x0000) + N'%')--Has Null-Character(s).
THEN REPLACE(CAST(myString as VarChar(MAX)), Char(0x00), '')--Cast as ASCII
ELSE myString END)--Else, leave as Unicode to preserve Unicode-Only chars.
I'm not completely sure what is wrong with your strings, but here are some things to try, are you using varchar?, edit question with more details:
if you have NULL characters within a string:
declare #x varchar(10)
set #x='123'+char(0)+'456'
SELECT #x AS Has_NULL_in_it, REPLACE(#x, char(0), '') AS Has_NULL_removed
OUTPUT:
Has_NULL_in_it Has_NULL_removed
-------------- ----------------
123 456 123456
(1 row(s) affected)
If you can't tell the character within the string, try this ASCII:
DECLARE #y varchar(10),#c int
set #y='123'+char(0)+'456'
set #c=0
WHILE #c<LEN(#y)
BEGIN
SET #c=#c+1
PRINT CONVERT(varchar(5),#c)+' - '+SUBSTRING(#y,#c,1)+' - CHAR('+CONVERT(varchar(5),ASCII(SUBSTRING(#y,#c,1)))+')'
END
OUTPUT:
1 - 1 - CHAR(49)
2 - 2 - CHAR(50)
3 - 3 - CHAR(51)
4 - - CHAR(0)
5 - 4 - CHAR(52)
6 - 5 - CHAR(53)
7 - 6 - CHAR(54)
try this unicode:
DECLARE #y nvarchar(10),#c int
set #y='123'+char(0)+'456'
set #c=0
WHILE #c<LEN(#y)
BEGIN
SET #c=#c+1
PRINT CONVERT(nvarchar(5),#c)+' - '+SUBSTRING(#y,#c,1)+' - UNICODE('+CONVERT(nvarchar(5),UNICODE(SUBSTRING(#y,#c,1)))+')'
END
if your have strings that are completely NULL:
declare #z varchar(10)
set #z=NULL
select #z AS IS_NULL, ISNULL(#Z,'') AS NULL_Removed
OUTPUT:
IS_NULL NULL_Removed
---------- ------------
NULL
(1 row(s) affected)
If you are concatenating values to get your string use IsNull(value, replacement) to avoid having null values or set CONCAT_NULL_YIELDS_NULL ON to avoid null strings as a result.
We had the same problem: Ending \0 character in nvarchar fields and unable to replace it with any of the REPLACE variants proposed (SQL Server 2008). When using
LEFT(Bar, LEN(Bar)-1)
it cut off the last regular character together with the \0 !
Our solution now to correct the fields is (as weird as it may seem on a first glimpse):
UPDATE Foo
SET Bar = LEFT(Bar, LEN(Bar))
WHERE RIGHT(Bar, 1) = CHAR(0)
Examples resolved
CREATE FUNCTION dbo.F_ReplaceNullChar( #STR NVARCHAR(MAX) )
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #i INT=0
DECLARE #RET NVARCHAR(MAX)=''
WHILE #I<LEN(#STR)
BEGIN
SET #i=#i+1
IF UNICODE(SUBSTRING(#STR,#i,1)) <> 0x0000
SET #RET=#RET+SUBSTRING(#STR,#i,1)
END
RETURN #RET
END
GO
SELECT LEN(mycol) lenbefore,mycol,
LEN( dbo.F_ReplaceNullChar(mycol)) lenafter, dbo.F_ReplaceNullChar(mycol) mycolafter
FROM mytab
select zz.xx
, replace(zz.xx, '', '')
from (
select
t.string_with_null,
(
select s.string_with_null+''
from TABLE_1 s
where s.token_hash = t.token_hash
for xml path('')
) xx
from TABLE_1 t(nolock)
)zz