【问题标题】:Trying to parameterize the database in a sql test script尝试在 sql 测试脚本中参数化数据库
【发布时间】:2019-09-12 18:46:16
【问题描述】:

我正在尝试创建一组可供我的测试人员使用的查询,这些查询可以重复,当然还有“参数”驱动。

我的一个测试涉及将一个表的数据类型与另一个表的数据类型进行比较,并且我尝试将查询参数化到使用该查询的人只需要修改查询的顶部区域的程度,但我遇到了问题一个区域,数据库名称。

我不想为此走动态 SQL 路线,但正在寻找解决问题的方法

    DECLARE @Source_DB      nvarchar(128) = 'SourceDB'
    ,       @Source_Table   nvarchar(128) = 'SourceTable'
    ,       @Source_Schema  nvarchar(128) = 'PJ';

    SELECT  *
    FROM    @Source_DB.INFORMATION_SCHEMA.COLUMNS isc  
    WHERE   isc.TABLE_NAME = @Source_Table AND isc.TABLE_SCHEMA = @Source_Schema

下面的完整查询:

    DECLARE @Source_DB      nvarchar(128) = 'SourceDB'
    ,       @Source_Table   nvarchar(128) = 'SourceTable'
    ,       @Source_Schema  nvarchar(128) = 'SS'
    ,       @Target_DB      nvarchar(128) = 'TargetDB'
    ,       @Target_Table   nvarchar(128) = 'TargetTable'
    ,       @Target_Schema  nvarchar(128) = 'TS';

    SELECT  a.Source_TABLE_CATALOG
    ,       b.Target_TABLE_CATALOG  
    ,       a.Source_SCHEMA  
    ,       b.Target_SCHEMA  
    ,       a.Source_TABLE_NAME  
    ,       b.Target_TABLE_NAME  
    ,       a.Source_ORDINAL_POSITION  
    ,       b.Target_ORDINAL_POSITION  
    ,       a.Source_COLUMN_NAME  
    ,       b.Target_COLUMN_NAME  
    ,       a.Source_DATATYPE  
    ,       b.Target_DATATYPE  
    ,       a.Source_Nullable  
    ,       b.Target_Nullable  
    ,       a.Source_Default  
    ,       b.Target_Default
    FROM    (
            SELECT  isc.TABLE_CATALOG       AS [Source_TABLE_CATALOG]
            ,       isc.TABLE_SCHEMA        AS [Source_SCHEMA]  
            ,       isc.TABLE_NAME          AS [Source_TABLE_NAME]  
            ,       isc.ORDINAL_POSITION    AS [Source_ORDINAL_POSITION]  
            ,       isc.COLUMN_NAME         AS [Source_COLUMN_NAME]  
            ,       CASE 
                        WHEN isc.DATA_TYPE IN ('decimal', 'money', 'numeric') 
                            THEN CONCAT(isc.DATA_TYPE, '(', isc.NUMERIC_PRECISION, ',', isc.NUMERIC_SCALE, ')') 
                        WHEN isc.DATA_TYPE IN ('datetime','date','int') 
                            THEN isc.DATA_TYPE 
                        WHEN isc.DATETIME_PRECISION IS NOT NULL 
                            THEN CONCAT(isc.DATA_TYPE, '(', isc.DATETIME_PRECISION, ')') 
                        WHEN isc.DATA_TYPE = 'varchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 8000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                            THEN 'varchar(MAX)' 
                        WHEN isc.DATA_TYPE = 'nvarchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 4000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                            THEN 'nvarchar(MAX)' 
                        ELSE 
                            CONCAT(isc.DATA_TYPE, '(', isc.CHARACTER_MAXIMUM_LENGTH, ')') 
                    END AS [Source_DATATYPE]
            ,       CASE isc.IS_NULLABLE
                        WHEN 'YES' THEN 'NULL'
                        WHEN 'NO' THEN 'NOT NULL'
                    END AS [Source_Nullable]
            ,       isc.COLUMN_DEFAULT AS [Source_Default]
            FROM    @Source_DB.INFORMATION_SCHEMA.COLUMNS isc  
            WHERE   isc.TABLE_NAME = @Source_Table AND isc.TABLE_SCHEMA = @Source_Schema
            ) a
            JOIN    (
                    SELECT  isc.TABLE_CATALOG       AS [Target_TABLE_CATALOG]
                    ,       isc.TABLE_SCHEMA        AS [Target_SCHEMA]  
                    ,       isc.TABLE_NAME          AS [Target_TABLE_NAME]  
                    ,       isc.ORDINAL_POSITION    AS [Target_ORDINAL_POSITION]  
                    ,       isc.COLUMN_NAME         AS [Target_COLUMN_NAME]  
                    ,       CASE
                                WHEN isc.DATA_TYPE IN ('decimal', 'money', 'numeric')
                                    THEN CONCAT(isc.DATA_TYPE, '(', isc.NUMERIC_PRECISION, ',', isc.NUMERIC_SCALE, ')')
                                WHEN isc.DATA_TYPE IN ('datetime','date','int')
                                    THEN isc.DATA_TYPE
                                WHEN isc.DATETIME_PRECISION IS NOT NULL
                                    THEN CONCAT(isc.DATA_TYPE, '(', isc.DATETIME_PRECISION, ')')
                                WHEN isc.DATA_TYPE = 'varchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 8000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                                    THEN 'varchar(MAX)'
                                WHEN isc.DATA_TYPE = 'nvarchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 4000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                                    THEN 'nvarchar(MAX)'
                                ELSE
                                    CONCAT(isc.DATA_TYPE, '(', isc.CHARACTER_MAXIMUM_LENGTH, ')')
                            END AS [Target_DATATYPE]
                    ,       CASE isc.IS_NULLABLE
                                WHEN 'YES' THEN 'NULL'
                                WHEN 'NO' THEN 'NOT NULL'
                            END AS [Target_Nullable]
                    ,       isc.COLUMN_DEFAULT      AS [Target_Default]
                    FROM    @Target_DB.INFORMATION_SCHEMA.COLUMNS isc
                    WHERE   isc.TABLE_NAME = @Target_Table AND isc.TABLE_SCHEMA = @Target_Schema 
                    ) b ON  a.Source_COLUMN_NAME = b.Target_COLUMN_NAME
    WHERE   a.Source_DATATYPE <> b.Target_DATATYPE OR
            a.Source_Nullable <> b.Target_Nullable OR
            a.Source_Default <> b.Target_Default

