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>
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 am new to Stored Procedures, I am using MSSMS
So what I want is I have a table aItemPackingDetail and I have an sp to insert values into the table
ALTER PROCEDURE [dbo].[spInsertaItemPackingDetail]
#ItemID INT
,#PackingTypeID INT
,#PackingSlNo INT
,#PackingBarCode VARCHAR(25)
,#active BIT
AS
BEGIN
INSERT INTO aItemPackingDetail (
ItemID
,PackingTypeID
,PackingSlNo
,PackingBarCode
,active
)
VALUES (
#ItemID
,#PackingTypeID
,#PackingSlNo
,#PackingBarCode
,#active
)
END
As you can see this is a simple sp for insert. Then I got an update to do. If #PackingBarCode is null or empty I need to create a barcode in sp and save it.
The format of barcode needs to be like
#ItemID+ Zeros +#PackingTypeID // length needs to be 10
ie. if
#ItemID = 1212 and #PackingTypeID = 4521 then #PackingBarCode
needs to be#PackingBarCode
= 1212004521` so on.
And if length of #ItemID+#PackingTypeID >= 10 then dont add any zeros.
This as far as I did, I am stuck and no idea how to complete please help.
ALTER PROCEDURE [dbo].[spInsertaItemPackingDetail]
#ItemID INT
,#PackingTypeID INT
,#PackingSlNo INT
,#PackingBarCode VARCHAR(25)
,#active BIT
AS
BEGIN
-- declare variables to store ount ant zeros
DECLARE #ItemIDcount AS INT
DECLARE #PackingTypeIDCount AS INT
DECLARE #noofZeros AS INT
DECLARE #Barcode AS INT
-- find count of ids
SET #ItemIDcount = LEN(#ItemID)
SET #PackingTypeIDCount = LEN(#PackingBarCode)
-- how many zeros needed
IF (#ItemIDcount + #PackingTypeIDCount) < 10
BEGIN
SET #noofZeros = 10 - (#ItemIDcount + #PackingTypeIDCount)
END
-- if #PackingBarCode becomes null or empty
IF #PackingBarCode = NULL OR #PackingBarCode = ''
BEGIN
SET #PackingBarCode = -- how to create barcode??
END
INSERT INTO aItemPackingDetail (
ItemID
,PackingTypeID
,PackingSlNo
,PackingBarCode
,active
)
VALUES (
#ItemID
,#PackingTypeID
,#PackingSlNo
,#PackingBarCode
,#active
)
END
You can do it like this:
DECLARE #ItemID INT = 1212
, #PackingTypeID INT = 4521
SELECT CASE
WHEN LEN(#ItemID) + LEN(#PackingTypeID) < 10
THEN CONCAT(#ItemID, RIGHT(CONCAT('0000000000', #PackingTypeID), 10 - LEN(#ItemID)))
ELSE CONCAT(#ItemID, #PackingTypeID)
END AS BarCode
Result: 1212004521
The idea is basically to stick 10 0's in front of #PackingTypeID, and only taking those you need.
Here is a simple solution:
DECLARE
#ItemID INT = 1212,
#PackingTypeID INT = 4521
SELECT CAST(#ItemID AS VARCHAR)
+ REPLICATE('0', 10 - (LEN(CAST(#ItemID AS VARCHAR)) + LEN(CAST(#ItemID AS VARCHAR))))
+ CAST(#PackingTypeID AS VARCHAR)
Another approach (apart from concatenating strings) is to solve the problem mathematically, using base 10 logarithms to find the number of digits in each number.
Essentially, you want to multiply the first number by 10 until the number is 10 digits long, and then add the second number. The padding zeroes will automatically be where you want them to be. The only case to cover separately is when the total length is already 10 or greater.
BEGIN
DECLARE
#ItemID BIGINT = 1212,
#PackingTypeID BIGINT = 4521
SELECT
CASE
WHEN FLOOR(LOG10(#ItemID))+FLOOR(LOG10(#PackingTypeID)) >= 8
THEN CONVERT(varchar, #ItemID * POWER(10,FLOOR(LOG10(CAST(#PackingTypeID AS FLOAT)))+1) + #PackingTypeID)
ELSE
CONVERT(varchar, #ItemID * POWER(10,9-FLOOR(LOG10(CAST(#ItemID AS FLOAT)))) + #PackingTypeID)
END
END;
GO
EDIT
Note that the above will preserve the two numbers, regardless of their length, as per the following:
#ItemID = 12345
#PackingTypeID = 67891
--> 1234567891
#ItemID = 12345
#PackingTypeID = 678912
--> 12345678912
#ItemID = 1234
#PackingTypeID = 6789
--> 1234006789
If you want either of the numbers truncated (see case 2 above), to arrive at a maximum of 10 digits, then you probably need some more logic added. However, if the sum of the digits will never be greater than 10 (before the zeroes are added), then this is a moot point.
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 have a column of hexadecimal values in a table. I want to add a hex value to all values in that table. If it were a simple int I would run something like this:
UPDATE myTable
SET num = num + 4000
Is there any way to do this simply using hexadecimal arithmetic? Or do I have to convert the column value to decimal, convert the value I want to add to decimal, add them, and convert the value back to hex? (And if so, what's the simplest way to do that?)
(NOTE: We are currently using sql server 2000.)
use something like :
print convert(varbinary(4),0 + 0x002E + 0x001D)
it should give you a result like :
0x0000004B
the zero in the equation fools it to believe its all numbers so it calculates the value.
Assuming that num is actually a string representation of the hexadecimal number, I think you can convert it to an integer by using a couple of User Defined Functions:
-- Based on Feodor's solution on
-- http://blog.sqlauthority.com/2010/02/01/sql-server-question-how-to-convert-hex-to-decimal/
CREATE FUNCTION fn_HexToInt(#str varchar(16))
RETURNS BIGINT AS BEGIN
SELECT #str=upper(#str)
DECLARE #i int, #len int, #char char(1), #output bigint
SELECT #len=len(#str),#i=#len, #output=case WHEN #len>0 THEN 0 END
WHILE (#i>0)
BEGIN
SELECT #char=substring(#str,#i,1)
, #output=#output
+(ASCII(#char)
-(case when #char between 'A' and 'F' then 55
else case when #char between '0' and '9' then 48 end
end))
*power(16.,#len-#i)
, #i=#i-1
END
RETURN #output
END
-- Example conversion back to hex string - not very tested
CREATE FUNCTION fn_IntToHex(#num int)
RETURNS VARCHAR(16) AS BEGIN
DECLARE #output varchar(16), #rem int
SELECT #output = '', #rem=0
WHILE (#num > 0)
BEGIN
SELECT #rem = #num % 16
SELECT #num = #num / 16
SELECT #output = char(#rem + case when #rem between 0 and 9 then 48 else 55 end) + #output
END
RETURN #output
END
select dbo.fn_HexToInt ('7FFF') -- = 32767
select dbo.fn_IntToHex(32767) -- = 7FFF
So you can try
UPDATE myTable
SET num = dbo.fn_IntToHex(dbo.fn_HexToInt(num) + 4000)
You can use the prefix 0x
eg
Select 0x3F + 2
returns 65
So
UPDATE myTable
SET num = num + 0x4000
(This works in SQL 2008 - I'm not sure if it's new since SQL 2000 - let me know!)
If you have two 0x values, they get concatenated by the + operator, so use convert to convert one of them to an int
I am hoping someone can help me here as google is not being as forthcoming as I would have liked. I am relatively new to SQL Server and so this is the first function I have set myself to do.
The outline of the function is that it has a Phone number varchar(15) as a parameter, it checks that this number is a proper number, i.e. it is 8 digits long and contains only numbers. The main character I am trying to avoid is '+'. Good Number = 12345678 Bad Number = +12345678. Once the number is checked I would like to produce a random number for each phone number that is passed in.
I have looked at substrings, the like operator, Rand(), left(), Right() in order to search through the number and then produce a random number. I understand that Rand() will produce the same random number unless alterations are done to it but right now it is about actually getting some working code. Any hints on this would be great or even point me towards some more documentation. I have read books online and they haven't helped me, maybe I am not looking in the right places.
Here is a snippet of code I was working on the Rand
declare #Phone Varchar (15)
declare #Counter Varchar (1)
declare #NewNumber Varchar(15)
set #Phone = '12345678'
set #Counter = len(#Phone)
while #Counter > 0
begin
select case when #Phone like '%[0-9]%' then cast(rand()*100000000 as int) else 'Bad Number' end
set #counter = #counter - 1
end
return
Thanks for the help in advance
Emer
Simply use LIKE and ensure each digit is between 0 and 9.
One way to generate random numbers is CHECKSUM(NEWID()), or use this as the seed for RAND
IF #phone LIKE '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
SELECT #NewNumber = LEFT(
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)), 15)
Or the double negative LIKE with length check
IF #phone NOT LIKE '%[^0-9]%' AND LEN(#phone) = 8
SELECT #NewNumber = LEFT(
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)) +
CAST(ABS(CHECKSUM(NEWID())) AS varchar(10)), 15)
I thought I would update my post with the solution I have come up with for other people who may be searching for something similar. From my research you are unable to use RAND() within a UDF. Instead you have to create a view and call it from that view.
Create Function [dbo].[AlterPhone](#Phone Varchar(15))
Returns varchar (15)
AS
BEGIN
declare #Counter int
declare #NewNumber varchar(15)
set #NewNumber = 0
select #NewNumber = case when len(#Phone)=8 and isnumeric(#Phone) = 1
then (select RandValue from dbo.vw_RandomVarchar) else 'Bad Number' end
return #NewNumber
END
/*
CREATE VIEW [dbo].[vw_RandomVarchar]
AS
SELECT cast(cast(rand()*100000000 as int)as varchar) AS RandValue
END
SELECT dbo.AlterPhone(12345678)
*/