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
 SQL Server 2005 Forums
 Transact-SQL (2005)
 Recursive Function

Author  Topic 

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-01 : 16:25:32
I have several tables which all have a unique 10-character string as their primary index. I would like to create a scalar function which will create a new unique id for a give table. I tried the following, but apparently you can't use sp_executesql to execute dynamic sql in a function. The reason I wanted to use dynaic sql was that I wanted to make the function a little generic, so I could pass the table name and fieldname and return a new unique ID for that particular table. Any help would be appreciated.

My first attempt used dynamic SQl to retrieve a random existant id from the specified table and alter it a little. Then if the altered id is unique, return that, otherwise, recursively run the function again. Like I said, this doesn't seem to work at all because I can't call sp_executeSQL. Perhaps there is a better way of doing this?


CREATE FUNCTION [dbo].[f_createUniqueID]
(
-- Add the parameters for the function here
@tableName nvarchar(50) = null,
@idField nvarchar(50) = null
)
RETURNS nchar(10)
AS
BEGIN
-- Declare the return variable here
DECLARE @sql nvarchar(max)

SET @sql = N'

DECLARE @refId nchar(10)

SELECT TOP 1 @refId=' + @idField + ' FROM ' + @tableName + ' ORDER BY NEWID()

SET @refId = REPLACE(@refId,''1'',''2'')
SET @refId = REPLACE(@refId,''3'',''4'')
SET @refId = REPLACE(@refId,''5'',''6'')
SET @refId = REPLACE(@refId,''7'',''8'')
SET @refId = REPLACE(@refId,''9'',''0'')
SET @refId = REPLACE(@refId,''A'',''B'')
SET @refId = REPLACE(@refId,''C'',''D'')
SET @refId = REPLACE(@refId,''E'',''F'')

IF EXISTS (SELECT ' + @idField + ' FROM ' + @tableName + ' WHERE ' + @idField + '=@refId)

BEGIN

RETURN EXEC f_createUniqueID ' + @tableName + ', ' + @idField + '

END

ELSE

RETURN @refId '


EXECUTE sp_executesql @sql

RETURN ''

END

X002548
Not Just a Number

15586 Posts

Posted - 2009-07-01 : 16:30:58
use IDENTITY() and be done with it?



Brett

8-)

Hint: Want your questions answered fast? Follow the direction in this link
http://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspx

Add yourself!
http://www.frappr.com/sqlteam



Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-01 : 16:34:14
quote:
Originally posted by X002548

use IDENTITY() and be done with it?



Brett

8-)





Unfortunately, that's not an option :)
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2009-07-01 : 16:59:22
OK, please explain what you mean by a "unique" ID



Brett

8-)

Hint: Want your questions answered fast? Follow the direction in this link
http://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspx

Add yourself!
http://www.frappr.com/sqlteam



Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-01 : 17:06:51
quote:
Originally posted by X002548

OK, please explain what you mean by a "unique" ID



Brett





The tables have a field which is made up of a unique 10-characer alpha-numeric string. The id is unique within the given table. Normally, the id is generated within the application that uses the tables, but I would like to generate it within the database. As an aternative to what I tried, it would be suitable if I can come up with a function which generates a random 10-charater alpha-numeric string and simply tests to make sure it is not already being used in the given table. If so, recursively generate a new string.
Go to Top of Page

Lamprey
Master Smack Fu Yak Hacker

4614 Posts

Posted - 2009-07-01 : 17:12:36
Are there any rules on what it ID looks like? Like the first five digits are numbers and the last five are alpha.? Can the digits be 0-9 and or A-Z?

Try this:
SELECT LEFT(REPLACE(NEWID(), '-', ''), 10)
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2009-07-01 : 17:16:28
ok, but how do you know if it's unique even after you generate it?

and what if 2 threads at the same time generate the unique id, AND both check to see that it is unique at the time, then try to insert the new row with the new "unique" id

What now

(Oh, I know...it's the old, "That'll never happen" response)

If it can, it will, and it will be a mystery to you when it does





Brett

8-)

Hint: Want your questions answered fast? Follow the direction in this link
http://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspx

Add yourself!
http://www.frappr.com/sqlteam



Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-01 : 17:19:40
quote:
Originally posted by Lamprey

Are there any rules on what it ID looks like? Like the first five digits are numbers and the last five are alpha.? Can the digits be 0-9 and or A-Z?

