2

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

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

现在该人的 id 的算法如下: - 最多占用人名的前 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

4 回答 4

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

于 2013-03-21T04:43:58.783 回答
3

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

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

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

  3. 继使用更适合程序工作的东西之后……您总是可以编写一个简单的 .NET 应用程序或类似的应用程序。从我自己的(有限的)经验来说,我在过去看到过这样做,但是里程往往会根据操作的复杂性(在你的情况下,就转换 UserId 字段而言听起来很简单)、数量和写它的人......我会说我从来没有见过它做得特别好(因为我们从来没有转身说“太棒了”)但更像是它完成了工作,所以我们会继续做一些事情否则,从经验中既不好也不坏(只是“平均”)。

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

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

有点,内森

于 2013-03-21T04:07:42.577 回答
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

于 2013-03-21T04:38:43.130 回答
0

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

于 2013-03-21T04:21:52.233 回答