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 |
|
weitzhandler
Yak Posting Veteran
64 Posts |
Posted - 2009-04-15 : 19:25:01
|
Function:CREATE FUNCTION GetId(@TableName sysname)RETURNS intASBEGIN DECLARE @Id int, @Sql nvarchar(1024) = 'SELECT IdOUT = TOP(1) ' + @TableName + 'Id FROM ' + @TableName + ' ORDER BY ' + @TableName + 'Id DESC', @ParmDefinition nvarchar(256) = 'IdOUT int OUTPUT' EXEC sp_executesql @Sql, @ParmDefinition, @IdOUT = @Id OUTPUT RETURN @IdEND Usage:SELECT dbo.Getid('Contact')Result:Msg 557, Level 16, State 2, Line 1Only functions and some extended stored procedures can be executed from within a function. Now I know that executing procedures from within funcs is not support (which really sucks), I am just looking for a way so I can have this function in other script or whatever.Note, I want to use this function as a default-value value so I want it in a function an not as a SP.Any discussion would be really appreciated.Thanks!Shimmy |
|
|
TG
Master Smack Fu Yak Hacker
6065 Posts |
Posted - 2009-04-15 : 22:50:02
|
| Can't say it any clearer than the error message you got. Forget about trying to EXEC anything from within a function.>>Now I know that executing procedures from within funcs is not supportyou know sp_executesql is a stored procedure, right?What are you planning on doing with this ID once you have it? If you are using it to increment the value and then add a new row, that method won't hold up during concurrent requests. 2 or more people get the same ID and then all but one gets a PK violation. IDENTITY columns were developed to solve that exact problem.Be One with the OptimizerTG |
 |
