【问题标题】:Cursor use to update another table using SQL Server, T-SQL?游标使用 SQL Server、T-SQL 来更新另一个表?
【发布时间】:2019-05-31 07:34:44
【问题描述】:

我有一张桌子,我需要对其进行初始更新,然后每晚更新一次。我有一个文件名,我需要从中获取值才能更新两列。我来自 Oracle PL/SQL 背景,在 SQL Server 和 T-SQL 方面经验很少。我很擅长使用 Oracle 游标来遍历表,并且理解它们在 SQL Server 中做事是有争议的。

我的最终目标是向用户提供有关每种文件类型和水权类型已处理多少的信息。

文件名如下所示:

UPDATE_TO_FILE_STATEMENT_OF_CLAIM_41L_44799_00_2780.pdf
PRELIMINARY_DECREE_STATEMENT_OF_CLAIM_41G_40643_00_1134.pdf
FILE_STOCKWATER_PERMIT_40N_30116114_10437.pdf
UPDATE_TO_FILE_GROUND_WATER_CERTIFICATE_40E_30025354_10399.pdf

从这个文件名中,我需要拉取文件类型和水权类型。有 4 种文件类型和 20 种水权类型。我可以使用Case,但是如果他们添加文件类型或 Water Right 类型,我必须更改代码。解析名称也不是一件容易的事。我已经创建了一个表格,其中包含我将使用的代码、文件名的外观、描述以及它是文件类型还是水权类型。

FILE_CD FILE_NAME                       DESCR                       FILE_TYPE
----------------------------------------------------------------------------
62GW    62-73_GROUND_WATER_RECORD       62-73 GROUND WATER RECORD       WT
CDWR    CONSERVATION_DISTRICT_RECORD    CONSERVATION DISTRICT RECORD    WT
UPDT    UPDATE_TO_FILE_                 UPDATE TO FILE                  FT

我的计划是遍历此表并为初始运行运行更新。我还将遍历此表,并在触发器中为每晚添加的每条记录进行一次更新。

这是我的代码:

BEGIN TRANSACTION

DECLARE @F_type NVARCHAR(2)
DECLARE @F_name NVARCHAR(30)
DECLARE @F_CD NVARCHAR(4)

-- local means the cursor name is private to this code
-- fast_forward enables some speed optimizations
DECLARE c_TUpdt CURSOR LOCAL FAST_FORWARD FOR
   SELECT 
       FILE_CD, FILE_NAME, FILE_TYPE
   FROM 
       PRDECMSTATS.dbo.FileType 
   WHERE 
       FILE_CD NOT IN ('OTHT', 'OTHF')

OPEN c_TUpdt

FETCH NEXT FROM c_TUpdt INTO @F_CD, @F_name, @F_type

WHILE @@fetch_status = 0
BEGIN
    -- CHECK FILE TYPE
    IF @F_type = 'FT' THEN
       UPDATE PRDECMSTATS.dbo.FileDetails 
       SET File_Type = @F_CD
       WHERE File_Type IS NULL
         AND FileNAME LIKE @F_name || '%'
    END IF

    IF @F_type = 'WT' THEN
       UPDATE PRDECMSTATS.dbo.FileDetails 
       SET WR_Type = @F_CD
       WHERE WR_Type IS NULL
         AND FileNAME LIKE '%' || @F_name || '%'
    END IF

    FETCH NEXT FROM c_TUpdt INTO @F_CD, @F_name, @F_type
END

CLOSE c_TUpdt
DEALLOCATE c_TUpdt

/* need to then traverse for other types, these will need to be corrected by hand at some point */
UPDATE PRDECMSTATS.dbo.FileDetails 
SET File_Type = 'OTHF'
WHERE File_Type IS NULL

UPDATE PRDECMSTATS.dbo.FileDetails 
SET WR_Type = 'OTHT'
WHERE WR_Type IS NULL

COMMIT TRANSACTION

我希望这会根据文件名中的数据更新 2 列。

这行得通吗?有没有更好的方法来做到这一点?

