【问题标题】:Creating a parametrized field name for a SELECT clause为 SELECT 子句创建参数化字段名称
【发布时间】:2015-06-04 13:59:51
【问题描述】:

我目前正在研究数据 API,我想根据请求的字段名称生成 SQL 查询。这些字段名称将公开可用,用户还可以使用别名来重命名 API 返回的数据。

这些表中可用的记录不会被归档为“绝密”,但我仍然希望通过使用参数化的字段名称和别名来防止任何 SQL 注入,以避免人们添加不需要的数据。

DECLARE @requestedFieldName NVARCHAR(max) = 'FirstName';

DECLARE @alias NVARCHAR(max) = 'First name';

从 MyTable 中选择 @requestedFieldName 为 @alias

在 WHERE 子句和其他子句中使用参数的例子很多,涉及与字段匹配/分配/设置的值...但是,我在 SQL 服务器中找不到任何涉及参数化字段名称/别名的示例(有一些关于 JDBCMySQL 的问题,但 SQL Server 没有)

有没有办法对字段名称进行参数化,或者我是否应该考虑构建一个中间接口来保存用户可以请求的每个可用字段的列表? (我知道第二个选项经常使用,但我们有很多表,它们的结构会定期更改)。

【问题讨论】:

  • AFAIK,使用纯 t-sql 实现这一目标的唯一方法是使用动态 SQL。没有其他方法可以使用参数指定别名,也没有其他方法可以指定列名。
  • 也许你可以用多个视图做你想做的事,让不同的用户访问不同的视图。
  • 即使您对字符进行转义,也可能由于列不存在而出错。解决此问题的一种可能方法是构建基于 MsSQL 列名标准的正则表达式,并拒绝其他所有内容。
  • 实际上,您的主要问题不是列名。可以根据sys.columns 表轻松检查它们。然而,化名是你盔甲的软肋。您如何将数据返回给 API 客户端?
  • Web 服务将以 JSON 格式返回数据。别名可以在 sql 查询之后应用于该 JSON 字符串。

标签: c# sql parameters sql-server-2014 parameterized


【解决方案1】:

使用varchar参数获取列列表和表名。
然后对照sys.columns 表检查这些参数的值。
如果所有列都匹配,那么您可以安全地使用这些值来创建动态 SQL 并执行它。如果您还需要用户通过条件来构建 where 子句,请对那里的列进行相同的测试。

更新 现在我已经创建了一个示例代码,我认为这可能不是最适合您的解决方案,除非您可以找到一种方法来为每个表动态创建存储过程。原因是 where 子句,以及每个表具有不同数据类型的不同列数的事实,使得 where 子句参数列表的创建非常依赖表。 (实际上,select参数的创建数量也取决于表)。
因此,我不确定这是一个实用的解决方案,但对我来说这是一个有趣的 t-sql 挑战 不过,我已经创建了代码示例,也许有人会找到使用这种程序的方法,所以我会分享它:

首先,示例表的 DDL:

