调用动态SQL或存储过程

时间:2019-09-13 19:41:35

标签: sql-server tsql dynamic-sql

我正在使用搜索对象查询(可在Internet上找到,希望我能称赞开发人员)在数据库中搜索编写查询时所需的列。输出搜索对象查询允许我输入要查找的表的类型(部分名称)以及我要查找的列名称(部分名称)。我一直在尝试修改搜索对象查询,以便它返回找到的第一个值(前1个)。这将有助于我快速查看一下该列是否包含我要查找的特定类型的数据。

我试图将其写为一个存储过程,可以传递两个参数(部分表和部分列名),并且还尝试使用动态SQL(我第一次尝试使用它,所以我初学者使用它)。我在使用动态SQL方面取得了一定的成功,但是只能得到它来产生一个结果,而不是针对搜索对象输出中的所有结果多次调用它。我使用的代码如下所示:

--  This is the search object query found on internet
Use masterdb

Select a.name, b.name
From sysobjects a
Inner Join syscolumns b On a.id = b.id
Where b.name like '%Result%'
  And a.name like '%Lab%'
Order By a.name, b.name

--  This is a separate query I used to test calling the data with dynamic SQL
DECLARE @value VARCHAR(100), @tablename VARCHAR(100)

SET @value = 'Result'
SET @tablename = 'LabSpecimen'

DECLARE @sqlText NVARCHAR(1000); 
SET @sqlText = N'SELECT Top 1 ' + @value + ' FROM testndb.dbo.' +     @tablename

EXEC (@sqlText)

如果我使用搜索对象查询并搜索具有结果的实验​​室名称和列名称的表,则可能会得到如下输出:

LabMain,ResultID
LabSpecimen,ResultCategory
LabSpecimen,ResultDate
LabSpecimen,Results

我想让搜索对象查询从第一列中的表中提取数据,并从第二列中的列名中提取数据,并返回找到的第一个值,从而为给定的列名/表提供示例输出。输出看起来像这样:

LabMain,ResultID,E201812310001
LabSpecimen,ResultCategory,ExampleCategory
LabSpecimen,ResultDate,20181231
LabSpecimen,Results,34.20

2 个答案:

答案 0 :(得分:0)

好吧,我真的不想发布答案,但是可以了。

因此,第一个真正非常重要的事情是:SQL注入。根据OWASP,SQL注入是运行大约十年的第一安全漏洞。基本上,SQL注入是您使用动态SQL的地方,该动态SQL具有由用户填充的sql命令的任何片段。因此,在OP的情况下,此部分位于此处:

SET @value = 'Result'
SET @tablename = 'LabSpecimen'

DECLARE @sqlText NVARCHAR(1000); 
SET @sqlText = N'SELECT Top 1 ' + @value + ' FROM testndb.dbo.' +     @tablename

EXEC (@sqlText)

...如果最终化身是@tableName和@value被用户填充为搜索的一部分?然后,用户可以进行“搜索”,最终注入直接运行服务器的sql语句;举一个便宜的例子,想象一下@value:

3' ; drop table @tableName --

...将继续删除与您传入的@tablename匹配的每个表。

无论如何,当我们经历这个问题时,我们将在每个步骤中牢记SQL注入。

问题#1:如何获取匹配的表/列。

您几乎已经确定了这一点。唯一缺少的是将其放入一个临时表中,以便您可以遍历它(并将其限制为U型,因为否则将获得存储的proc和系统表。)模式信息-这样,如果您的表具有不同的模式,则仍然可以获取结果。

declare @tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare @columnNameFragment varchar(100) -- be changed to stored proc args

set @tableNameFragment = 'Performance' -- and populated by the user calling
set @columnNameFragment = 'status' -- the proc (instead of hard-coded.)

declare @entityMatches TABLE (TableName varchar(200), ColName varchar(128))

insert into @entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name 
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(@tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(@columnNameFragment,'') + '%')

现在,请注意,虽然使用 使用了@tableNameFragment和@columnNameFragment,但在 dynamic 查询中并未使用它们。用户是否将恶意内容放入这些值中都没关系

问题2-如何遍历表

基本上,您将需要一个光标。我讨厌光标,但有时(像这样),它们是必需的。

问题#3-如何实际执行动态查询并获取结果

这实际上比看起来要复杂。您不能为返回值做原始的EXEC(),也不能简单地让正在执行的cmd填充变量-因为EXEC(和SP_ExecuteSql在不同的上下文中运行,所以它们不能在外部填充变量)您的脚本。)