【问题讨论】:

    标签: sql-server cursors mss


    【解决方案1】:

    光标可以工作,但几乎从来都不是最有效的答案。我相信这是您光标的完全替代品

    UPDATE fd
    SET File_Type = ft.FILE_CD
    FROM PRDECMSTATS.dbo.FileDetails fd
    INNER JOIN PRDECMSTATS.dbo.FileType ft ON ft.FILE_CD NOT IN ('OTHT', 'OTHF')
        AND ft.FILE_TYPE='FT'
        AND fd.File_Type IS NULL
        AND fd.FileNAME LIKE ft.FILE_NAME + '%'
    
    UPDATE fd
    SET WR_Type = ft.FILE_CD
    FROM PRDECMSTATS.dbo.FileDetails fd
    INNER JOIN PRDECMSTATS.dbo.FileType ft ON ft.FILE_CD NOT IN ('OTHT', 'OTHF')
        AND ft.FILE_TYPE='WT'
        AND fd.WR_Type IS NULL
        AND fd.FileNAME LIKE  '%' + ft.FILE_NAME + '%'
    

    它可以被浓缩成一个单一的陈述,但我认为它会使它变得不那么清晰。顺便说一句,“+”运算符在 SQL Server 中连接。此外,第一个 UPDATE 在字符串前面没有通配符,如第二个;不知道是不是故意的。

    扩展超出问题的光标,这可以替换所有内容

    UPDATE fd
    SET File_Type = ISNULL(ft.FILE_CD,'OTHF')
    FROM PRDECMSTATS.dbo.FileDetails fd
    LEFT JOIN PRDECMSTATS.dbo.FileType ft ON ft.FILE_CD NOT IN ('OTHT', 'OTHF')
        AND ft.FILE_TYPE='FT'
        AND fd.FileNAME LIKE ft.FILE_NAME + '%'
    WHERE fd.File_Type IS NULL
    
    UPDATE fd
    SET WR_Type = ISNULL(ft.FILE_CD,'OTHT')
    FROM PRDECMSTATS.dbo.FileDetails fd
    LEFT JOIN PRDECMSTATS.dbo.FileType ft ON ft.FILE_CD NOT IN ('OTHT', 'OTHF')
        AND ft.FILE_TYPE='WT'
        AND fd.FileNAME LIKE  '%' + ft.FILE_NAME + '%'
    WHERE fd.WR_Type IS NULL
    

    【讨论】:

    • 通配符是有意的,因为文件名总是以文件类型开头,因此不需要通配符。这真是太棒了。虽然游标在 Oracle 中工作得很好,但它们在 MS SQL 中有点麻烦。很高兴看到其他选择和做事方式。谢谢。
    • 有趣,第一个选项将所有文件类型设置为 OTHF,第二个选项什么也没做。
    • 好的,这也需要文件名上的 RTRIM。我喜欢单通答案,但它只适用于 WR_type 而不是 File_Type。单次通过也明显更快。
    【解决方案2】:

    当然,您可以为此使用游标,但您可以通过一次数据传递来完成所有这些操作(效率更高、效率更高)。比较一下去杂货店买所有东西与去商店买牛奶、开车回家、回到商店买面包、开车回家等等。

    ;WITH ft AS
    (
      SELECT FILE_CD, FILE_TYPE, 
        Pattern = CASE FILE_TYPE WHEN 'WT' THEN '%' ELSE '' END + FILE_NAME + '%'
      FROM PRDECMSTATS.dbo.FileType
      WHERE FILE_CD NOT IN ('OTHT','OTHF')
    )
    UPDATE f SET 
      FILE_TYPE = CASE WHEN ft.FILE_TYPE = 'FT' THEN ft.FILE_CD ELSE 'OTHF' END,
      WR_TYPE = CASE WHEN ft.FILE_TYPE = 'WT' THEN ft.FILE_CD ELSE 'OTHT' END
    FROM PRDECMSTATS.dbo.FileDetails AS f
    LEFT OUTER JOIN ft -- changed this
    ON f.FileNAME LIKE ft.pattern;
    

    现在,你的问题有这样的逻辑:

    UPDATE PRDECMSTATS.dbo.FileDetails 
    SET File_Type = 'OTHF'
    WHERE File_Type IS NULL
    
    UPDATE PRDECMSTATS.dbo.FileDetails 
    SET WR_Type = 'OTHT'
    WHERE WR_Type IS NULL
    

    您抱怨我将 File_Type 设置为 OTHF,但这正是您已经在做的!这是我得到的结果:

    根据您提供的示例数据,以及您在伪光标中编写的逻辑,我不知道您如何获得:

    • WR_Type 中除 OTHT 以外的任何值(因为您在问题中提供的文件均不匹配 WT 的模式)
    • File_Type 中的 OTHT(您的光标和我的代码都无法做到这一点)
    • 在 WR_Type 中完全没有 OTHF(您的光标和我的代码都不能这样做)

    也许只从一整套样本数据(例如,还包括至少一个与 WT 模式匹配的文件)和所需结果重新开始,并且不要向我们显示光标中的光标中间,因为我认为您在其中存在逻辑错误,这些错误会导致读者产生假设,并且会加倍地使您感到困惑。

    【讨论】:

    • 这影响了 0 行。我将不得不再看看这个。
    • 我肯定需要 RTRIM 文件名。这给了我完美的 WR_TYPE,但由于某种原因,FILE_TYPE 仍然为每条记录返回 OTHF。
    • 可能您表中的数据与您问题中的数据不相似。
    • ...或者您没有正确复制代码。您很有可能将OTHF 放在both CASE 表达式中。
    • 我完全复制了代码,只修改了表名并添加了 RTRIM。如果它更新文件类型,它会将 WR 类型设置为 OTHT,如果它更新 WR 类型,它会将文件类型设置为 OTHF。它不会正确更新两者。
    猜你喜欢
    • 1970-01-01
    • 2011-02-22
    • 1970-01-01
    • 1970-01-01
    • 2021-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-12
    相关资源
    最近更新 更多