Try this:
SELECT LEFT(REPLACE(NEWID(), '-', ''), 10)




Thanks, Laprey. I think that might just do the trick as far as generating the unique ID. I'm still somewhat stuck as far as testing to see if it exists in the table or not. I could probably create a recursive function which test a given table, but is there any way to do so while passing the tableName and fieldName to a function?
Go to Top of Page

Lamprey
Master Smack Fu Yak Hacker

4614 Posts

Posted - 2009-07-01 : 18:22:49
quote:
Originally posted by X002548

ok, but how do you know if it's unique even after you generate it?

and what if 2 threads at the same time generate the unique id, AND both check to see that it is unique at the time, then try to insert the new row with the new "unique" id

What now

(Oh, I know...it's the old, "That'll never happen" response)

If it can, it will, and it will be a mystery to you when it does
I agree. Every time I see a request like this I'm baffled. That's why I'll ask a few questions and then start hadding out rope to whom ever wants to hang them.. errrr I mean, use it. :)

Akashenk,

I don't think this can be done in a function. But, it can be done with a stored procedure easily enough.
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-01 : 19:32:40
Any guidance regarding the Stored Procedure would be appreciated. As for the question of whether the unique ID is truely unique... These tables will hold something on the order of 100,000 records. It is HIGHLY unlikely that two simultaneous threads will generate the same random 10 character alphanumeric string. However, even if this were to happen, the result would be a SQL error stating that an existing primary key could not be added. My Applications handle SQL errors reasonably well and the cause would be immediately known. Anyhow, this is a legacy application and the built-in SQL server id generationi mechanisms could not be used because the application needed to be DB independant, to as great a degree as possible.
Go to Top of Page

X002548
Not Just a Number

15586 Posts

Posted - 2009-07-01 : 21:01:33
quote:
Originally posted by akashenk
It is HIGHLY unlikely



See

OK, so you're stuck

to be simple is 9,999,999,999 unique id's enough?

Yes (if not take 999,999,999 and make the billionth digit an alpha, so you can have basically 26 billion unique ids)

OK, Lets start with

0000000000

Next

0000000001

Next

0000000002

So, my question to you is...are we just trying to be cute with this alpha numeric nonsense?

Unique is unique, no?



Brett

8-)

Hint: Want your questions answered fast? Follow the direction in this link
http://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspx

Add yourself!
http://www.frappr.com/sqlteam



Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-01 : 22:53:48
quote:
Originally posted by X002548

quote:
Originally posted by akashenk
It is HIGHLY unlikely



See

OK, so you're stuck

to be simple is 9,999,999,999 unique id's enough?

Yes (if not take 999,999,999 and make the billionth digit an alpha, so you can have basically 26 billion unique ids)

OK, Lets start with

0000000000

Next

0000000001

Next

0000000002

So, my question to you is...are we just trying to be cute with this alpha numeric nonsense?

Unique is unique, no?



Brett

8-)





I'm not quite following. The alphanumeric structure is really out of my control, but I'm not sure why it would make a difference. I can't change the application to use SQL Server Identity columns.
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-02 : 10:55:46
OK, so I have managed to create the following Stored Procedure which generates the unique ID, but I am a little unsure about how I would use this in another procedure that needs the ID. So if my procedure inserts a new record into one of these tables, how do I use the generator to get the new Id value?

Generator SP:

Alter PROCEDURE [dbo].[p_createId] 
-- Add the parameters for the stored procedure here
@tableName nvarchar(50) = null,
@idField nvarchar(50) = null

AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;


DECLARE @sql nvarchar(max)

SET @sql = N'

DECLARE @id nchar(10)

