在动态 SQL 中插入字符串而不是值

问题描述 投票:0回答:1

我正在尝试在 catch 块中记录 SP 的参数,我已经为参数创建了动态字符串。但是,当它插入表时,它会插入字符串而不是参数的值。下面是我的代码:

DECLARE @ErrorMessage VARCHAR(4000),
@ErrorSeverity INT,
@ErrorState INT,
        @ErrorProcedure VARCHAR(200),
        @ErrorLine INT,
        @ErrorNumber INT,
        @ErrorParams VARCHAR(MAX)='';

-- Get the error information
SET @ErrorMessage = ERROR_MESSAGE();
SET @ErrorSeverity = ERROR_SEVERITY();
SET @ErrorState = ERROR_STATE();
SET @ErrorProcedure = ERROR_PROCEDURE();
SET @ErrorLine = ERROR_LINE();
SET @ErrorNumber = ERROR_NUMBER();

DECLARE @SPParameter AS TABLE (Id INT IDENTITY PRIMARY KEY, ParaName VARCHAR(100))
INSERT INTO @SPParameter (ParaName)
SELECT [name]
FROM sys.parameters 
WHERE OBJECT_ID = OBJECT_ID(@ErrorProcedure)

DECLARE @minId INT, @maxId INT
SELECT @minId = MIN(Id),@maxId = MAX(Id) FROM @SPParameter

SET @ErrorParams = ''''

WHILE @minId <= @maxId
BEGIN
    SELECT @ErrorParams = @ErrorParams + ''+ParaName+' = ''+ISNULL(CAST('+CAST(ParaName AS varchar)+' AS VARCHAR),'''')+'', '
    FROM @SPParameter WHERE Id=@minId
    SET @minId = @minId + 1
END

SET @ErrorParams = LEFT(@ErrorParams, LEN(@ErrorParams) - 2)

SET @ErrorParams = @ErrorParams + ''''''

INSERT INTO ErrorLog (ErrorMessage, ErrorSeverity, ErrorState, ErrorProcedure, ErrorLine, ErrorNumber, ErrorParams)
VALUES (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorNumber, @ErrorParams);

它正在插入我动态创建的字符串。下面是插入到我的日志表中的字符串:

