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".