理想的结果是我可以让 SQL 查询工作,但它可能证明这是在动态查询之外无法完成的事情,从我的角度来看,这不会导致问题,因为代码会被保留在文本文件中(与所有其他模板化查询一起),并且在输入参数之前不会运行。

【问题讨论】:

  • “我不想为此走动态 SQL 路线,但我正在寻找解决问题的方法” 你不能不这样做。您不能用表达式或参数/变量替换对象的名称。它们必须是文字值。 SELECT * FROM @DBName.dbo.MyTable; 会产生错误Incorrect syntax near '.'.
  • 如果您使用的是该副本,请查看使用 QUOTENAME 的示例(并且被赞成),而不是接受的答案。
  • 对于这种特殊情况还有另一种解决方案,它涉及使用相关信息架构列的UNION ALL 创建一个跨数据库视图。您必须维护每个数据库的创建或删除视图(使用服务器级触发器)。
  • 有一个没有使用SQLCMD脚本变量的动态SQL的解决方案。不幸的是,这个问题被关闭了,所以我无法提供答案。
  • @DanGuzman 现在营业了

标签: sql-server sqlcmd


【解决方案1】:

T-SQL 语句中的标识符不能参数化。但是,您可以使用SQLCMD scripting variables,以便在编译和执行 T-SQL 查询之前替换变量,其功能类似于文本搜索/替换。

下面的示例使用 SQLCMD 脚本变量而不是 T-SQL 变量/参数。带有 SQLCMD 变量的脚本可以从 SSMS 以 SQLCMD 模式(查询-->选项-->SQLCMD 模式)或通过 SQLCMD 命令行实用程序运行。

:SETVAR SOURCE_DB YourSourceDatabase
:SETVAR TARGET_DB YourTargetDatabase