您需要使用SP_ExecuteSQL,但要指定一个返回变量,以内部sql命令填充。例如:

declare @sqlCmd nvarchar(max)
declare @dynamicReturn varchar(max)

set @sqlCmd = 'select @retVal=1'

EXEC Sp_executesql @sqlCmd,
    N'@retVal varchar(max) output',
    @dynamicReturn output

select @dynamicReturn

问题4-如何编写动态命令

在这里事情变得井井有条,因为这是我们使用动态SQL命令的地方。这里重要的是:您不能使用用户提供的任何输入。这意味着您不能使用变量@tableNameFragment或@columnNameFragment。不过,您可以使用@entityMatches表中的值。为什么?因为用户没有填充它们。它们由sys表中的数据填充-用户是否在输入变量中添加了恶意内容并不重要,@ entityMatches数据仅包含匹配的现有表/列名称。

同样重要:如果将来的开发人员需要调整或复制/粘贴内容,则在编写可能会出现问题的代码时-您应该添加注释警告以阐明问题。

>

那么,将它们放在一起吗?您将看到类似以下的内容:

declare @tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare @columnNameFragment varchar(100) -- be changed to stored proc args

set @tableNameFragment = 'Performance' -- and populated by the user calling
set @columnNameFragment = 'status' -- the proc (instead of hard-coded.)

declare @entityMatches TABLE (TableName varchar(200), ColName varchar(128))

insert into @entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name 
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(@tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(@columnNameFragment,'') + '%')

declare @returnResults TABLE (TableName varchar(200), ColName varchar(128), FirstValue varchar(max))

declare Cur Cursor For select TableName,ColName from @entityMatches
declare @cursorTable varchar(200), @cursorColumn varchar(128)
open Cur
fetch Next from cur into @cursorTable,@cursorColumn
while @@FETCH_STATUS = 0
begin 
    -- Note: the variables @cursorTable, @cursorColumn are NOT user populated
    -- but instead are populated from the Sys tables.  Because of this,
    -- this dynamic sql below is not SQL-Injection vulnerable (the entries
    -- are not populated from user entry of any sort.)
    -- Be very careful modifying the lines below to make sure you don't
    -- introduce a vulnerability.
    declare @sqlCmd nvarchar(max)
    declare @dynamicReturn varchar(max)
    set @sqlCmd = 'select top 1 @retVal=[' + @cursorColumn + '] from ' + @cursorTable
    EXEC Sp_executesql @sqlCmd,
        N'@retVal varchar(max) output',
        @dynamicReturn output

    insert into @returnResults values (@cursorTable, @cursorColumn, @dynamicReturn)

    fetch Next from cur into @cursorTable,@cursorColumn
End
close cur
deallocate cur

select * from @returnResults

答案 1 :(得分:-1)

创建一个存储过程,如下所述。

sysobjectsyscolumn获取表名和列名,并根据存储过程的参数将其添加到哈希表中。之后,声明一个游标,并在游标循环中创建列和表名的动态查询,并从游标循环表中获取当前列的第一行。之后,执行查询并更新哈希表中的结果。在查找结束时,选择“从哈希表记录”。检查以下存储过程。希望对您有帮助。

Create procedure Sp_GetSampleData
    @TName varchar(200) = ''
as
    Select  
        a.name TableName, b.name ColumnName,
        CAST('' as varchar(max)) as SampleValue 
    into 
        #Tbl
    from 
        sysobjects a
    inner join 
        syscolumns b on a.id = b.id
    where 
        (@TName='' or a.name = @TName)
    order ny 
        a.name, b.name

    declare @TableName varchar(200), @ColumnName varchar(200),
            @sqlText nvarchar(max), @Val varchar(max)

    declare Cur Cursor For
        select TableName, ColumnName 
        from #Tbl

    open Cur
    fetch Next from cur into @TableName,@ColumnName
    while @@FETCH_STATUS =0
    begin 
            set @sqlText=''
            set @Val=''
             SET @sqlText = N'SELECT Top 1 @Val=[' + @ColumnName + '] FROM testndb.dbo.' +     @TableName

            EXEC Sp_executesql
              @sqlText,
              N'@Val varchar(max) output',
              @Val output

             print @sqlText
          update #Tbl set SampleValue=@Val where TableName=@TableName and ColumnName =@ColumnName

      fetch Next from cur into @TableName,@ColumnName
    End
    close cur
    deallocate cur
  select * from #Tbl