CREATE TABLE [dbo].[TblDays](
    [Day_Id] [int] IDENTITY(1,1) NOT NULL,
    [Day_Date] [date] NOT NULL,
    [Day_Name] [nchar](10) NOT NULL,
 CONSTRAINT [PK_TblDays] PRIMARY KEY CLUSTERED 
(
    [Day_Id] ASC
)

以及一个安全的动态 sql 存储过程的示例:

CREATE PROCEDURE DynamicalySelectFromTblDays
(
    @ErrorMessage varchar(100) output,
    @SelectCol1 sysname,
    @SelectCol2 sysname = null,
    @SelectCol3 sysname = null,
    @WhereCol1 sysname = null,
    @WhereCol2 sysname = null,
    @WhereCol3 sysname = null,
    @WhereValue1 int = null,
    @WhereValue2 date = null,
    @WherValue3 nchar(10) = null
)
AS

DECLARE @ExceptedColumnCount int,
        @ActualColumnsCount int,
        @TableName sysname = 'TblDays',
        @SQL varchar(max)

-- get the number of columns expcected to get back from sys.columns
SELECT @ExceptedColumnCount = COUNT(*) 
FROM (VALUES (@SelectCol1), (@SelectCol2), (@SelectCol3)) as x(a)
WHERE a is not null 

-- get the number of columns from sys.columns
SELECT @ActualColumnsCount = COUNT(*)
FROM sys.columns c
INNER JOIN sys.tables t ON(c.object_id = t.object_id)
WHERE t.name = @TableName
AND c.name IN(@SelectCol1, @SelectCol2, @SelectCol3)

-- only if we get all of the non null columns back from the sys.columns table
IF @ExceptedColumnCount = @ActualColumnsCount AND (@ExceptedColumnCount = 0 OR @ActualColumnsCount > 0)
BEGIN

    -- same test for where clause columns
    SELECT @ExceptedColumnCount = COUNT(*) 
    FROM (VALUES (@WhereCol1), (@WhereCol2), (@WhereCol3)) as x(a)
    WHERE a is not null 

    SELECT @ActualColumnsCount = COUNT(*)
    FROM sys.columns c
    INNER JOIN sys.tables t ON(c.object_id = t.object_id)
    WHERE t.name = @TableName
    AND c.name IN(@WhereCol1, @WhereCol2, @WhereCol3)

    IF @ExceptedColumnCount = @ActualColumnsCount AND (@ExceptedColumnCount = 0 OR @ActualColumnsCount > 0)
    BEGIN

        -- dynamically build the sql statement:
        SET @SQL = 'SELECT '+ COALESCE(@SelectCol1 +', ', '') + COALESCE(@SelectCol2 +', ', '')+ COALESCE(@SelectCol3, '') +
                   ' FROM '+ @TableName 

        IF COALESCE(@WhereCol1, @WhereCol2, @WhereCol3) IS NOT NULL
        BEGIN

            SET @SQL = @SQL + ' WHERE '
            IF @WhereCol1 IS NOT NULL 
                SET @SQL = @SQL + @WhereCol1 +' = '+ CAST(@WhereValue1 as varchar(10))
            IF @WhereCol2 IS NOT NULL 
                SET @SQL = @SQL + ' AND ' + @WhereCol2 +' = '''+ CONVERT(char(10), @WhereValue2, 120) +''''
            IF @WhereCol3 IS NOT NULL 
                SET @SQL = @SQL + ' AND ' + @WhereCol3 +' = '''+ @WherValue3 +''''
        END

        PRINT @SQL
        -- EXEC(@SQL) -- Commented out since it's better to print first and look at the results, and only then execute it.
    END
    ELSE
    BEGIN
        -- perhaps using raise error instead would suit your needs better
        SELECT @ErrorMessage = 'where columns does not match table columns'
    END
END
ELSE
BEGIN
    -- perhaps using raise error instead would suit your needs better
    SELECT @ErrorMessage = 'select columns does not match table columns'
END

GO

当然还有一个示例执行代码:

DECLARE @ErrorMessage varchar(100),
        @SelectCol1 sysname = 'Day_Id',
        @SelectCol2 sysname = 'Day_Date',
        @SelectCol3 sysname = 'Day_Name',
        @WhereCol1 sysname = 'Day_Id',
        @WhereCol2 sysname = 'Day_Date',
        @WhereCol3 sysname = null,
        @WhereValue1 int = 1,
        @WhereValue2 date = convert(date, '14/04/2015', 103),
        @WherValue3 nchar(10) = null

EXEC DynamicalySelectFromTblDays    @ErrorMessage output, 
                                    @SelectCol1, 
                                    @SelectCol2, 
                                    @SelectCol3,
                                    @WhereCol1,
                                    @WhereCol2,
                                    @WhereCol3,
                                    @WhereValue1,
                                    @WhereValue2,
                                    @WherValue3


PRINT @ErrorMessage

所有这些混乱的结果是:

SELECT Day_Id, Day_Date, Day_Name FROM TblDays WHERE Day_Id = 1 AND Day_Date = '2015-04-14'

您可以将每个表的列列表添加到您的 c# 代码中,并在那里构建动态查询。它可能会是更短更快的代码。

【讨论】:

  • 最初我为列列表写了table valued parameter,但后来我意识到这也会带来安全风险。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-17
  • 1970-01-01
  • 2021-02-14
  • 1970-01-01
  • 2017-08-06
相关资源
最近更新 更多