Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 General SQL Server Forums
 Script Library
 Alphabetical ordering of numeric strings

Author  Topic 

rrb
SQLTeam Poet Laureate

1479 Posts

Posted - 2007-03-27 : 20:45:12
Apologies if this has been done before, but I couldn't find a completed example. If anyone has time, I'd love to see some improvements...

Where's fribble when you need him?
/*
function:
numeric_order

arguments:
@numeric_string - a string of mixed alpha and numeric values
@max_digits - the maximum length of digits to compare

description:
Function numeric_order creates an orderable string based on the "numeric" value of @numeric_string
which can be ordered alphabetically.

Ideally the strings should really be broken up into constituent parts and ordered properly,
but occasionally you come across data where it's just not worth the while.

eg
select title from regulations order by title
returns:

Regulation 1(a) section 3
Regulation 11 section 100(b)
Regulation 11 section 2(c)(iii)
Regulation 2 section 1
Regulation 21 section 3 (b)

whereas
select title from regulations order by dbo.numeric_order(title,10)
returns:

Regulation 1(a) section 3
Regulation 2 section 1
Regulation 11 section 2(c)(iii)
Regulation 11 section 100(b)
Regulation 21 section 3 (b)

which is the order most users would expect.

NOTE: because the original strings are mixed, the user may include alphas between digits which are to be
sorted alphabetically, which means the string must keep all alpha parts of the original string as is.

expected output:
select dbo.numeric_order('Part 1(b) subsection 1',4)
returns
Part 0001(b) subsection 0001

test data:
use test
--go

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[regulations]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[regulations]
--go

create table regulations (id int identity(1,1), title varchar(200))
--go

insert into regulations (title) select 'Regulation 1(a) section 3'
insert into regulations (title) select 'Regulation 11 section 100(b)'
insert into regulations (title) select 'Regulation 11 section 2(c)(iii)'
insert into regulations (title) select 'Regulation 2 section 1'
insert into regulations (title) select 'Regulation 21 section 3 (b)'
--go

select title as [Incorrectly Ordered] from regulations order by title
--go

select title as [Correctly Ordered] from regulations order by dbo.numeric_order(title, 10)
--go

drop table [dbo].[regulations]
--go

*/


if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[numeric_order]') and xtype in (N'FN', N'IF', N'TF'))
drop function [dbo].[numeric_order]
go

create function [dbo].[numeric_order](
@numeric_string as varchar(1000),
@max_digits as int)
returns varchar(8000) as
begin
declare @return varchar(8000)

declare @part varchar(1000)
declare @digit_position int
declare @rest varchar(1000)
declare @non_digit_position int
declare @numeric_term varchar(1000)
declare @after varchar(1000)
declare @between varchar(1000)
declare @first_time int
declare @digits varchar(100)

--create a string of zeros equal in length to max number of digits of enclosed numeric values
set @digits = replace(space(@max_digits),' ','0')

--handle length issues
--worst case scenario is a number every second character multiplied by padlen < 8000
--which would potentially add len(@numeric_string)/2 * padlen characters - so subtract this from the string
set @part = left(@numeric_string, ((len(@numeric_string)/2) * @max_digits))

--starting values
set @non_digit_position = 0
set @first_time = 1
set @return = ''

--loop while not at end of string
while ((@non_digit_position > 0 or @first_time > 0) and (len(@part) > 0))
begin
--if there are digits in the string
set @digit_position = patindex('%[0-9]%', @part)
if @digit_position > 0
begin
--get the part of the string after the first digit
set @rest = substring(@part, patindex('%[0-9]%', @part) + 1, len(@part)-patindex('%[0-9]%', @part) + 1)
set @non_digit_position = patindex('%[^0-9]%',@rest)

--extract the string of digits
set @numeric_term = case
when @non_digit_position > 0 then substring(@part, @digit_position, @non_digit_position)
else substring(@part, @digit_position, len(@part) - @digit_position + 1)
end

