65.9K
CodeProject 正在变化。 阅读更多。
Home

在表插入中使用用户定义函数并通过存储过程进行初始化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (7投票s)

2009年8月26日

CPOL

3分钟阅读

viewsIcon

45252

downloadIcon

197

使用 UDF 和存储过程生成唯一的列值。

概述

本文(我的第一篇),演示了如何使用 INSERT 语句自动设置列值。我展示了两种方法:第一种方法使用用户定义函数在 Table INSERT 上自动生成唯一代码;第二种方法使用存储过程将行插入到具有指定或默认索引编号的表中。

背景

您是否需要为索引列(如任务编号或序列中的步骤)进行 seeding? 如何自动为客户生成唯一代码? 以下是我对这些问题的解决方案。 这些可能不是实现此功能的最佳或最优雅的方式,但它是一个很好的入门。

我们要做的第一件事是打开 SQL Management Studio (SSMS) 并创建一个名为 DemoTaskList 的示例数据库;然后在新的查询窗口中,我们创建我们的第一个表

  USE DemoTaskList
  GO

  CREATE TABLE Person(
    ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    [Name] VARCHAR(100) UNIQUE NOT NULL
  )

  -- We will add some sample data...

  INSERT Person([Name]) VALUES('Richard Nixon')
  INSERT Person([Name]) VALUES('Bill Clinton')
  GO

