【问题标题】:Stored Procedure algorithm taking 9 hours- better way of doing this?存储过程算法需要 9 小时 - 更好的方法?
【发布时间】:2013-03-21 03:36:43
【问题描述】:

我需要构建一个 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

【问题讨论】:

    标签: sql sql-server stored-procedures plsql


    【解决方案1】:

    这样的事情不用光标也能做到:

    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

    【讨论】:

    • 看起来不错,我会试一试!
    • 谢谢。我运行了它,它几乎可以完美运行,而无需我进行任何修改,尽管它创建了很多重复项。我不得不将分区更改为 LEFT(NAME_2,4)、LEFT(NAME_1,2) 分区。非常感谢! 9 小时缩短到 4 秒
    • 这很有道理......我已经为未来的观众更新了我的答案。
    【解决方案2】:

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

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

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

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

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

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

    有点,内森

    【讨论】:

      【解决方案3】:

      这个查询将得到你想要的,而且速度更快。

      
      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

      【讨论】:

      • 你需要做的是,使用这个查询来更新你的表。当然,您可以对其进行一些自定义。没有判断你为什么需要这样的身份证,只是帮助你得到它。但是,我不能停止告诉你,这种代理ID并不能帮助你查询。
      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-10
        • 1970-01-01
        • 2023-04-07
        • 1970-01-01
        相关资源
        最近更新 更多