--keep track of the rest of the string after and between the digits
set @after = substring(@part, @digit_position + len(@numeric_term), len(@part) - @digit_position + @non_digit_position)
set @between = '' + substring(@part, 1, @digit_position - 1)

--build return string
set @return = @return + @between + right(@digits + @numeric_term, @max_digits)
end
else
begin
--no more digits, just add back the rest of the original string
set @return = @return + @part
set @after = ''
end

--iterate
set @first_time = 0
set @part = @after
end

return @return

end
go


--
I hope that when I die someone will say of me "That guy sure owed me a lot of money"

Fantasiiio
Starting Member

1 Post

Posted - 2007-04-19 : 15:17:55
hmmm.. yeah.. if you need something less complicated, try this

CREATE function [dbo].[numeric_order](
@numeric_string as varchar(1000),
@max_digits as int)
returns varchar(8000) as
begin
declare @return varchar(8000)

declare @PaddingChar AS NVARCHAR(1000)
set @PaddingChar = ''

declare @i as integer
set @i = 0

while (@i < @max_digits)
begin
set @PaddingChar = @PaddingChar + '0'
set @i = @i + 1
end

set @return = SUBSTRING(@PaddingChar, 0, @max_digits - LEN(@numeric_string)) + @numeric_string

return @return

end

Go to Top of Page

rrb
SQLTeam Poet Laureate

1479 Posts

Posted - 2007-04-19 : 19:43:55
Hi Fantasiiio

Not meaning to sound pedantic, but just in case others are looking for a solution - note that your code only works for a single numeric value in the string - not for repeated numerics (as described in the comments above the code), and it also fails when numeric_string is longer than max_digits. (Note the test data included in the code comments).

However, I'd love to see an improvement on the code I posted above if you have the time to work on it. I'm not a great coder and I couldn't work out how to aviod passing in the max_digits parameter without the code getting a lot longer and more complicated - that's where we need fribble! - I was going to scan through the string first to work out the longest numeric, but I wasn't sure about the hit on resources and whether this was overkill, since you won't know in advance how many numerics there could be in a string.

It would be really cool not to have to pass in the max_digits parameter because then the function would work in the general case, without needing to know anything about what was contained.

Anyhow, thanks for posting back

there once was a yak named fantasiiio
who coded away in her studio
she laughed and she winked
and her code was succinct
and what it lacked, she made up for, with enthusio!

(Sorry if you're not a "her" - couldn't tell from your profile)

Cheers

PS, It's Grumpy-day here in Oz.

--
I hope that when I die someone will say of me "That guy sure owed me a lot of money"
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2007-04-20 : 02:32:17
I haven't had the time to compare speed.
declare @regulations table (id int identity(1,1), title varchar(200))

insert into @regulations (title) select 'Regulation 1(a) section 3'
insert into @regulations (title) select 'Regulation 11 section 100(b)'
insert into @regulations (title) select 'Regulation 11 section 2(c)(iii)'
insert into @regulations (title) select 'Regulation 2 section 1'
insert into @regulations (title) select 'Regulation 21 section 3 (b)'

select * from @regulations

select title
from (
SELECT title,
dbo.fnfilterstring(title, '%[0-9]%', '#', 1) as data
from @regulations
) as d
order by cast(dbo.fnparsestring(-1, '#', data) as int),
cast(dbo.fnparsestring(-2, '#', data) as int)
with references to
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=76033
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=79083


Peter Larsson
Helsingborg, Sweden
Go to Top of Page

rrb
SQLTeam Poet Laureate

1479 Posts

Posted - 2007-04-20 : 02:55:16
Hey Peso

That's looks like a much neater solution, but it doesn't quite work for me - am I using it wrong?

declare @regulations table (id int identity(1,1), title varchar(200))

insert into @regulations (title) select 'Regulation 15(3)'
insert into @regulations (title) select 'Regulation 15(4)'
insert into @regulations (title) select 'Regulation 15A(3)'
insert into @regulations (title) select 'Regulation 15B'
insert into @regulations (title) select 'Regulation 15C'
insert into @regulations (title) select 'Regulation 15D(1)'
insert into @regulations (title) select 'Regulation 15E(3)'
insert into @regulations (title) select 'Regulation 15E(4)'
insert into @regulations (title) select 'Regulation 15E(5)'