接下来我们需要创建一个用户定义函数来执行 string 操作,以便从 Person Table 中的 Person.Name 列和 Job Table 中的 Job 的 ID 的组合中自动创建一个唯一值(即将推出...)

  CREATE FUNCTION udf_SET_JobCode(
    @PersonID INT,
    @ID INT
  )
  RETURNS VARCHAR(10)
  AS
  BEGIN

    -- Get the Person referenced by @PersonID
    DECLARE @Person VARCHAR(100) = (SELECT [Name]
				      FROM Person
				      WHERE ID = @PersonID)

    -- A VARCHAR copy of the @ID input
    DECLARE @RET VARCHAR(10) = CAST(@ID AS VARCHAR(10))

    -- CHAR Length of the @RET variable
    DECLARE @LEN INT = LEN(@RET)

    SET @RET = UPPER(LEFT(@Person,3)) +		-- Magic (see BOL for
		     STUFF(@RET,1,0,LEFT('0000000',7 - @LEN)) -- Function 
							-- definitions/syntax

    RETURN @RET

  END

 -- Now the all important Job table that calls our UDF as a reference
  CREATE TABLE Job(
    ID INT Identity(1,1) NOT NULL PRIMARY KEY,
    PersonID INT NOT NULL FOREIGN KEY REFERENCES Person(ID),
    JobCode AS dbo.udf_SET_JobCode(PersonID, ID),		-- UDF Reference
    JobDescription VARCHAR(200) NOT NULL
    )

正如您在上面这一行中看到的:JobCode AS dbo.udf_SET_JobCode(PersonID, ID)JobCode 的列定义引用了我们的用户定义函数并传递了两个参数:PersonIDID,并为我们返回了自定义值。

UDF 返回的自定义值由人名的前三个字母和填充到 7 位的自动生成的 Identity 值组成。 阅读 BOL 文档中关于 STUFF 函数的说明,了解其工作原理 - 无需多说,它会将一个 String 填充到另一个 String 中。

让我们通过为 Richard Nixon 添加几个新 Job 来测试一下

  INSERT Job(PersonID,JobDescription)
    VALUES(1,'Become President of the USA')
  INSERT Job(PersonID,JobDescription)
    VALUES(1,'Become Infamous')

  -- Select the data...
  SELECT * FROM Job
  GO
  RESULT:
  ===========================================================
  ID	PersonID            JobCode	       JobDescription
  ----  ------------------- ----------  ---------------------
  1	 1		  RIC0000001    Become President of the USA
  2       1		  RIC0000002    Become Infamous

正如预期的那样,为我们的 Person 自动为每个 INSERT 生成了一个 JobCode。 让我们为 Bill Clinton 添加一个 job

  INSERT Job(PersonID, JobDescription)
    VALUES(2, 'Achieve Notoriety')
  GO

  -- Select the data...
  SELECT * FROM Job
  GO
  RESULT:
  ===========================================================
  ID	PersonID     JobCode	    JobDescription
  ----  ------------------- ----------  ---------------------
  1	 1	    RIC0000001        Become President of the USA
  2       1	    RIC0000002        Become Infamous
  3       2	    BIL0000003        Achieve Notoriety

一切都清楚了吗? 现在我想构建一个 Table ,它将保存完成分配给某个人的 Job 所需的任务。 我们希望在查询表时按顺序列表任务,并且我们希望能够在列表中的指定索引处插入新任务。 例如,如果某个 Job 不存在任何任务,我们将在任务编号 1 处添加新任务。但是,如果确实存在任务,我们可以根据您的标准在列表中的指定位置或列表末尾插入任务。 让我们看一下脚本

  CREATE TABLE Task(
    ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
    JobID INT NOT NULL FOREIGN KEY REFERENCES Job(ID),
    TaskNo INT NOT NULL,
    TaskDescription VARCHAR(200)
  )
  GO

  CREATE PROCEDURE usp_INSERT_Task(
    @JobCode VARCHAR(10),
    @TaskNo INT = NULL,
    @TaskDescr VARCHAR(200)
  )
  AS
  BEGIN
    SET NOCOUNT ON;
    
    -- Get the JobID
    DECLARE @JobID INT = (SELECT ID
      FROM Job
      WHERE JobCode = @JobCode)
    
    -- Get the largest TaskNo for the job
    DECLARE @MAX INT = (SELECT MAX(TaskNo)
		          FROM Task t
			    JOIN Job j
			      ON t.JobID = j.ID
		          WHERE j.JobCode = @JobCode)
    
    -- Fancy error handling block
    BEGIN TRY
      BEGIN TRANSACTION

	-- Check TaskNo is valid else set defaults
	SET @TaskNo = (
	       SELECT CASE ISNULL(@TaskNo,0)     -- if @TaskNo is null then check @MAX
			WHEN 0 
			THEN	
			  CASE ISNULL(@MAX,0) -- if max is null then set @TaskNo = 1
			    WHEN 0 
			    Then 1 
			    ELSE @MAX + 1
			    END	  			
			ELSE	        	-- Else if @TaskNo is not null Then
			  Case WHEN @TaskNo > @MAX	-- if @TaskNo > @MAX 
						-- set to @MAX + 1
			       THEN @MAX + 1
			       ELSE @TaskNo 		-- else do nothing
			       END			
			END)

	DECLARE @Count INT = @MAX
	
	WHILE @Count >= @TaskNo		-- If @MAX >= @TaskNo Update TaskNos >=
	  BEGIN				-- @TaskNo
	    
	    UPDATE Task
	      SET TaskNo = TaskNo + 1
		WHERE JobID = @JobID
		  AND TaskNo = @Count

	    SET @Count = @Count - 1			-- Decrement Counter

	  END
	
	INSERT Task(JobID, TaskNo, TaskDescription)	-- Perform Insert
	  VALUES(@JobID, @TaskNo, @TaskDescr)

      COMMIT TRANSACTION				-- Commit transaction

    END TRY

    -- Perform Error Handling
    BEGIN CATCH

      ROLLBACK

      BEGIN
	RAISERROR('Error when processing INSERT statement or Task Table.',15,1)
	RETURN -100
      END

    END CATCH

    RETURN

  END

这就是它的全部内容 - 我们迭代遍历 TaskNo,将它们递增 1,然后在指定位置 INSERT 新记录,就像这样...

  EXEC dbo.usp_INSERT_Task 'RIC0000001',NULL,'Run for Class President' -- Default position
  GO
  EXEC dbo.usp_INSERT_Task 'RIC0000001',1,'Join Student Union'
  GO
  EXEC dbo.usp_INSERT_Task 'RIC0000001',5, 'Run for President of USA'
  GO

  SELECT p.[Name], j.JobCode, t.TaskNo, t.TaskDescription
    FROM Task t
      JOIN Job j
        ON t.JobID = j.ID
      JOIN Person p
        ON j.PersonID = p.ID
    WHERE p.ID = 1
    ORDER BY p.[Name], j.JobCode, t.TaskNo
  GO
  RESULT:
  =======================================================================================
  Name		 JobCode      TaskNo    TaskDescription		
  ---------------------------------------------------------------------------------------
  Richard Nixon	 RIC0000001   1	       Join Student Union
  Richard Nixon	 RIC0000001   2	       Run for Class President
  Richard Nixon	 RIC0000001   3	       Run for President

正如您所看到的,存储过程能够根据 @MAX 的值确定默认位置,并根据我们的规则使用正确的 TaskNo INSERT 新行。

关注点

UDF 使用各种 String 函数:CASTLENLEFTSTUFF。 有关如何使用这些内置函数的详细说明,请参阅联机丛书。

谢谢

感谢 UTS/SSW.com.au 的 Andy Taslim 和 Peter Gfader 在过去几周提供的宝贵建议,以及 CodeProject 上的所有朋友们,他们给了我发布第一篇文章的信心。

版本

  • 版本 1.0
© . All rights reserved.