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.
| 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)ASBEGIN -- 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 + ' ENDELSE RETURN @refId ' EXECUTE sp_executesql @sql RETURN ''END |
|
|
X002548
Not Just a Number
15586 Posts |
|
|
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?Brett8-)
Unfortunately, that's not an option :) |
 |
|
|
X002548
Not Just a Number
15586 Posts |
|
|
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" IDBrett
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. |
 |
|
|
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) |
 |
|
|
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" idWhat 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 doesBrett8-)Hint: Want your questions answered fast? Follow the direction in this linkhttp://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspxAdd yourself!http://www.frappr.com/sqlteam |
 |
|
|
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? |
 |
|
|
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" idWhat 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. |
 |
|
|
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. |
 |
|
|
X002548
Not Just a Number
15586 Posts |
Posted - 2009-07-01 : 21:01:33
|
quote: Originally posted by akashenkIt is HIGHLY unlikely
SeeOK, so you're stuckto 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 with0000000000Next0000000001Next0000000002So, my question to you is...are we just trying to be cute with this alpha numeric nonsense?Unique is unique, no?Brett8-)Hint: Want your questions answered fast? Follow the direction in this linkhttp://weblogs.sqlteam.com/brettk/archive/2005/05/25/5276.aspxAdd yourself!http://www.frappr.com/sqlteam |
 |
|
|
akashenk
Posting Yak Master
111 Posts |
Posted - 2009-07-01 : 22:53:48
|
quote: Originally posted by X002548
quote: Originally posted by akashenkIt is HIGHLY unlikely
SeeOK, so you're stuckto 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 with0000000000Next0000000001Next0000000002So, my question to you is...are we just trying to be cute with this alpha numeric nonsense?Unique is unique, no?Brett8-)
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. |
 |
|
|
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) = nullASBEGIN -- 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 |
 |
|
|
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. |
 |
|
|
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...)? |
 |
|
|
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. |
 |
|
|
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) OUTPUTASBEGIN -- 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 |
 |
|
|
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 hereDECLARE @tableName nvarchar(50) , @idField nvarchar(50) , @NewID NCHAR(10) OUTPUTASBEGIN -- 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 |
 |
|
|
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. :) |
 |
|
|
|
|
|
|
|