'@Param1 = '+ISNULL(CAST(@Param1 AS VARCHAR),'') +
  ', @Param2 = '+ISNULL(CAST(@Param2 AS VARCHAR),'') +
  ', @Param3 = '+ISNULL(CAST(@Param3 AS VARCHAR),'') +
  ', @Param4 = '+ISNULL(CAST(@Param4 AS VARCHAR),''
sql sql-server error-handling
1个回答
0
投票

免责声明:我不一定建议将此方法用于一般用途,但我将其发布为原始海报在上述问题中试图完成的替代方案。

为了实现您的目标,您也许可以使用如下初始代码片段来编写存储过程:

DECLARE @ErrorParams VARCHAR(MAX);
SET @ErrorParams = 'Not yet customized';

然后,您可以针对所有已定义的存储过程运行一个进程,以将上述代码片段替换为以下内容:

SET @ErrorParams = CONCAT('@Parm1=', @Parm1, ', ', '@Parm2=', @Parm2, ...);

以下逻辑可以使用游标循环来完成此操作,该循环检索每个存储过程定义,找到

SET @ErrorParams = ...;
语句,将其替换为实际的存储过程参数,然后使用
ALTER PROCEDURE
语句重新应用该定义。

这可能还没有100%准备好迎接黄金时段,但这是一个开始。我建议您在应用此过程之前编写脚本并安全地存储原始存储过程,以防您放错了“;”或者出现其他问题。

DECLARE CSR CURSOR FOR 
    SELECT P.object_id, P.name, D.Definition
    FROM sys.procedures P
    CROSS APPLY (SELECT OBJECT_DEFINITION(P.object_id) AS Definition) D
    WHERE P.name LIKE 'Proc%'
    AND D.Definition LIKE '%SET @ErrorParams =%;%'
OPEN CSR

DECLARE @ObjectId INT
DECLARE @Name SYSNAME
DECLARE @Definition NVARCHAR(MAX)

FETCH NEXT FROM CSR INTO @ObjectId, @Name, @Definition
WHILE @@FETCH_STATUS = 0
BEGIN
    DECLARE @StartPos INT = CHARINDEX('SET @ErrorParams =', @Definition)
    DECLARE @EndPos INT = CHARINDEX(';', @Definition, @StartPos)
    DECLARE @Length INT = @EndPos - @StartPos + 1

    DECLARE @ErrorParamsList NVARCHAR(MAX) = (
        -- '@Parm1=', @Parm1, ', ', '@Parm2=', @Parm2, ', ', '@Parm3=', @Parm3, ...'
        SELECT STRING_AGG(I.Item, ', '', '', ') WITHIN GROUP(ORDER BY PRM.parameter_id)
        FROM sys.parameters PRM
        JOIN sys.types T
            ON T.user_type_id = PRM.user_type_id
        CROSS APPLY (
            SELECT
                CASE 
                WHEN T.name = 'xml' -- Explicit conversion needed
                    THEN 'CONVERT(NVARCHAR(MAX), ' + PRM.name + ')'
                WHEN T.name in ('binary', 'varbinary') -- Custom conversion
                    THEN 'CONVERT(NVARCHAR(MAX), ' + PRM.name + ', 2)'
                WHEN T.name in ('datetime', 'datetime2') -- Custom conversion
                    THEN 'CONVERT(NVARCHAR(MAX), ' + PRM.name + ', 21)'
                -- ... Handle other custom or unsupported cases
                ELSE PRM.name -- Default conversion handled by CONCAT()
                END AS Expression
        ) E
        CROSS APPLY (
            -- '@ParmN=, @ParmN'
            -- TODO: Special handling for certain parameter types (binary, table, geo?)
            SELECT QUOTENAME(PRM.name + '=', '''') + ', ' + E.Expression AS Item
        ) I
        WHERE PRM.object_id = @ObjectId
    )

    DECLARE @ErrorParamsStatement NVARCHAR(MAX) =
        'SET @ErrorParams = CONCAT(' + @ErrorParamsList + ');'
  
    DECLARE @UpdatedDefinition NVARCHAR(MAX)
        = STUFF(@Definition, @StartPos, @Length, @ErrorParamsStatement)

    -- The original definition might be a CREATE PROC (or PROCEDURE)
    -- Change to an ALTER. Only one space is allowed afterthe CREATE.
    -- *** This is *not* entirely safe, if the code contains other "CREATE PROC" content.
    SET @UpdatedDefinition = REPLACE(@UpdatedDefinition, 'CREATE PROC', 'ALTER PROC')

    PRINT ''
    PRINT 'Procedure Name: ' + OBJECT_NAME(@ObjectId)
    PRINT 'ErrorParamsList: ' + @ErrorParamsList
    PRINT 'ErrorParamsStatement: ' + @ErrorParamsStatement
    PRINT 'Updated Definition:' + CHAR(10) + @UpdatedDefinition

    EXEC (@UpdatedDefinition)

    FETCH NEXT FROM CSR INTO @ObjectId, @Name, @Definition
END
  
CLOSE CSR
DEALLOCATE CSR

尽管语句终止符 (;) 在 SQL Server 中通常是可选的,但上述逻辑需要它的存在。

对于某些参数类型,需要显式或自定义转换。以上仅涵盖了几种,但可能还有其他需要定制的情况。如果您使用表参数或用户定义类型,这些可能需要特殊处理。

Null 值显示为空值。字符串、XML 和其他通常可能在代码中引用的值在上述实现中未引用。

包含换行符的字符串可能会在日志中跨多行显示。您可能想阅读“日志注入”并确定这是否是一个问题。

请参阅 this db<>fiddle 进行演示。

© www.soinside.com 2019 - 2024. All rights reserved.