存储过程算法需要9个小时 - 更好的方法吗?

时间:2013-03-21 03:36:44

标签: sql sql-server stored-procedures plsql

我需要构建一个SQL存储过程,它基本上使用ID更新现有的表(大约150,000行)。

此存储过程将运行的表基本上是人员,他们的姓名,地址等的列表。

现在该人的身份识别算法如下: - 取得人名的前4个字符。 - 接受姓氏的前2个字符。 - 用0表示填充其余部分,最后用计数编号,直到该字段为8个字符。

例如,名称JOHN SMITH的ID为“JOHNSM00”。如果有2个JOHN SMITH,那么下一个人的ID就是JOHNSM01。例如,如果人名是FI LYNN,则ID将为FILY0000。

我已经编写了以下存储过程,但运行大约需要9个小时!有没有更好的方法来做到这一点,我错过了?

ALTER PROCEDURE [dbo].[LM_SP_UPDATE_PERSON_CODES]
AS

DECLARE @NAMEKEY NVARCHAR(10)
DECLARE @NEWNAMEKEY NVARCHAR(10)
DECLARE @LENGTH INT
DECLARE @KEYCOUNT INT
DECLARE @I INT
DECLARE @PADDING NVARCHAR(8)
DECLARE @PERSONS CURSOR
DECLARE @FIRSTNAME NVARCHAR(30)
DECLARE @LASTNAME NVARCHAR(30)

SET @PADDING = '00000000'
--FIRST CLEAR OLD NEW NAMEKEYS IF ANY EXIST
UPDATE LM_T_PERSONS SET NEW_NAMEKEY = NULL
SET @PERSONS = CURSOR FOR
SELECT NAMEKEY, NAME_2, NAME_1 FROM LM_T_PERSONS

OPEN @PERSONS
FETCH NEXT FROM @PERSONS INTO @NAMEKEY, @FIRSTNAME, @LASTNAME

WHILE @@FETCH_STATUS = 0
BEGIN
    --CHECK THE LENGTH OF FIRST NAME TO MAKE SURE NOTHING EXCEEDS 4
    SET @LENGTH = LEN(@FIRSTNAME)
    IF @LENGTH > 4 
        SET @LENGTH = 4

    SET @NEWNAMEKEY = SUBSTRING(@FIRSTNAME,1,@LENGTH)

    --CHECK THE LENGTH OF LAST NAME TO MAKE SURE NOTHING EXCEEDS 2
    SET @LENGTH = LEN(@LASTNAME)
    IF @LENGTH > 2
        SET @LENGTH = 2

    SET @NEWNAMEKEY = @NEWNAMEKEY + SUBSTRING(@LASTNAME,1,@LENGTH)
    SET @LENGTH = LEN(@NEWNAMEKEY)
    SET @I = 0
    SET @PADDING = SUBSTRING('00000000',1,8 - LEN(@NEWNAMEKEY) - LEN(CONVERT(NVARCHAR(8),@I)))
    --SEE IF THIS KEY ALREADY EXISTS
    SET @KEYCOUNT = (SELECT COUNT(1) FROM LM_T_PERSONS WHERE NEW_NAMEKEY = @NEWNAMEKEY + @PADDING + CONVERT(NVARCHAR(8),@I) )
    WHILE @KEYCOUNT > 0
    BEGIN
        SET @I = @I+1
        SET @PADDING = SUBSTRING('00000000',1,8 - LEN(@NEWNAMEKEY) - LEN(CONVERT(NVARCHAR(8),@I)))
        SET @KEYCOUNT = (SELECT COUNT(1) FROM LM_T_PERSONS WHERE NEW_NAMEKEY = @NEWNAMEKEY + @PADDING + CONVERT(NVARCHAR(8),@I) )
    END
    UPDATE LM_T_PERSONS SET NEW_NAMEKEY = @NEWNAMEKEY + @PADDING + CONVERT(NVARCHAR(8),@I) WHERE NAMEKEY = @NAMEKEY

    FETCH NEXT FROM @PERSONS INTO @NAMEKEY, @FIRSTNAME, @LASTNAME
