SQL 服务器将标识符传递给存储 procedures/dynamic SQL

SQL Server passing identifiers to stored procedures/dynamic SQL

背景:

SQL Server Management Studio 允许定义自己的查询快捷方式 (Tools > Options > Environment > Keyboard > Query Shortcuts):

图片来自:http://social.technet.microsoft.com/wiki/contents/articles/3178.how-to-create-query-shortcuts-in-sql-server-management-studio.aspx

my_schema.my_table
-- highlight it
-- press CTRL + 3 and you will get the number of rows in table

它工作正常,但它以基本形式连接查询(据我所知只在最后)。查询:

SELECT COUNT(*) FROM my_schema.my_table;

尝试 #1

现在我想写一些更具体的东西,例如 pass/concatenate table 以下查询的名称(这只是示例):

SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(...)

所以当我在查询快捷方式中写入时:

SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID('

我必须使用:

my_schema.my_table')
-- highlight it
-- press CTRL + 3

额外的')很丑很不方便

尝试 #2:

二试是用Dynamic-SQL:

EXEC dbo.sp_executesql
      N'SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(@obj_name)'
     ,N'@obj_name SYSNAME'
     ,

正在执行:

 my_table
 -- highligt it 
 -- and run 

LiveDemo

当 table 名称被引用 [my_table] 时也有效。只要对象在 dbo(默认)架构中。

问题是当 table 有模式时它不会工作:

EXEC dbo.sp_executesql
      N'SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(@obj_name)'
     ,N'@obj_name SYSNAME'
     ,

正在执行:

my_schema.my_table
[my_schema].[my_table]

LiveDemo2

Incorrect syntax near '.'.

我当然可以写:

EXEC dbo.sp_executesql
      N'SELECT * FROM sys.columns WHERE [object_id] = OBJECT_ID(@obj_name)'
     ,N'@obj_name SYSNAME'
     ,'

并将其命名为:

 [my_schema].[my_table]'

但是额外的'又丑又不方便

问题:

  1. 是否可以传递值,查询快捷方式window,在中间(位置甚至多个值)?

  2. 是否可以传递 do stored_procedure/dynamic-sql 限定标识符而不用 ', "?

备注:

编辑:

澄清在没有 '" 的情况下将标识符传递给 SP:

CREATE TABLE dbo.my_table(col INT);
GO
    
CREATE PROCEDURE dbo.my_proc
  @a SYSNAME
AS
SELECT *
FROM sys.columns
WHERE [object_id] = OBJECT_ID(@a)
GO

EXEC dbo.my_proc
   @a = my_table;

EXEC dbo.my_proc
   @a = dbo.my_table;
-- Incorrect syntax near '.'.

LiveDemo3

1。是否可以在中间传递值,查询快捷方式window?

据我所知,没有实现此目的的解决方法。

1-b。是否可以传递多个值?

可以使用分隔符对字符串值进行操作,然后在另一侧拆分该值。可悲的是,没有很多特殊字符可以完成这项工作,因为它们几乎都会引发语法错误。然而,'#' 可能是一个明智的选择,因为对于进入 tempDB 的 temp table,它已经是 SQL 的特殊字符。只需检查您是否还没有使用它的标识符,因为 SQL 允许它(强硬,它被禁止作为第一个字符)。

这是一个例子:
创建一个存储过程以将参数接收到一个字符串中并将字符串拆分为每个参数。

    CREATE PROCEDURE sp_PassingMultipleStringValues 
        @Param1 NVARCHAR(MAX)
    AS
    
    --Here I'm using a XML split, but feel free to use any string split function you already have.
    DECLARE @xml        AS XML,
            @separator  AS VARCHAR(1)
    
    SELECT  @separator ='#',
            @xml = CAST('<X>'+ (REPLACE(@Param1,@separator ,'</X><X>') +'</X>') AS XML)
    
    SELECT N.value('.', 'VARCHAR(200)') AS value 
    FROM @xml.nodes('X') as T(N)
    --Do whatever is needed with them

然后如图所示配置您的快捷方式。 (注意最后的space)

结果:

2。是否可以传递给 stored_procedure/dynamic-sql 限定标识符而不用 ', "?

您是否有多个具有相同标识符的模式?
因为如果不是,那么使用 sys.schemas 而不是传递它在另一端检索它怎么样?
与其在末尾输入不方便的字符,不如输入更少的内容。

使用检索到的架构,您可以根据需要执行动态 SQL。

    SELECT @Param1 = REPLACE(REPLACE(@Param1, '[', ''), ']', '')
    
    SELECT TOP 1 @Param1 = [Schema].name + '.' + @Param1
    FROM     sys.objects AS obj
    JOIN     sys.schemas AS [Schema] ON obj.schema_id = [Schema].schema_id
    WHERE    obj.name = @Param1
    
    SELECT * 
    FROM sys.columns 
    WHERE [object_id] = OBJECT_ID(@Param1)

    DECLARE @Query NVARCHAR(MAX) = 'SELECT TOP 1 * FROM ' + @Param1
    EXEC sp_sqlexec @Query

如果您确实想处理两个具有相同标识符的不同模式,那么使用答案 1-b 中解释的方法将模式和标识符作为两个参数传递仍然可行。

一个例子中的一切

因为这里我们要传递多个标识符并指定它们的架构,所以需要两个分隔符。

CREATE PROCEDURE sp_MultiArgsWithSchema
    @Param1 NVARCHAR(MAX)
AS

SELECT @Param1 = REPLACE(REPLACE(@Param1, '[', ''), ']', '')

--Here I'm using a XML split, but feel free to use any string split function you already have.
DECLARE @xml            AS XML,
        @ArgSeparator   AS VARCHAR(2),
        @SchemaSeparor  AS VARCHAR(1)

SELECT  @ArgSeparator = '##',
        @SchemaSeparor = '#',
        @xml = CAST('<X>'+ (REPLACE(@Param1,@ArgSeparator, '</X><X>') +'</X>') AS XML)

IF OBJECT_ID('tempdb..#QualifiedIdentifiers') IS NOT NULL 
    DROP TABLE #QualifiedIdentifiers;

--While splitting, we are putting back the dot instead of '#' between schema and name of object
SELECT QualifiedIdentifier = REPLACE(N.value('.', 'VARCHAR(200)'), @SchemaSeparor, '.') 
INTO #QualifiedIdentifiers
FROM @xml.nodes('X') as T(N)

SELECT * FROM #QualifiedIdentifiers

--From here, use what is inside #QualifiedIdentifiers and Dynamic SQL if need to achieve what is needed
DECLARE @QualifiedIdentifier    NVARCHAR(500)
WHILE EXISTS(SELECT TOP 1 1 FROM #QualifiedIdentifiers)
BEGIN
    SELECT TOP 1 @QualifiedIdentifier = QualifiedIdentifier
    FROM #QualifiedIdentifiers

    SELECT * 
    FROM sys.columns 
    WHERE [object_id] = OBJECT_ID(@QualifiedIdentifier)
 
    DELETE TOP (1) 
    FROM #QualifiedIdentifiers
    WHERE QualifiedIdentifier = @QualifiedIdentifier
END

用法(请注意,指定架构不是强制性的):

所以,由于必须将拆分字符加倍不方便,所以如果模式可以像上面说的那样被猜到就更好了。

这是一个通过多部分标识符而不用引号引起来的远景。

解法:

  1. 查询快捷方式将在数据库中创建一个具有特定名称的同义词和一个DDLTrigger来拦截这个特定的同义词创建。

    在查询快捷方式中设置以下快捷方式。 (确保包括最后一个 space)

    DECLARE @CreateTriggerSQL NVARCHAR(MAX) = 'CREATE TRIGGER DDLTrigger_QueryShortcutX ON DATABASE FOR CREATE_SYNONYM AS BEGIN DECLARE @EventData XML = EVENTDATA(), @SynonymName NVARCHAR(255), @DbName NVARCHAR(255), @SchemaName NVARCHAR(255), @ObjectName NVARCHAR(255), @Alias NVARCHAR(255) SELECT @SynonymName = @EventData.value(''(/EVENT_INSTANCE/ObjectName)[1]'', ''NVARCHAR(255)'') IF(@SynonymName = ''QueryShortcutX'') BEGIN DROP SYNONYM QueryShortcutX DROP TRIGGER DDLTrigger_QueryShortcutX ON DATABASE SELECT @DbName = @EventData.value(''(/EVENT_INSTANCE/DatabaseName)[1]'', ''NVARCHAR(255)''), @SchemaName = @EventData.value(''(/EVENT_INSTANCE/TargetSchemaName)[1]'', ''NVARCHAR(255)''), @ObjectName = @EventData.value(''(/EVENT_INSTANCE/TargetObjectName)[1]'', ''NVARCHAR(255)''), @Alias = (CASE WHEN LEN(@SchemaName) > 0 THEN @SchemaName + ''.'' ELSE '''' END) + @ObjectName /*EXEC yourStoredProcHere @Param = @Alias*/ SELECT DbName = @DbName, SchemaName = @SchemaName, ObjectName = @ObjectName, Alias = @Alias, ObjectId = OBJECT_ID(@Alias) END END' EXEC sp_executeSQL @CreateTriggerSQL CREATE SYNONYM QueryShortcutX FOR 
    

    正如@Vladimir 所建议的,这里我们使用 "sp_executesql" 来同时创建触发器和同义词。

    这是没有内联的触发器代码。

    CREATE TRIGGER DDLTrigger_QueryShortcutX ON DATABASE FOR CREATE_SYNONYM
    AS
    BEGIN
        DECLARE @EventData      XML = EVENTDATA(),
                @SynonymName    NVARCHAR(255),
                @DbName         NVARCHAR(255),
                @SchemaName     NVARCHAR(255),
                @ObjectName     NVARCHAR(255),
                @Alias          NVARCHAR(255)
    
        SELECT  @SynonymName    = @EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)') 
    
        --Safety in case someone else really create a synonym meanwhile. 
        IF(@SynonymName = 'QueryShortcutX')
        BEGIN
    
            --2. Clean up what we created
            DROP SYNONYM QueryShortcutX
            DROP TRIGGER DDLTrigger_QueryShortcutX ON DATABASE
    
            --3. Parsing identifier code here
            SELECT  @DbName         = @EventData.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(255)'), 
                    @SchemaName     = @EventData.value('(/EVENT_INSTANCE/TargetSchemaName)[1]', 'NVARCHAR(255)'), 
                    @ObjectName     = @EventData.value('(/EVENT_INSTANCE/TargetObjectName)[1]', 'NVARCHAR(255)'),
                    @Alias          = (CASE WHEN LEN(@SchemaName) > 0 THEN @SchemaName + '.' ELSE '' END) + @ObjectName
    
            --4. Here, write any print/select statement you want. 
            --For maintenance, it would be easier to just call a stored procedure from here with parameter and put the desired print/select there. 
            --Thus avoiding to redo inlining the whole trigger each time.
            --EXEC yourStoredProcHere @Param = @Alias
            SELECT  DbName      = @DbName,
                    SchemaName  = @SchemaName,
                    ObjectName  = @ObjectName,
                    Alias       = @Alias,
                    ObjectId    = OBJECT_ID(@Alias)
    
        END
    END
    

    这里是没有内联的快捷方式代码。

        DECLARE @CreateTriggerSQL NVARCHAR(MAX) = 'Trigger creation code here...'
        IF EXISTS(SELECT TOP 1 1 FROM sys.triggers WHERE name = 'DDLTrigger_QueryShortcutX') 
        BEGIN 
            DROP TRIGGER DDLTrigger_QueryShortcutX ON DATABASE 
        END 
    
        EXEC sp_executeSQL @CreateTriggerSQL 
    
        IF EXISTS(SELECT TOP 1 1 FROM sys.synonyms WHERE name = 'QueryShortcutX') 
        BEGIN 
            DROP SYNONYM QueryShortcutX 
        END 
        CREATE SYNONYM QueryShortcutX FOR 
    
  2. 触发器删除自身和同义词以避免模式污染。

  3. 触发器解析信息以检索标识符。
  4. 根据您的需要使用标识符。 (如果需要,使用动态 SQL)

每个测试项目的结果

1.RealColumnName    
2.WhatEverText
3.dbo.tests
4.[No selection]
5.dbo.tests.very.much

 DbName SchemaName      ObjectName      Alias           ObjectId
1.TEST                  RealColumnName  RealColumnName  NULL --FN OBJECT_ID doesn't return value with only column name
2.TEST                  WhatEverText    WhatEverText    NULL
3.TEST  dbo             tests           dbo.tests       245575913
4.Incorrect syntax near 'FOR'.
5.TEST  very            much            very.much       NULL

我所做的解析不能正确处理超过两个多部分的标识符。如果你想改进它。以下 XML 显示要使用的标签。

<TargetServerName>dbo</TargetServerName>
<TargetDatabaseName>tests</TargetDatabaseName>
<TargetSchemaName>very</TargetSchemaName>
<TargetObjectName>much</TargetObjectName>

注:

  • 如果您愿意,可以让触发器永久保留在数据库中。
  • 另外,如果你想传递多个标识符,像我在其他答案中所做的那样的字符串解析在这里仍然是可能的。
  • 要使用此解决方案,用户必须拥有 "create synonym permission" 并且拥有架构或拥有 "ALTER SCHEMA permission".