|
|
weitzhandler
Yak Posting Veteran
64 Posts |
Posted - 2009-04-15 : 23:38:45
|
Well, why don't tell you my exact problem:Here is the code (you can create a test database for testing):USE TestCREATE TABLE Contact( ContactId int not null IDENTITY(1,1), Name varchar(64) not null )CREATE TABLE Phone( PhoneId int not null IDENTITY(1,1), Phone varchar(32) not null, ContactId int not null, SortOrder int not null )GOALTER TABLE Contact ADD CONSTRAINT PK_Contact PRIMARY KEY CLUSTERED (ContactId) ON [PRIMARY] ALTER TABLE Phone ADD CONSTRAINT PK_Phone PRIMARY KEY CLUSTERED (PhoneId) ON [PRIMARY] ALTER TABLE dbo.Phone ADD CONSTRAINT IX_Phone UNIQUE NONCLUSTERED ( ContactId, SortOrder )GO ALTER TABLE dbo.Phone ADD CONSTRAINT FK_Phone_Contact FOREIGN KEY (ContactId) REFERENCES dbo.Contact(ContactId) ON UPDATE NO ACTION ON DELETE CASCADE GO Now let me explain briefly, as you saw we have a parent contact table that has many child phone tables.I want the user to have control on the order of the phones according to each user.On the other hand, I don't want things to get messy which means:- Strictly consistent groups - under each contact parent should be a consistent zero based chaine of sort order values.
- If there are 5 phones for this contact, there shouldn't be SortOrder = 7 (or SortOrder = 5 as it's zero based and max allowed is 4).
- I have already set up the unique key for the groups so there won't be duplicates.
- There are few more, I don't remember or I am too lazy now...
Anyway, I think you got the picture.My problem begins now since I don't have only a Contact->Phone pair but also Building->Address (I need 1 building to have many addresses in a custom order), or I have a Job table and a Contractor table and I want to have a third party header table JobContractorHeader(JobId int, ContractorId int, SortOrder int) to connect between them while having the control on each jobs' contractors' ordered within this job) and so on and so on.Let's say that I have in my application more than 20 tables that provide this SortOrder column.You understand that this is rediculus to start writing insert update and delete sprocs for each of them, I thought about moving things to get a bit more dynamic.First I developed a Procedure that handles changing of existing SortOrder values (this PROC handles the whole logic meaning pushing back and forward all the corresponding rows' SortOrder values).Second - which we're standing here - I want to have a function that I will be able to set up the DEFAULT value of the SortOrder col, for instance, if the above function would of been avilable I could use:[dbo].[GetSortOrder]('Phone', --passes table name'ContactId', --passes the group column name[ContactId] --passes the group parent)Now you can chose whether to help me with this particular function from above, or to help me with the entire issue (I donno, maybe there is some built in API that provides it all...), or not help at all, anyway if you reached till here I already owe you, lolz.Any further discussion will be really appreciated.Shimmy |
 |
|
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2009-04-16 : 05:22:21
|
| Functions cannot change the state of the database in any way. Also they need to be completely deterministic (at least in 2000 / 2005). I.E given the same inputs (and the same underlying data) they will always produce the same output. To make sure function's can't modify data the data engine won't let you call stored procs from them.rewrite the function a a stored proc that either SELECT's the ID as a result set or includes it as a OUTPUT parameter depending on how you want to use it.Charlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
|
weitzhandler
Yak Posting Veteran
64 Posts |
Posted - 2009-04-16 : 05:26:41
|
| It suck but OK.Thanks for your help, I did it with a SPROC...(Pity, I only wanted to select data, not to change, so I could use the function as the default value of the column rather than creating triggers)Shimmy |
 |
|
|
weitzhandler
Yak Posting Veteran
64 Posts |
Posted - 2009-04-16 : 23:17:08
|
quote: Originally posted by weitzhandler Well, why don't tell you my exact problem:..........................Shimmy
Here you go, A a dynamic procedure that handles the entire logic:USE [Bss]GO/****** Object: StoredProcedure [dbo].[sp_sortorder] Script Date: 04/17/2009 06:07:49 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO-- ================================================-- Description: Handles the SortOrder column logic.-- For tables that provide it.-- @TableName sysname, The table name you want to apply the logic on.-- @IdColumn varchar(32) = null, Specify the ID column name if other than @TableName + 'ID'-- @RowId, The ID of the row you want to apply the logic on.-- @SortOrderColumn varchar(32) = 'SortOrder', The name of the column that exposes the SortOrder.-- @SortOrderValue int = -1, The new SortOrder value - ifargument is -1, will change to max value in the SortOrder.-- @ParentIdColumn varchar(32) = null, If you want to limit the sorting to a sub-group, pass the parent-table's name.-- ================================================CREATE PROCEDURE [dbo].[sp_sortorder]( @TableName sysname, @IdColumn varchar(32) = null, @RowId int, @SortOrderColumn varchar(32) = 'SortOrder', @SortOrderValue int = -1, @ParentIdColumn varchar(32) = null)ASBEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON;DECLARE @CurrentSortOrder int, -- Current SortOrder value for the specified row. @ParentId int, -- The parent ID to limit the group with. @Group varchar(256) = '', -- The sql syntax that will limit the group. @Direction bit DECLARE @SortOrderData TABLE (RowId int not null, SortOrder int not null)SET @IdColumn = ISNULL(@IdColumn, @TableName + 'ID')IF NOT ISNULL(@ParentIdColumn, '') = '' BEGIN DECLARE @Sql nvarchar(1024) = N'SELECT @CurrentSortOrderOUT = ' + @SortOrderColumn + ', @ParentIdOUT = ' + @ParentIdColumn + ' FROM ' + @TableName + ' WHERE ' + @IdColumn + ' = ' + ISNULL(CAST(@RowId AS varchar(10)), '0'), @ParmDefinition nvarchar(256) = '@CurrentSortOrderOUT int OUTPUT, @ParentIdOUT int OUTPUT' EXECUTE sp_executesql @Sql, @ParmDefinition, @CurrentSortOrderOUT = @CurrentSortOrder OUTPUT, @ParentIdOUT = @ParentId OUTPUT ENDIF @SortOrderValue >= 0 AND @SortOrderValue = @CurrentSortOrder PRINT '!!!!!!! R E T U R N E D !!!!!!!!' --RETURNSET @Group = CASE WHEN ISNULL(@ParentIdColumn, '') = '' THEN '' ELSE ' WHERE ' + @ParentIdColumn + ' = ' + ISNULL(CAST(@ParentId AS varchar(10)), '0') END--Select the entire group including our row.INSERT INTO @SortOrderData(RowId, SortOrder)EXEC (N'SELECT ' + @IdColumn + ', ' + @SortOrderColumn + ' FROM ' + @TableName + @Group + ' ORDER BY ' + @SortOrderColumn)--Set current row's data:SELECT @CurrentSortOrder = SortOrder FROM @SortOrderData WHERE RowId = @RowIdDECLARE @Max int = (SELECT TOP(1) SortOrder FROM @SortOrderData ORDER BY SortOrder DESC)IF @SortOrderValue < 0 SET @SortOrderValue = @Max ELSE IF @SortOrderValue > @Max BEGIN SET @SortOrderValue = @Max ENDIF @SortOrderValue = @CurrentSortOrder PRINT '!!!!!!! R E T U R N E D 2!!!!!!!!' --RETURN SET @Direction = CASE WHEN @CurrentSortOrder < @SortOrderValue THEN 1 --1 means that the current is smaller and needs to go up (i.e. 5 to 7). ELSE 0 --Means that it wants to move from a high SortOrder value to low (i.e. 7 to 5) END----------------------------------- print 'DIRECTION:' print @directionprint 'Current sort order:' print @currentsortorderPRINT 'SORT ORDER VALUE:' PRINT @SORTORDERVALUE----------------------------------- --Delete from the table all the rows we don't need for the sorting including our row.IF @Direction = 1 DELETE FROM @SortOrderData WHERE SortOrder <= @CurrentSortOrder OR SortOrder > @SortOrderValueELSE DELETE FROM @SortOrderData WHERE SortOrder >= @CurrentSortOrder OR SortOrder < @SortOrderValue BEGIN TRY BEGIN TRANSACTION --Temporarily change this row's SortOrder to -1 to avoid conflicts. IF @Group <> '' SET @Group = REPLACE(@Group, 'WHERE', 'AND') EXEC('UPDATE ' + @TableName + ' SET ' + @SortOrderColumn + ' = - 1 WHERE ' + @IdColumn + ' = ' + @RowId + @Group) DECLARE @Id int, @SortOrder int, @Dir CHAR(3) = CASE WHEN @Direction = 1 THEN ' - ' ELSE ' + ' END print 'FETCH=============================================' DECLARE SortOrder_Cursor CURSOR FOR SELECT * FROM @SortOrderData ORDER BY CASE WHEN @Direction = 1 THEN SortOrder ELSE -SortOrder END OPEN SortOrder_Cursor FETCH NEXT FROM SortOrder_Cursor INTO @Id, @SortOrder WHILE @@FETCH_STATUS = 0 BEGIN PRINT 'current fetch: ' print @id print @SortOrder EXEC('UPDATE ' + @TableName + ' SET ' + @SortOrderColumn + ' = ' + @SortOrder + @Dir + '1 WHERE ' + @IdColumn + ' = ' + @Id) FETCH NEXT FROM SortOrder_Cursor INTO @Id, @SortOrder END CLOSE SortOrder_Cursor DEALLOCATE SortOrder_Cursor EXEC('UPDATE ' + @TableName + ' SET ' + @SortOrderColumn + ' = ' + @SortOrderValue + ' WHERE ' + @IdColumn + ' = ' + @RowId + @Group) COMMIT END TRY BEGIN CATCH ROLLBACK TRANSACTION PRINT 'TRAN CANCELLED: ' + error_message() END CATCH ENDShimmy |
 |
|
|
weitzhandler
Yak Posting Veteran
64 Posts |
Posted - 2009-04-16 : 23:19:59
|
And here is a SPROC (Not a function :( so I can't use in DEFAULT, only in triggers) that retrieves the next available SortOrder value:USE [Bss]GO/****** Object: StoredProcedure [dbo].[sp_getsortorder] Script Date: 04/17/2009 06:18:06 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO-- =============================================-- Author: <Author,,Name>-- Create date: <Create Date,,>-- Description: <Description,,>-- =============================================CREATE PROCEDURE [dbo].[sp_getsortorder] -- Add the parameters for the function here @TableName sysname, @ParentIdColumn varchar(32) = null, @ParentId int = null, @SortOrderColumn varchar(32) = 'SortOrder'ASBEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here DECLARE @SortOrder int, @Group nvarchar(1024) = '' IF NOT ISNULL(@ParentIdColumn, '') = '' IF ISNULL(@ParentId, '') = '' RAISERROR('If parameter @ParentIdColumn is specified, parameter @ParentId must be specified too.', 15, 1) ELSE SET @Group = ' WHERE ' + @ParentIdColumn + ' = ' + ISNULL(CAST(@ParentId AS nvarchar(16)), '0') DECLARE @Sql nvarchar(1024) = N'SELECT TOP(1) @SortOrderOUT = ' + @SortOrderColumn + ' FROM ' + @TableName + @Group + ' ORDER BY ' + @SortOrderColumn + ' DESC', @ParmDefinition nvarchar(256) = '@SortOrderOUT int OUTPUT' print @Sql EXEC sp_executesql @Sql, @ParmDefinition, @SortOrderOUT = @SortOrder OUTPUT SELECT @SortOrder ENDGOShimmy |
 |
|
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2009-04-17 : 09:01:48
|
| you could make a view that contains UNIONED queries and then reference that from a function without having to resort to dynamic sql.-- That's assume that the table names themselves aren't dynamic? -- you are not doing something crazy like making base tables on the fly?Say you have 3 tables (Foo, Bar, Woo)You could have a view with 2 columns (Table_Name, Next_available) That holds queries that produce data like belowTable_Name | Next Available---------------------------foo | 45bar | 12woo | 1And then have a function that references the view with a table_name parameter) -- the function would work as it doesn't involve any dynamic sql or calls to other stored procedures.I'm still not convinced why you would want to do this but I think it could be implemented that way.Charlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
|
weitzhandler
Yak Posting Veteran
64 Posts |
Posted - 2009-04-18 : 18:05:52
|
| could you please post a brief sample on how to create such a view.Shimmy |
 |
|
|
Transact Charlie
Master Smack Fu Yak Hacker
3451 Posts |
Posted - 2009-04-20 : 04:43:34
|
Here:IF OBJECT_ID('_sortOrders') IS NOT NULL DROP VIEW _sortOrdersIF OBJECT_ID('_tableA') IS NOT NULL DROP TABLE _tableAIF OBJECT_ID('_tableB') IS NOT NULL DROP TABLE _tableBGOCREATE TABLE _tableA ( [cola] INT , [colb] INT )CREATE TABLE _tableB ( [cola] INT )GOINSERT INTO _tableA ([cola], [colb]) SELECT 1,2UNION SELECT 323,0UNION SELECT 0,123INSERT INTO _tableB(cola) SELECT 0UNION SELECT 1GOCREATE VIEW _sortOrders AS SELECT TOP (1) 'tableA' AS [table], 'cola' AS [column], 'DES' AS [Direction], [cola] AS [value] FROM _tableA ORDER BY [colA] DESCUNION ALL SELECT TOP (1) 'tableA', 'cola', 'ASC', [cola] FROM _tableA ORDER BY [colA] ASCUNION ALL SELECT TOP (1) 'tableA', 'colb', 'DES', [colb] FROM _tableA ORDER BY [colB] DESCUNION ALL SELECT TOP (1) 'tableB', 'cola', 'ASC', [cola] FROM _tableB ORDER BY [cola] ASCGOSELECT * FROM dbo._sortOrdersOF course it will be slow as it will query all the tables rather than target the one you are after.I really think you should rethink your position on a general procedure / function to do this -- it's almost always a bad idea as the database engine just isn't a good tool for this.Charlie===============================================================Msg 3903, Level 16, State 1, Line 1736The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION |
 |
|
|
SwePeso
Patron Saint of Lost Yaks
30421 Posts |
Posted - 2009-04-20 : 05:39:34
|
And you still can't use the ID in a INSERT case, since the procedure is only invoked once, and you problably will have concurrency issues.Is there a special reason you can't use IDENTITY columns? E 12°55'05.63"N 56°04'39.26" |
 |
|
|
weitzhandler
Yak Posting Veteran
64 Posts |
Posted - 2009-04-20 : 05:57:17
|
| man, it seems you really didn't understand me.first: since when is an identity col subject to change!?second, i said i wanted it to be dynamic so i don't have to write milion views for all my table.the only little job is to create triggers which will call that func.and what we have to do here is really easy now, since we only have to provide the sproc params (table name, parent id, etc.)Shimmy |
 |
|
|
|
|
|
|
|