May 18, 2007 at 3:16 pm
below a function I have been using in a App. Its quite similar to yours. Its fast as it only loops in memory and does not require temp tables. as usual usage examples are in the remarks on the top of the function. The input string size is 4000 characters, therefore it can only return an ascii string of 2000 characters ie 1/2 the size passed in. secondly, if the string passed in is of an odd length, it left pads a 0 before iterating so that it does not crash
create function Hex2String(@strHex as nvarchar(4000))
returns nvarchar(2000)
as
begin
/*
Function : Hex2String
Author : Pratap J Prabhu
Syntax :
> select dbo.Hex2String('6566676869')
> declare @strHexString nvarchar(4000)
declare @strReturnedString nvarchar(4000)
set @strHexString ='65666768'
set @strReturnedString = dbo.Hex2String(@strHexString)
print @strReturnedString
Purpose : passed a hexadecimal encoded string this function returns a Ascii string
Limitations : Max input Hex string size = 4000 characters
Max string size returned = 2000
*/
declare @cChar char(1)
declare @nDeciVal int
declare @cHexBit1 char(1)
declare @cHexBit2 char(1)
declare @strRetVal nvarchar(2000)
declare @iLoop smallint
set @strRetVal=''
set @strHex = ltrim(rtrim(isnull(@strHex,'')))
if @strHex<>''
begin
----- Hex numbers should always have a even length, so lets make it so
if (round(len(@strHex)/2,0)*2)<>len(@strHex)
set @strHex='0'+@strhex
----- Now loop down the length of the HexString handling 2 bits at a time
set @iLoop=1
while @iLoop <len(@strHex)
begin
set @cHexBit1=substring(@strHex, @iLoop , 1) -- The First Bit
set @cHexBit2=substring(@strHex, @iLoop+1, 1) -- the second bit
-- Convert the First Hex Bit value to its equivalent Decimal Value
-- Multiplying by 16 as its in the 10s place
if @cHexBit1>=0 and @cHexBit1<=9
set @nDeciVal=convert(int,@cHexBit1)*16
else
set @nDeciVal=convert(int,ASCII(@cHexBit1)-ASCII('A')+1) * 16
-- Convert the second Hex Bit value to its equivalent Decimal Value
-- do not Multiply by 16 as its in the units place
if @cHexBit2>=0 and @cHexBit2<=9
set @nDeciVal=@nDeciVal+convert(int,@cHexBit2)
else
set @nDeciVal=@nDeciVal+(ASCII(@cHexBit2)-ASCII('A')+1)
-- Store the Ascii Value
set @strRetVal=@strRetVal+ char(@nDeciVal)
set @iLoop=@iLoop+2 -- continue to the next character ie the next 2 Hex Bits
end
end
return (@strRetVal)
end
go
May 18, 2007 at 9:20 pm
Hey, Richard... just to let you know... I can normally beat most looping functions by using a Tally table... but not this one (least ways, not so far ). Your looping function is about 2 seconds faster that my Tally table solution on a 100,000 row test table. Nice job!
FYI... the direct fixed width solution I posted smokes both the Tally table and the looping inside the function by a full 10 out of 13 seconds.
--Jeff Moden
Change is inevitable... Change for the better is not.
May 19, 2007 at 3:53 am
How about this? No dynamic SQL, no (explicit) loop. Just a permanent lookup table.
====================================================================
declare @dec2hex1 table (
id smallint identity(0,1),
hex1 char(1)
)
insert into @dec2hex1 (hex1)
select top 16 null from sysobjects
update @dec2hex1
set hex1 = substring('0123456789abcdef',id+1,1)
create table dec2hex (
dec smallint identity(0,1),
hex char(2)
)
insert into dec2hex (hex)
select a.hex1+b.hex1
from @dec2hex1 a cross join @dec2hex1 b
order by 1
create unique index ix_dec2hex on dec2hex (hex, dec);
create function hex2alpha (
@input varchar(256)
) returns varchar(256)
begin
declare @output varchar(256)
set @output = ''
select @output = @output + char(b.dec)
from dec2hex a, dec2hex b
where b.hex = substring(@input,a.dec*2+1,2)
order by a.dec
return @output
end
select dbo.hex2alpha('416E6E61737461736961')
============================================
Jeff, can you run a performance test for this?
May 19, 2007 at 10:41 am
Sure thing, JH... I don't like the looks of that unconstrained cross-join in the function, though... I think it's going to gen 65,536 internal rows every time it executes...
I'll try without and with a limit in the criteria...
--Jeff Moden
Change is inevitable... Change for the better is not.
May 19, 2007 at 10:54 am
Ok... I was right about the cross-join... and it took more than 3 minutes to exec on 100,000 rows.
And when I change that snippet in the function to have a limit (see below), it still takes about 46 seconds (Richard's weigh's in at only 13).
select @output = @output + char(b.dec)
from dec2hex a, dec2hex b
where b.hex = substring(@input,a.dec*2+1,2)
AND a.dec*2+1 < LEN(@input)
order by a.dec
I think you're on the right track using a helper table to do this without a loop or dynamic SQL...
In case anyone else want's to do some testing on the problem, here's the code I used to gen the 100,000 row test table...
--===== Create and populate a 100,000 row test table.
SELECT TOP 100000
RowNum = IDENTITY(INT,1,1),
HexValue = '416E6E61737461736961'
INTO dbo.HexTest
FROM Master.dbo.SysColumns t1,
Master.dbo.SysColumns t2
--===== Add primary key
ALTER TABLE dbo.HexTest
ADD PRIMARY KEY CLUSTERED (RowNum)
--Jeff Moden
Change is inevitable... Change for the better is not.
May 19, 2007 at 1:53 pm
Thank you Jeff, I was too sleepy to run comparison tests myself last night.
Your input length condition is a nice touch.
But no Jeff, it not a cross-join nor generates 65,536 internal rows. It's a n inner join. You can see it if you check out its execution plan, and it can be replaced with:
----------------------------------------
select @output = @output + char(b.dec)
from dec2hex a inner join dec2hex b
on b.hex = substring(@input,a.dec*2+1,2)
where a.dec*2+1 < LEN(@input)
order by a.dec
----------------------------------------
without affecting its semantics at all.
And dec2hex table is looked-up just LEN(@output) times.(It was 256 times before you add the criteria, though.)
I think it's weird that Richard's function is still much faster. I thought the join routine of the server should be most efficient. Moreover, my function calls substring() half the times of Richard's.
Anyway, it was a very interesting quiz. Thank you all!
May 19, 2007 at 6:06 pm
Heh... and I was too sleepy this morning... I shouldn't post when I'm pooped...
It is still a bit of a half cross join (more specifically, a triangular join and does an index scan of 76 rows) but it certainly isn't 65,536. My mistake. That would also explain why Richard's is still faster...
--Jeff Moden
Change is inevitable... Change for the better is not.
May 22, 2007 at 6:17 am
Another function for the mix:
if objectproperty (object_id ('dbo.fn_HexToAlpha'), 'istablefunction') = 0 drop function dbo.fn_HexToAlpha
go
create function dbo.fn_HexToAlpha (@HexString char (40))
returns varchar (20) as
begin
declare @Loop tinyint, @AsciiString varchar (20), @Nibble1 char (1), @Nibble2 char (1)
select @AsciiString = '', @Loop = 1
while @Loop < len (@HexString)
begin
select @Nibble1 = upper (substring (@HexString, @Loop, 1)), @Nibble2 = upper (substring (@HexString, @Loop + 1, 1))
set @AsciiString = @AsciiString + char (16 * case @Nibble1 when 'A' then 10 when 'B' then 11 when 'C' then 12 when 'D' then 13 when 'E' then 14 when 'F' then 15 else @Nibble1 end + case @Nibble2 when 'A' then 10 when 'B' then 11 when 'C' then 12 when 'D' then 13 when 'E' then 14 when 'F' then 15 else @Nibble2 end)
set @Loop = @Loop + 2
end
return @AsciiString
end
go
select dbo.fn_HexToAlpha (HexString) from #HexToAlpha
Eddie Lee.
May 22, 2007 at 8:39 am
I ran mine and Richard's functions on Jeffs test data (pushed up to 1000000 rows) and my function above (at 00:01:25.603) just pipped Richards (at 00:01:57.740).
alter table #HexTest add ASCIIValue varchar (20)
go
if object_id ('tempdb.dbo.#TimeTrial') is not null drop table #TimeTrial
create table #TimeTrial (Event varchar (20), DateStamp datetime default getdate ())
update #HexTest set ASCIIValue = null -- reset
insert #TimeTrial (Event) values ('RichardStart')
update #HexTest set ASCIIValue = dbo.f_hextostr (HexValue)
insert #TimeTrial (Event) values ('RichardFinish')
select * from #HexTest
update #HexTest set ASCIIValue = null -- reset
insert #TimeTrial (Event) values ('EddieStart')
update #HexTest set ASCIIValue = dbo.fn_HexToAlpha (HexValue)
insert #TimeTrial (Event) values ('EddieFinish')
select * from #HexTest
select (select DateStamp from #TimeTrial where Event = 'RichardFinish') - (select DateStamp from #TimeTrial where Event = 'RichardStart') Richard
select (select DateStamp from #TimeTrial where Event = 'EddieFinish') - (select DateStamp from #TimeTrial where Event = 'EddieStart') Eddie
Eddie.
May 22, 2007 at 9:45 am
Hey Eddie!
I like this conversation very much!
Try this with only 100.000 recs!
if object_id ('tempdb.dbo.#TimeTrial') is not null drop table #TimeTrial
create table #TimeTrial (id integer identity(1,1), Event varchar (20), datestart datetime default getdate (), dateend datetime null)
declare @counter integer
set @counter=1
while @counter<13
begin
insert #TimeTrial (Event) values ('Richard')
update HexTest set ASCIIValue = dbo.f_hextostr (HexValue)
update #TimeTrial set dateend=getdate() where id=@counter
set @counter=@counter+1
insert #TimeTrial (Event) values ('Eddie')
update HexTest set ASCIIValue = dbo.fn_HexToAlpha (HexValue)
update #TimeTrial set dateend=getdate() where id=@counter
set @counter=@counter+1
end
select *, dateend-datestart as diff from #TimeTrial order by id
The result:
1 Richard 00:00:17.333
2 Eddie 00:00:08.283
3 Richard 00:00:06.610
4 Eddie 00:00:08.010
5 Richard 00:00:06.420
6 Eddie 00:00:08.000
7 Richard 00:00:06.520
8 Eddie 00:01:02.360
9 Richard 00:00:06.460
10 Eddie 00:00:07.780
11 Richard 00:00:06.510
12 Eddie 00:00:07.803
Conclusions: The first runs (1-2) must be eliminated, our data not in cash yet. The runs 7-8 must be eliminated, Your result totally differs from others ( really sometimes I don't know, what the hell is running on background), the rest results are comparable.
By the way: our functions are able to run faster, if:
You eliminate the UPPER function (suppose the HEX string is correct);
I eliminate the IF part (suppose the HEX string is correct)
Best regards: Richard
May 23, 2007 at 7:31 am
Richard yes, your loop does prove your fn to be a bit quicker. Weird how the run time of mine fluctuates so?!
Ho hum.
Good chatting.
Regards, Eddie.
May 28, 2007 at 4:15 pm
I have found using STUFF to create the output to be a little bit faster. I am assuming that it is due to traditional string concantination issues...
ALTER FUNCTION hex2alpha( @input VARCHAR(256) ) RETURNS VARCHAR(256) AS BEGIN DECLARE @output VARCHAR(256); SET @output = SPACE(LEN(@input)/2) SELECT @output = STUFF(@output,a.dec+1,1,CHAR(b.dec)) FROM dec2hex AS a INNER JOIN dec2hex AS b ON b.hex = SUBSTRING(@input,a.dec*2+1,2) WHERE a.dec < LEN(@input)/2 ORDER BY a.dec RETURN @output END
June 28, 2007 at 7:09 pm
Firstly my apologies for not responding sooner
Secondly, thank you all for the marvellous responses and ensuing competition. Sadly I must now say my original SQL was a very poor contender over 1,000,000 records, and some of the alternatives, well let's just say WOW!
I have really learned some stuff from you people, so much appreciated.
Stephen
June 28, 2007 at 11:02 pm
Thanks for coming back with that... lots of us end up wondering if we actually helped or confused. Really appreciate the feedback even if it is a bit later than most.
--Jeff Moden
Change is inevitable... Change for the better is not.
April 25, 2008 at 4:32 am
Surely the real lesson from this should be that if something is this hard in SQL you should be doing it in a different language?
I rarely come across an application that is written purely in SQL, so there is usually a presentation layer that is more capable of handling what looks like a purely presentational issue.
Viewing 15 posts - 16 through 30 (of 31 total)
You must be logged in to reply to this topic. Login to reply