END
CLOSE @PERSONS
DEALLOCATE @PERSONS

4 个答案:

答案 0 :(得分:6)

这样的事情可以在没有光标的情况下完成:

UPDATE P
SET NAMEKEY = FIRSTNAME + LASTNAME + REPLICATE('0', 8 - LEN(FIRSTNAME) - LEN(LASTNAME) - LEN(I)) + I
FROM
  LM_T_PERSONS AS P JOIN
  (
    SELECT
      NAMEKEY,
      LEFT(NAME_2, 4) AS FIRSTNAME,
      LEFT(NAME_1, 2) AS LASTNAME,
      CONVERT(NVARCHAR, ROW_NUMBER() OVER(PARTITION BY LEFT(NAME_2, 4), LEFT(NAME_1, 2) ORDER BY NAMEKEY)) AS I
    FROM
      LM_T_PERSONS
  ) AS DATA
  ON P.NAMEKEY = DATA.NAMEKEY

您可以在此处验证查询: http://sqlfiddle.com/#!3/47365/19

答案 1 :(得分:3)

我没有任何严格的“你应该采用XYZ方式”,但过去的类似练习:

  1. 如果你想保留存储过程,你有一个窗口,你可以做一个很长(时间)的任务,比如一个周末,你可以确定你将是唯一的操作运行然后将数据库设置为简单恢复模式(我假设您正在处理Prod数据库,因此它处于完全恢复模式),在您的工作期间,这可能会加快速度(因为您没有写入交易日志 - 因为你没有,即有限的可恢复性,你想确保你是唯一做任何事情的人。在开始工作之前,如果事情变得讨厌,我会做一个完整的备份

  2. 我认为它不是存储过程,而是游标用法,子字符串等,因为你在某处主要是基于集合的过程代码。我理解为什么这些存在的“原因”,但是可以选择将其取出并使用SQL Server Integration Services之类的东西,即使用更适合循环或针对各行进行转换的技术选项

  3. 继续使用更适合程序性工作的东西......你总是可以编写一个简单的.NET应用程序或类似的东西。从我自己的(有限的)经验来看,我已经看到过去这样做了,但里程往往会因操作的复杂性(在你的情况下在转换UserId字段方面听起来很简单),卷和写这篇文章的人...我会说我从来没有见过它特别好(因为我们从来没有转过身去“那太棒了”)但是更像是完成了工作所以我们继续前进否则,从经验中看既不好也不好(只是“平均”)。

  4. 我认为SSIS是一个很好的方法,因为您可以从数据库中提取这些记录,执行您需要的操作(考虑到SSIS支持您可以对数据做的各种各样的事情,包括编写.NET代码{尽管如此从内存中的VB.NET}如果你必须)然后更新你的数据库。

    其他类型的ETL技术可能会让你做类似的事情,但我最熟悉的是SSIS。 150k行不会是一个大问题,因为它可以处理更大的数量;从我自己的经验来看,我们会编写SSIS软件包,这些软件包没有什么特别之处,但他们可以在大约15分钟内完成超过100万行的这类操作...我认为专家们会说它仍然有点慢: - )

    HTH,Nathan

答案 2 :(得分:1)

此查询将获得您想要的内容,并且速度更快。


select FirstName, 
       LastName, 
       ID + replicate('0',8-len(ID)-len(cast(rankNumber as varchar)))+cast(rankNumber as varchar)
from (
        select dense_rank() over (partition by id order by rownumber) rankNumber,
        FirstName, 
        LastName,
ID from ( select row_number() over (Order by FirstName) rownumber, FirstName, LastName, RTRIM(cast(FirstName as char(4)))+ RTRIM(cast(LastName as char(2))) as ID from person ) A ) B

答案 3 :(得分:0)

如何通过获取现有WHILE的最大序列号后缀(@I)来避免使用内部NEW_NAMEKEY循环,如果大于0则只添加1,否则为0它返回NULL。