如何在 sql 中修复该过程

How to fix that procedure in sql

我创建的过程不计算列中的空行,但查询抛出错误:@tableName 未声明且对象名称 tempTable 无效。我不知道为什么代码会抛出这些错误,因为所有变量都已声明。

Msg 1087, Level 16, State 1, Procedure getLenCol, Line 7 [Batch Start Line 0]
Must declare the table variable "@tableName".
Msg 208, Level 16, State 1, Line 11
Invalid object name 'tempTable'.


CREATE OR ALTER PROC getLenCol
      @tableName  varchar(255),
      @colName varchar(255)
    as
      DECLARE @tempTable Table(smth varchar(255));
      DECLARE @query varchar(255)
      insert into @tempTable(smth) select @colName from @tableName where @colName is not null
      exec (@query)
      select @@ROWCOUNT
    GO
    exec getLenCol 'users','name'

另外,当我以另一种方式制作该程序时,该代码抛出 消息 1087,级别 15,状态 2,第 11 行 错误。

Must declare the table variable "@tempTable".

CREATE OR ALTER PROC getLenCol
  @tableName  varchar(255),
  @colName varchar(255)
as
  DECLARE @tempTable Table(smth varchar(255))
  DECLARE @query varchar(255)
  SET @query = concat('insert into @tempTable(smth) select ',@colName,' from ',@tableName,' where ',@colName,' is not null');/*@colName from @tableName where @colName is not NULL*/
  exec (@query)
  select @@ROWCOUNT
GO
exec getLenCol 'users','name'

是否有解决该错误的方法?

显然,您的代码受到 SQL 注入攻击——正如对问题的评论所解释的那样。

但是您的问题是围绕 table 变量的范围规则。您可以使用以下方法解决该问题:

set @query = concat('select ', @colName, ' from ', @tableName, ' where ', @colName,' is not null');

insert into @tempTable (smth)
    exec(@query);

我认为对于您建议的逻辑,SQL 注入漏洞没有任何解决方法。但是,您的代码如此 non-sensical 我怀疑它是否真的代表了您的实际代码。

我强烈建议不要使用这种方法,首先调用这个过程是一样多的,如果不是更多的打字只是做一个计数。比较两者

EXECUTE dbo.getLenCol @tableName = 'dbo.SomeTable', @colName = 'ID';
SELECT COUNT(ID) FROM dbo.SomeTable;

即使使用缩短的 exec,并且不使用命名参数,它也更长:

EXEC dbo.getLenCol dbo.SomeTable', 'ID';

像这样将对象名称作为参数传递的捕获所有查询将成为正确方法的情况非常非常罕见。有一些维护查询有用,但这些是例外情况,不是规则。

如果你必须这样做,你应该先做一些验证,并在执行任何动态 SQL 之前检查 table 名称和列名称是否有效 COL_LENGTH(@tableName, @ColName)。例如

CREATE OR ALTER PROC getLenCol @tableName SYSNAME, @colName SYSNAME
AS
BEGIN

    IF COL_LENGTH(@tableName, @ColName) IS NOT NULL
    BEGIN
        DECLARE @SQL NVARCHAR(MAX) = CONCAT('SELECT COUNT(', @colName, ') FROM ', @tableName, ';');
        EXECUTE sp_executesql @SQL;
        RETURN;
    END

    -- TABLE OR COLUMN WAS NOT VALID RETURN -1 TO INDICATE THAT
    SELECT -1;

END

似乎很多人都没有意识到 SQL 注射的危险,包括戈登,我想先详细说明一下。让我们来看看 (在撰写本文时),它给出以下内容:

CREATE OR ALTER PROC getLenCol
      @tableName  varchar(255),
      @colName varchar(255)
as
    DECLARE @query varchar(255)
    DECLARE @tempTable Table(smth varchar(255))
    set @query = concat('select ', @colName, ' from ', @tableName, ' where ', @colName,' is not null');

    insert into @tempTable  (smth)
    exec(@query);
GO

现在,让我们成为一个恶意的人:

EXEC dbo.getLenCol @colName = N'1; CREATE LOGIN NewLogin WITH PASSWORD = ''1'', CHECK_POLICY = OFF;/*',
                   @tableName =N'*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--';

那么,在动态 SQL 运行 中,上面的内容是什么?让我们通过在 SP 的定义中添加 PRINT @query; 来找出答案:

select 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* from */ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null

并且,为了便于阅读,进行了一些格式化:

select 1;
CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;
/* from */
ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;
-- where 1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/* is not null

哦。哦哦哦哦哦哦。恭喜您成为拥有新 sysadmin LOGIN!

的 SQL 服务器的新主人

从不,将未经处理的字符串注入 SQL 中的字符串。 从不.

我不想重复我自己,而是要 link 我的文章 Dos and Don'ts of Dynamic SQL,但是,您可以通过几次使用 QUOTENAME 轻松地使上述查询安全:

CREATE OR ALTER PROC getLenCol
      @schemaName sysname = N'dbo', --You should define the schema too
      @tableName  sysname, --An object can't be longer than 128 characters, so sysname is best
      @colName sysname
AS
BEGIN
    DECLARE @query nvarchar(MAX);
    DECLARE @tempTable Table(smth varchar(255));
    SET @QUERY = CONCAT(N'SELECT ', QUOTENAME(@colName),N' FROM ', QUOTENAME(@schemaName), N'.', QUOTENAME(@tableName), N' WHERE ', QUOTENAME(@colName), N' IS NOT NULL;');
    PRINT @query;
    INSERT INTO @tempTable (smth)
    EXEC sys.sp_executesql @query;
END;
GO

如果我们运行上面的EXEC声明之前会发生什么?那么你得到下面的声明(添加了格式):

SELECT [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*]
FROM [dbo].[*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--]
WHERE [1; CREATE LOGIN NewLogin WITH PASSWORD = '1', CHECK_POLICY = OFF;/*] IS NOT NULL;

毫不奇怪,这产生了错误

Invalid object name 'dbo.*/ ALTER SERVER ROLE sysadmin ADD MEMBER NewLogin;--'.

现在你的动态语句是安全的,不会被注入。