SQL Server передает идентификаторы хранимым процедурам/динамическому 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 для следующего запроса (это просто пример):

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

Работает также при цитировании имени таблицы [my_table]. Пока объект находится в dbo (по умолчанию) схеме.

Проблема в том, что когда таблица имеет схему, она не будет работать:

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

Неправильный синтаксис около '.'.

Конечно, я мог бы написать:

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

и назовите его как:

 [my_schema].[my_table]'

Но дополнительный ' также является уродливым и неудобным.

Вопросы:

  • Можно ли передать значение, чтобы запросить окно ярлыков, посередине (позиционное или даже более одного значения)?

  • Можно ли передать do stored_procedure/dynamic-sql квалифицированный идентификатор, не разбивая его с помощью ', "?

Примечание:

  • Я не ищу плагины для SSMS
  • Я не хочу обертывать object_name как "my_schema.my_table"
  • Я знаю, что есть sp_helptext (это просто пример, я ищу метод)
  • Первый вопрос - это конкретный инструмент (я его знаю), но второй - SQL Server.

EDIT:

Чтобы прояснить передаваемый идентификатор 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

1. Можно ли передать значение, чтобы запросить окно ярлыков, посередине?

Насколько мне известно, для этого не существует обходного пути.

1-б. Возможно ли передать более одного значения?

Это можно сделать для строковых значений, используя символ разделителя, а затем разделив значение на другой стороне. К сожалению, для выполнения этой работы не так уж много специального персонажа, потому что они почти все поднимают синтаксическую ошибку. Однако '#' может быть разумным выбором, поскольку он уже является специальным символом для SQL для таблицы temp, идущей в tempDB. Просто проверьте, нет ли у вас идентификатора, который использует его, потому что он разрешен SQL (жесткий, он запрещен как первый char).

Вот пример:
Создайте хранимую процедуру для приема аргументов в одну строку и разделите строку на каждый аргумент.

    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

Затем настройте свой ярлык, как показано на этом изображении. (Запишите пробел в конце) введите описание изображения здесь

Результат:
введите описание изображения здесь

2. Можно ли перейти к идентификатору с сохраненным_процессом/динамическим 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

Использование (обратите внимание, что указание схемы не является обязательным):
введите описание изображения здесь

Итак, поскольку неудобно дублировать символ расщепления, было бы лучше, если бы схема могла быть угадана, как указано выше.

Ответ 2

Вот длинный снимок для передачи многочастного идентификатора без его кавычек.

Решение:

  • Ярлыки запросов собираются создать синоним в базе данных с определенным именем и DDLTrigger, чтобы перехватить это конкретное создание синонима,

    Создайте следующий ярлык в Query ShortCuts. (Убедитесь, что вы включили последнее пространство)

    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 
    

    Как предложил В.Владимир, здесь мы используем "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 
    
  • Триггер сам по себе и синоним, чтобы избежать загрязнения схемы.

  • Триггер проанализирует информацию для получения идентификатора.
  • Используйте идентификатор для ваших нужд. (при необходимости используйте динамический 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>

Примечание:

  • Если вы предпочитаете, вы можете позволить триггеру оставаться навсегда в базе данных.
  • Кроме того, если вы хотите передать несколько идентификаторов, синтаксический анализ, как и в моем другом ответе, по-прежнему возможен.
  • Чтобы использовать это решение, пользователь должен будет "создать разрешение для синонимов" и либо иметь схему, либо иметь разрешение "ALTER SCHEMA".