SELECT @id = LEFT(REPLACE(NEWID(), ''-'', ''''), 10)

IF EXISTS (SELECT ' + @idField + ' FROM ' + @tableName + ' WHERE ' + @idField + ' = @id)

BEGIN
EXEC p_createUniqueID ' + @tableName + ', ' + @idField + '
END

ELSE

BEGIN
SELECT @id
END

'

EXECUTE sp_executesql @sql


END
Go to Top of Page

Lamprey
Master Smack Fu Yak Hacker

4614 Posts

Posted - 2009-07-02 : 12:22:37
I'd probably use an OUTPUT parameter. Also, I'm not sure I'm following your proc. (may it's a cut-n-paste thing). But, Are you making a recursive call? If so, I'd change that to an iterative solution (WHILE loop) so you don't blow the stack looking for an ID.
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-02 : 12:25:54
quote:
Originally posted by Lamprey

I'd probably use an OUTPUT parameter. Also, I'm not sure I'm following your proc. (may it's a cut-n-paste thing). But, Are you making a recursive call? If so, I'd change that to an iterative solution (WHILE loop) so you don't blow the stack looking for a non-existant ID.



I thought about using an OUTPUT parameter, but I wasn't quite sure how to get the dynamic SQL @id value into the output parameter.

As for the recursiveness, yes it s intended to make a recursive call.. but only if the created id already exists. Would uding WHILE (EXISTS ....) in this context be any different than IF (EXISTS...)?
Go to Top of Page

Lamprey
Master Smack Fu Yak Hacker

4614 Posts

Posted - 2009-07-02 : 12:44:52
And yes there is a difference between WHILE and IF. I'm going to leave it at that for now. If anyone else wants to tackle that question, feel free.

You might take a look in BOL for sp_executesql. That will let you return an OUTPUT parameter from a dynamic sql call. Also, if I have some time I can see if I can put together one possible way to code your stored proc.
Go to Top of Page

akashenk
Posting Yak Master

111 Posts

Posted - 2009-07-02 : 12:58:43
quote:
Originally posted by Lamprey

And yes there is a difference between WHILE and IF. I'm going to leave it at that for now. If anyone else wants to tackle that question, feel free.

You might take a look in BOL for sp_executesql. That will let you return an OUTPUT parameter from a dynamic sql call. Also, if I have some time I can see if I can put together one possible way to code your stored proc.




Thanks for the suggestions, Lamprey. I have managed to get the output parameter to work. The problem I had was that I didn't put "OUTPUT" after the variable declaration in my calling SP. The following now works and seems to do the job of creating a unique 10-character ID well. Please let me know if you see any obvious problems with this SP. Thanks again!

CREATE PROCEDURE [dbo].[p_createUniqueID]
@tableName nvarchar(50) = null,
@idField nvarchar(50) = null,
@newID nchar(10) OUTPUT

AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

DECLARE @sql nvarchar(max),
@params nvarchar(max)

SET @sql = N'

SELECT @id = LEFT(REPLACE(NEWID(), ''-'', ''''), 10)

WHILE (EXISTS (SELECT ' + @idField + ' FROM ' + @tableName + ' WHERE ' + @idField + ' = @id))

BEGIN
EXEC p_createUniqueID ' + @tableName + ', ' + @idField + ', @id
END
'
SET @params = N'@id nchar(10) OUTPUT'

EXECUTE sp_executesql @sql,
@params,
@id=@newId OUTPUT

END
Go to Top of Page

Lamprey
Master Smack Fu Yak Hacker

4614 Posts

Posted - 2009-07-02 : 13:09:27
Here is another approach(note untested):
Alter PROCEDURE [dbo].[p_createId] 
-- Add the parameters for the stored procedure here
DECLARE @tableName nvarchar(50) ,
@idField nvarchar(50) ,
@NewID NCHAR(10) OUTPUT

AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;


DECLARE @sql NVARCHAR(4000)
DECLARE @id NCHAR(10)
DECLARE @RowCount INT

-- Build dynamic query
SET @sql = 'SELECT @cnt = 1 FROM ' + @tableName + ' WHERE ' + @idField + ' = @id'

-- Prime the loop
SET @RoweCount = 1 -- Get into the loop

WHILE @RowCount IS NOT NULL -- Duplicate found
BEGIN

-- Gen new id and check
SET @id = LEFT(REPLACE(NEWID(), '-', ''), 10)

EXEC sp_executesql @sql, N'@id NCHAR(10), @cnt INT OUTPUT', @id, @RowCount OUTPUT

END

SET @NewID = @ID

END
EDIT: Changed loop primer
Go to Top of Page

Lamprey
Master Smack Fu Yak Hacker

4614 Posts

Posted - 2009-07-02 : 13:17:12
Also, been a while since I've messed with dynamic sql. I think you need to check the Return Value from the EXEC call to test for errors. If you are using 2005 or later you can try using a TRY-CATCH block. Again, it's been a while and I know there are some limitation in trapper errors. So, I'd figure out the right way to trap them before you take down your server. :)
Go to Top of Page
   

- Advertisement -