DECLARE
            @Source_Table   nvarchar(128) = 'SourceTable'
    ,       @Source_Schema  nvarchar(128) = 'SS'
    ,       @Target_Table   nvarchar(128) = 'TargetTable'
    ,       @Target_Schema  nvarchar(128) = 'TS';

    SELECT  a.Source_TABLE_CATALOG
    ,       b.Target_TABLE_CATALOG  
    ,       a.Source_SCHEMA  
    ,       b.Target_SCHEMA  
    ,       a.Source_TABLE_NAME  
    ,       b.Target_TABLE_NAME  
    ,       a.Source_ORDINAL_POSITION  
    ,       b.Target_ORDINAL_POSITION  
    ,       a.Source_COLUMN_NAME  
    ,       b.Target_COLUMN_NAME  
    ,       a.Source_DATATYPE  
    ,       b.Target_DATATYPE  
    ,       a.Source_Nullable  
    ,       b.Target_Nullable  
    ,       a.Source_Default  
    ,       b.Target_Default
    FROM    (
            SELECT  isc.TABLE_CATALOG       AS [Source_TABLE_CATALOG]
            ,       isc.TABLE_SCHEMA        AS [Source_SCHEMA]  
            ,       isc.TABLE_NAME          AS [Source_TABLE_NAME]  
            ,       isc.ORDINAL_POSITION    AS [Source_ORDINAL_POSITION]  
            ,       isc.COLUMN_NAME         AS [Source_COLUMN_NAME]  
            ,       CASE 
                        WHEN isc.DATA_TYPE IN ('decimal', 'money', 'numeric') 
                            THEN CONCAT(isc.DATA_TYPE, '(', isc.NUMERIC_PRECISION, ',', isc.NUMERIC_SCALE, ')') 
                        WHEN isc.DATA_TYPE IN ('datetime','date','int') 
                            THEN isc.DATA_TYPE 
                        WHEN isc.DATETIME_PRECISION IS NOT NULL 
                            THEN CONCAT(isc.DATA_TYPE, '(', isc.DATETIME_PRECISION, ')') 
                        WHEN isc.DATA_TYPE = 'varchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 8000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                            THEN 'varchar(MAX)' 
                        WHEN isc.DATA_TYPE = 'nvarchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 4000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                            THEN 'nvarchar(MAX)' 
                        ELSE 
                            CONCAT(isc.DATA_TYPE, '(', isc.CHARACTER_MAXIMUM_LENGTH, ')') 
                    END AS [Source_DATATYPE]
            ,       CASE isc.IS_NULLABLE
                        WHEN 'YES' THEN 'NULL'
                        WHEN 'NO' THEN 'NOT NULL'
                    END AS [Source_Nullable]
            ,       isc.COLUMN_DEFAULT AS [Source_Default]
            FROM    [$(SOURCE_DB)].INFORMATION_SCHEMA.COLUMNS isc  
            WHERE   isc.TABLE_NAME = @Source_Table AND isc.TABLE_SCHEMA = @Source_Schema
            ) a
            JOIN    (
                    SELECT  isc.TABLE_CATALOG       AS [Target_TABLE_CATALOG]
                    ,       isc.TABLE_SCHEMA        AS [Target_SCHEMA]  
                    ,       isc.TABLE_NAME          AS [Target_TABLE_NAME]  
                    ,       isc.ORDINAL_POSITION    AS [Target_ORDINAL_POSITION]  
                    ,       isc.COLUMN_NAME         AS [Target_COLUMN_NAME]  
                    ,       CASE
                                WHEN isc.DATA_TYPE IN ('decimal', 'money', 'numeric')
                                    THEN CONCAT(isc.DATA_TYPE, '(', isc.NUMERIC_PRECISION, ',', isc.NUMERIC_SCALE, ')')
                                WHEN isc.DATA_TYPE IN ('datetime','date','int')
                                    THEN isc.DATA_TYPE
                                WHEN isc.DATETIME_PRECISION IS NOT NULL
                                    THEN CONCAT(isc.DATA_TYPE, '(', isc.DATETIME_PRECISION, ')')
                                WHEN isc.DATA_TYPE = 'varchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 8000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                                    THEN 'varchar(MAX)'
                                WHEN isc.DATA_TYPE = 'nvarchar' AND (isc.CHARACTER_MAXIMUM_LENGTH = 4000 OR isc.CHARACTER_MAXIMUM_LENGTH = -1) 
                                    THEN 'nvarchar(MAX)'
                                ELSE
                                    CONCAT(isc.DATA_TYPE, '(', isc.CHARACTER_MAXIMUM_LENGTH, ')')
                            END AS [Target_DATATYPE]
                    ,       CASE isc.IS_NULLABLE
                                WHEN 'YES' THEN 'NULL'
                                WHEN 'NO' THEN 'NOT NULL'
                            END AS [Target_Nullable]
                    ,       isc.COLUMN_DEFAULT      AS [Target_Default]
                    FROM    [$(TARGET_DB)].INFORMATION_SCHEMA.COLUMNS isc
                    WHERE   isc.TABLE_NAME = @Target_Table AND isc.TABLE_SCHEMA = @Target_Schema 
                    ) b ON  a.Source_COLUMN_NAME = b.Target_COLUMN_NAME
    WHERE   a.Source_DATATYPE <> b.Target_DATATYPE OR
            a.Source_Nullable <> b.Target_Nullable OR
            a.Source_Default <> b.Target_Default;

使用 SQLCMD 实用程序,您也可以使用 -v 参数在命令行而不是脚本本身中传递所需的值。

【讨论】:

  • 嗨,丹。这个解决方案 SQL 注入安全吗?
  • 这是完美的。它只会由测试人员运行,不会保存在任何“代码”库中,但将被测试人员用作他们可以在他们工作的每个 BI 部件上运行的一组测试。因此,我怀疑是否有必要担心 SQL 注入的发生,因为一旦它运行,它就会运行。没有其他东西持有代码。
  • @ZoharPeled,不,这不是 SQL 注入安全的。但请注意,无论如何都可以从 SQLCMD 或 SSMS 运行任何临时查询,仅受用户权限的限制,因此注入不是问题。这假设没有从应用程序代码中为最终用户功能调用 SQLCMD,在这种情况下,SQL 注入是一个问题,不应使用此方法。
  • @ZoharPeled,你说得对。无需担心任何 SQL 注入,因为它仅由测试人员运行。
猜你喜欢
  • 2020-05-08
  • 1970-01-01
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多