April 25, 2006 at 11:30 am
Hello,
I searched for this but couldnt find a solution. I need a way to increment a varchar field automatically. This field is a varchar(17) and holds both alpha and numeric characters. It is basically an identity field, and needs to be unique throughout the db, but was not created to enforce uniqueness (Not my database design). My values look like this "000000000000002Ww", the increment is first the numeric values, then lowercase alpha, followed by uppercase. 0-9 then lowercase "a", capital "A", lowercase "b" and so on. I am not an expert yet so if this has been answered elsewhere please point me in the right direction.
Here is a sample sequence.
000000000000002W9
000000000000002Wa
000000000000002WA
000000000000002Wb
Thank you.
April 25, 2006 at 11:45 am
is your database case sensative ?
Amit Lohia
April 25, 2006 at 11:49 am
Hi Aaron,
You request reminded me of this article...
http://www.sqlteam.com/item.asp?ItemID=1417
Ryan Randall
Solutions are easy. Understanding the problem, now, that's the hard part.
April 25, 2006 at 11:51 am
Yes. Basically this field is a Base 62 number that I need to auto-increment. I have not found a way to do this yet without some external programming, which is not available for my database.
April 25, 2006 at 12:07 pm
This Base64 encode and decode was posted here a while ago. It might have something you can use.
http://www.sqlservercentral.com/scripts/viewscript.asp?scriptid=1520
April 25, 2006 at 12:20 pm
Thank you Ryan and David, I will dig into the links. Wish me luck. If you can think of anything else I'll keep checking.
Again thank you.
April 25, 2006 at 12:22 pm
Good luck
Ryan Randall
Solutions are easy. Understanding the problem, now, that's the hard part.
April 25, 2006 at 12:27 pm
Well - I will certainly wish you luck for I think you could use all that you can get..
so you'd have to sort first on this varchar field to get max(id) before incrementing...how are "gaps" handled in a case like this ?!?!
**ASCII stupid question, get a stupid ANSI !!!**
April 25, 2006 at 1:40 pm
The first thing I have to say is a Sequence is an object while Identity is a property, so here is a Microsoft Provided solution. I think the Sequence is stored in a row which may relate to the differences of a property and an object I am not sure. Both are defined in ANSI SQL, but Oracle and Microsoft implemented only one and not both. Hope this helps.
http://blogs.msdn.com/sqlcat/archive/2006/04/10/572848.aspx
Kind regards,
Gift Peddie
Kind regards,
Gift Peddie
April 25, 2006 at 10:19 pm
Hi,
I just posted this article on this wonderfull site, not sure if it helps you 100%! However, I do hope it will sure give you some ideas to nail down your problem.
http://www.sqlservercentral.com/columnists/aSayed/generatingasequentialpattern.asp
Regards,
Asif Sayed
April 26, 2006 at 12:46 am
First, create a translation table... here's the code...
--===== Create the translation table and seed with
-- 62 numbers from 0 to 61
-- (10 digits + 26 Lower Case + 26 Upper Case)
SELECT TOP 62
IDENTITY(TINYINT,0,1) AS Number,
CAST(NULL AS CHAR(1)) AS Letter
INTO dbo.Translate
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
--===== Create the lower case letters
UPDATE dbo.Translate
SET Letter = CHAR(ASCII('a')+(Number/2)-5)
WHERE Number%2 = 0
--===== Create the upper case letters
UPDATE dbo.Translate
SET Letter = CHAR(ASCII('A')+((Number-1)/2)-5)
WHERE Number%2 = 1
--===== Create the numbers 0-9
UPDATE dbo.Translate
SET Letter = STR(Number,1)
WHERE Number BETWEEN 0 AND 9
--===== Update the AsciiVal column
UPDATE dbo.Translate
SET AsciiVal = ASCII(Letter)
...then, play with this... when you're sure that it's working correctly, turn it into a function.
DECLARE @Number DECIMAL(38)
SET @Number = 9999999999999999999999999999999 --largest possible number for this
DECLARE @Counter DECIMAL(38)
SET @Counter = 17
DECLARE @String VARCHAR(18)
SET @String = ''
DECLARE @Letter CHAR(1)
WHILE @Counter >= 0
BEGIN
SELECT @String = @String + Letter
FROM Translate
WHERE Number = FLOOR(CAST(@Number/POWER(62.0,@Counter) AS FLOAT))
SELECT @Number =
CASE
WHEN @Number - (FLOOR(CAST(@Number/POWER(62.0,@Counter) AS FLOAT)))*POWER(62.0,@Counter) > 0.0
THEN @Number - (FLOOR(CAST(@Number/POWER(62.0,@Counter) AS FLOAT)))*POWER(62.0,@Counter)
ELSE @Number
END
SET @Counter = @Counter-1
END
SELECT @String
Rather than trying to increment such a strange numbering set, maintain a decimal number somewhere, increment that, then use the code above to convert it to what you wanted it to look like.
Next... kill the stupid SOB that designed this numbering set.
Finally, send me a case of beer because I already have enough pretzels.
By, the way, tell the dummy that designed the numbering system that it goes out to 18 characters, not 17.
Gimme some feedback on this one, please... it took a while to science it out and make it so it looks simple.
--Jeff Moden
Change is inevitable... Change for the better is not.
April 26, 2006 at 8:06 am
Great work, Jeff. I agree with your recommended approach, and that it's crazy design.
For fun, I thought I'd try to write it without the while loop...
--inputs
declare @x decimal(38)
set @x = 38555935228647999764
--set @x = 11107
--set @x = 9999999999999999999999999999999
--calculation
declare @numbers table (i int identity(0, 1), x bit)
insert @numbers select top 62 null from master.dbo.syscolumns a
declare @powers table (i int, p decimal(38, 0))
insert @powers select i, power(62.0, i) from @numbers where i < 21
declare @MaxCharacters int
select @MaxCharacters = isnull(max(i), -1) from @powers where @x >= p
declare @s-2 varchar(18) --the base 62 string we're going to construct
set @s-2 = replicate('0', 17 - @MaxCharacters)
declare @d int --divisor
select
@d = isnull(@x / p, 0),
@x = @x - p * @d,
@s-2 = @s-2 + case when @d BETWEEN 0 AND 9 then STR(@d,1)
when @d % 2 = 0 then CHAR(ASCII('a') + (@d / 2) - 5)
when @d % 2 = 1 then CHAR(ASCII('A') + ((@d - 1) / 2) - 5)
end
from @powers p where i <= @MaxCharacters order by i desc
select @s-2
BTW, Jeff - 62 (for example) doesn't quite work for you, although it's pretty trivial to fix. And I get a slightly different result for you for 999... - and I'm not sure which is right (or why)
Ryan Randall
Solutions are easy. Understanding the problem, now, that's the hard part.
April 26, 2006 at 8:50 am
Use this function in a computed column that takes an Identity column as its input:
Create Function dbo.WeirdIdentity
(
@Num BigInt
)
Returns VarChar(256)
As Begin
Declare
@Digits Char(36),
@Results Char(17),
@i Int,
@j-2 Int
Set @Digits='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
If @Num<Len(@Digits) Begin
Return SubString(@Digits,@Num+1,1)
End
Return dbo.WeirdIdentity(@Num/Len(@Digits))+SubString(@Digits,(@Num%Len(@Digits))+1,1)
End
April 26, 2006 at 2:13 pm
Wow, you folks are brilliant. I havent had a chance to test them out yet, but thank you very much. And Jeff, trust me, I wish I was involved in the planning of this database. But we have to deal with what we are given.
Again, thank you. Ya'll are brilliant.
April 26, 2006 at 11:03 pm
As Ryan pointed out... had a little bug in my code and repaired it. Managed to condense it a bit, too. Still takes a very pigish 16 mintues to run on a million rows even in light of idexes but I'll post it anyway...
CREATE FUNCTION dbo.WierdNum(@Number DECIMAL(38))
RETURNS VARCHAR(18)
AS
BEGIN
DECLARE @Counter DECIMAL(38)
SET @Counter = 17
DECLARE @String VARCHAR(18)
SET @String = ''
WHILE @Counter >= 0
BEGIN
SELECT @String = @String + Letter,
@Number = @Number - (FLOOR(CAST(@Number/POWER(62.0,@Counter) AS FLOAT)))*POWER(62.0,@Counter)
FROM Translate
WHERE Number = FLOOR(CAST(@Number/POWER(62.0,@Counter) AS FLOAT))
SET @Counter = @Counter-1
END
RETURN @String
END
Ryan,
Thanks for finding the problem... turn out I over-scienced this puppy. I gotta turn your's into a function and test it 'cause now you have me curious. Cute trick with the name thing... wrote a ditty to reverse engineer the magic number for my name as well... almost reminds me of the early attempts at code sheet encryption but this whole wierd numbering system is actually a bit more sophisticated in some aspects.
Peter, brilliant job of recursion... after making a minor change to make it match Aaron's original post, it looks like this and only takes a minute:29 to run on the same million rows as above...
Create Function dbo.WeirdIdentity
(
@Num BIGINT
)
Returns VarChar(256)
As Begin
Declare
@Digits Char(62),
@Results Char(17),
@i Int,
@j-2 Int
Set @Digits='0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ'
If @Num<Len(@Digits)
Begin
Return SubString(@Digits,@Num+1,1)
End
Return dbo.WeirdIdentity(@Num/Len(@Digits))+SubString(@Digits,(@Num%Len(@Digits))+1,1)
End
go
Here's the call I used to get the leading zero's Aaron wanted...
SELECT RIGHT(REPLICATE('0',17)+dbo.WeirdIdentity(RowNum),17) FROM BigTest
Aaron,
Thanks for the feedback... I think that everyone who jumped in had some fun with this... tell the guy who came up with it that it would still be easier to just use GUID's with NEWID(). Takes fewer bytes and is globally unique... not just unique on the same server.
--Jeff Moden
Change is inevitable... Change for the better is not.
Viewing 15 posts - 1 through 14 (of 14 total)
You must be logged in to reply to this topic. Login to reply