I'm getting:

Regulation 15B
Regulation 15C
Regulation 15D(1)
Regulation 15E(3)
Regulation 15(3)
Regulation 15A(3)
Regulation 15(4)
Regulation 15E(4)
Regulation 15E(5)

Rather than:

Regulation 15(3)
Regulation 15(4)
Regulation 15A(3)
Regulation 15B
Regulation 15C
Regulation 15D(1)
Regulation 15E(3)
Regulation 15E(4)
Regulation 15E(5)




there once was a yak in australia
who tried very hard to regale ya*
with ordering code
from his southern abode
but it turned out that he was a failure

*(ya is aussie for "you" as in "ya ijut")

PS - I did some speed comparisons of my table of only 690 records.

I tried both solutions together and then swapped the order and tried again (just in case there were caching issues.) I'm no expert at benchmarking and what precise caching sql does...but I basically just put a getdate() before and after each call.

Here's my results:

test 1:
your solution (run first) - 563 ms
my solution (run second) - 47 ms

test2:
my solution (run first) - 47 ms
your solution (run second)- 563ms

so then (because I was waiting for my .NET project to compile) I tried it again with 690000 records and got these:

test 3:
my solution (run first) - 223.747 s
your solution (run second)- 691.910 s

test 4:
your solution (run first) - 588.440 s
my solution (run second) - 114.733 s

I guess my .NET solution finished compiling during the second test...anyhow - FYI

Cheers

--
I hope that when I die someone will say of me "That guy sure owed me a lot of money"
Go to Top of Page

SwePeso
Patron Saint of Lost Yaks

30421 Posts

Posted - 2007-04-20 : 06:21:56
Add TITLE as third sort argument, as I only sorts by the numeric values.


Peter Larsson
Helsingborg, Sweden
Go to Top of Page

rrb
SQLTeam Poet Laureate

1479 Posts

Posted - 2007-04-20 : 20:02:54
Cool

Thanks

--
I hope that when I die someone will say of me "That guy sure owed me a lot of money"
Go to Top of Page

jezemine
Master Smack Fu Yak Hacker

2886 Posts

Posted - 2007-04-20 : 20:18:35
quote:
Originally posted by rrb
there once was a yak in australia
who tried very hard to regale ya*
with ordering code
from his southern abode
but it turned out that he was a failure



now I see why they call ya the poet laureate


www.elsasoft.org
Go to Top of Page

rrb
SQLTeam Poet Laureate

1479 Posts

Posted - 2007-04-20 : 20:25:27
My highpoint - [url]http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=21054&SearchTerms=sql,night,elf[/url]

The curse of the SQL Night Elf.

There was-a-yak from Epsom, in Surrey
Who appeared to be in somewhat of a hurry
He was bored with his tag
"Starting Member? 's such a drag!"
"I'll give these SQLers some curry!"

Now he noticed that titles were assigned
based on how many answers one could find
so he posted, alright
posted all day and all night
Till his post count read nine-ninety-nine

But his luck ran out the very next day
For while he'd been posting away
His boss (in frustration)
Had seen no data migration
And sacked him without any pay

So forever he will no doubt remain
The yak with an automated name
The tale of his curse
Now recited in verse
Warns us all to avoid seeking fame.

Oh yeah - and it was all true (well except for the bits about him being stuck on 999 and being sacked by his boss - and probably most of the rest...)

- ah, the sql night elf - now that is a story!

--
I hope that when I die someone will say of me "That guy sure owed me a lot of money"
Go to Top of Page

jezemine
Master Smack Fu Yak Hacker

2886 Posts

Posted - 2007-04-22 : 03:15:11
all that and the poor guy didn't get his custom title...

...but you did!


www.elsasoft.org
Go to Top of Page
   

- Advertisement -