Почему я не могу использовать переменные в T-SQL, как я себе представляю?


18

Простите, я разработчик, который перешел в мир SQL. Я думал, что смогу улучшить SQL, добавив переменные, но он не работал так, как я ожидал. Может кто-нибудь сказать мне, почему это не работает? Я не хочу работать вокруг, я хочу знать причины, почему это не работает, как я полагаю, что должно, так как я уверен, что есть веская причина, но в настоящее время это не бросается в глаза мне.

DECLARE @DatabaseName varchar(150)
SET @DatabaseName = 'MyAmazingDatabaseName'

CREATE DATABASE @DatabaseName
GO

USE @DatabaseName
GO

Вам нужно использовать динамический SQL для этого. mssqltips.com/sqlservertip/1160/…
Стейн Винанц

3
Привет, спасибо за комментарии, но на мой вопрос, это не тот ответ, который я ищу. Я хочу знать, знает ли кто-нибудь, почему я не могу сделать это, как я показал.
Гар

2
Потому что невозможно использовать USEкоманду с параметром.
ТТ.

2
В дополнение к уже полученным ответам, но не достаточным для ответа, переменные ограничиваются максимальным значением текущей партии. (Я не знаю, можно ли ограничить область действия переменных более узко, чем в SQL Server.) Таким образом, в тот момент, когда у вас есть GO, ранее объявленная переменная исчезает. Возможно, вы захотите изучить переменные SQLCMD, которые могут или не могут быть применимы к вашему сценарию.
CVN

1
Мы склонны считать имена баз данных и имена столбцов строковыми значениями, но в контексте SQL они являются идентификаторами. То, что вы пытаетесь сделать, будет таким же, как ожидалось x = 7; быть таким же, как «х» = 7; на каком-то другом языке. Подобно тому, как может быть создан компьютерный язык, который работает с 'x' = 7 так же, как x = 7, может быть создана СУБД, которая обрабатывает Create Table X так же, как Create Table 'X'. Но это не будет SQL.
user1008646

Ответы:


20

На онлайн-странице Книги для переменных

Переменные можно использовать только в выражениях, а не вместо имен объектов или ключевых слов. Для построения динамических операторов SQL используйте EXECUTE.

Это будет работать так, как вы ожидаете, если, например, вы используете свою переменную в предложении where. Что касается того, почему, я думаю, что это как-то связано с синтаксическим анализатором, который не может оценить переменную и, таким образом, проверить существование. При выполнении запрос сначала анализируется на предмет синтаксиса и объектов, а затем, если анализ выполняется успешно, запрос выполняется, и в этот момент будет установлена ​​переменная.

DECLARE @name varchar(20);
SET @name = 'test';

CREATE TABLE [#tmp]([val] varchar(10));

insert into #tmp
values('test')

SELECT *
FROM [#tmp]
WHERE [val] = @name;

3
Обратите внимание, что по возможности следует избегать динамического SQL. Это аналог SQL для использования evalфункций в процедурных языках, таких как JavaScript и Python. Это быстрый способ создать дыры в безопасности.
jpmc26

1
@ jpmc26: Каков более безопасный способ сделать это, не используя динамический SQL?
Роберт Харви

1
@RobertHarvey То, что этого лучше избегать, не означает, что всегда есть альтернатива с точно такой же функциональностью. ;) Часто часть ответа такова: «Используйте совершенно другое решение проблемы». Иногда это лучше всего делать, но не без обдуманного и тщательного рассмотрения альтернатив, и даже тогда это должно сопровождаться здоровой дозой осторожности.
jpmc26

2
@ jpmc26: пример OP выглядит как то, что ORM «Code-First» может сделать для настройки таблиц в базе данных. Хотя динамический SQL в принципе небезопасен, конечный пользователь никогда не будет касаться этого конкретного кода.
Роберт Харви

@RobertHarvey Зависит от того, кого вы считаете «конечным пользователем». Для сценария, который развертывает БД, я бы назвал «конечного пользователя» разработчика и, возможно, некоторых системных администраторов. Я бы по-прежнему планировал использовать систему, чтобы в этом случае отказываться от небезопасных входных данных, хотя бы по любой другой причине, кроме как избежать несчастных случаев. Кроме того, что касается «никогда не трогай», ОП касается этого кода, так что ...
jpmc26

17

Ограничения на использование переменных в операторах SQL вытекают из архитектуры SQL.

Существует три этапа обработки оператора SQL:

  1. Подготовка - оператор анализируется и составляется план выполнения , определяющий, к каким объектам базы данных осуществляется доступ, как к ним осуществляется доступ и как они связаны. План выполнения сохраняется в кэше планов .
  2. Связывание - любые переменные в выражении заменяются фактическими значениями.
  3. Выполнение - кэшированный план выполняется со связанными значениями.

SQL-сервер скрывает этап подготовки от программиста и выполняет его намного быстрее, чем более традиционные базы данных, такие как Oracle и DB2. По причинам производительности SQL тратит потенциально много времени на определение оптимального плана выполнения, но делает это только при первом обращении к оператору после перезапуска.

Таким образом, в статическом SQL переменные могут использоваться только в тех местах, где они не приведут к аннулированию плана выполнения, но не для имен таблиц, имен столбцов (включая имена столбцов в условиях WHERE) и т. Д.

Динамический SQL существует для случаев, когда нельзя обойти ограничения, и программист знает, что выполнение займет немного больше времени. Динамический SQL может быть уязвим для внедрения вредоносного кода, поэтому будьте осторожны!


7

Как вы можете видеть, вопрос «почему» требует другого типа ответа, включая историческое обоснование и лежащие в основе предположения о языке, я не уверен, что смогу действительно оправдать это.

Эта всеобъемлющая статья SQL MVP Erland Sommarskog действительно пытается дать некоторое объяснение, наряду с механикой:

Проклятие и благословения динамического SQL :

Кэширование планов запросов

Каждый запрос, который вы запускаете в SQL Server, требует плана запроса. Когда вы запускаете запрос в первый раз, SQL Server создает для него план запроса - или, как говорит терминология, - он компилирует запрос. SQL Server сохраняет план в кеше, и в следующий раз, когда вы выполняете запрос, план используется повторно.

Это (и безопасность, см. Ниже), вероятно, самая большая причина.

SQL работает при условии, что запросы - это не разовые операции, а то, что они будут использоваться снова и снова. Если таблица (или база данных!) Фактически не указана в запросе, она не может сгенерировать и сохранить план выполнения для будущего использования.

Да, не каждый запрос, который мы выполняем, будет использоваться повторно, но это рабочая предпосылка SQL по умолчанию , поэтому «исключения» должны быть исключительными.

Несколько других причин перечисляет Эрланд (обратите внимание, что он явно перечисляет преимущества использования хранимых процедур , но многие из них также являются преимуществами параметризованных (нединамических) запросов):

  • Система разрешений : механизм SQL не может предсказать, есть ли у вас права на выполнение запроса, если он не знает таблицу (или базу данных), с которой вы будете работать. «Цепочки разрешений», использующие динамический SQL, являются проблемой в заднице.
  • Сокращение сетевого трафика . Передача имени хранимого процесса и нескольких значений параметров по сети короче, чем длинная инструкция запроса.
  • Инкапсуляция логики : вы должны быть знакомы с преимуществами инкапсуляции логики из других сред программирования.
  • Отслеживание того, что используется : если мне нужно изменить определение столбца, как я могу найти весь код, который его вызывает? Системные процедуры существуют для поиска зависимостей в базе данных SQL, но только если код находится в хранимых процедурах.
  • Простота написания кода SQL : проверка синтаксиса происходит при создании или изменении хранимой процедуры, поэтому, надеюсь, будет меньше ошибок.
  • Устранение ошибок и проблем : администратор базы данных может отслеживать и измерять производительность отдельных хранимых процедур гораздо проще, чем постоянно меняющийся динамический SQL.

Опять же, у каждого из них есть сто нюансов, в которые я не буду вдаваться.


2

Вам нужно использовать динамический SQL

DECLARE @DatabaseName varchar(150) = 'dbamaint'
declare @sqltext nvarchar(max) = N''

set @sqltext = N'CREATE DATABASE '+quotename(@DatabaseName)+ ';'

print @sqltext 

-- once you are happy .. uncomment below
--exec sp_executesql @sqltext
set @sqltext = ''
set @sqltext = N'use '+quotename(@DatabaseName)+ ';'
print @sqltext 
-- once you are happy .. uncomment below
--exec sp_executesql @sqltext

ниже вывод команды print .. как только вы раскомментируете exec sp_executesql @sqltextоператоры будут фактически выполнены ...

CREATE DATABASE [dbamaint];
use [dbamaint];

1
Да, спасибо, я знаю это, но я хочу знать, знает ли кто-нибудь, почему вы не можете просто использовать переменную?
Гар

Синтаксический анализатор T-SQL будет выдавать синтаксические ошибки. Это не правильный T-SQL, который распознает синтаксический анализатор.
Кин Шах

Спасибо Кин, я уверен, что для этого должны быть веские причины. Возможно, потому что имена баз данных могут содержать '@' и, возможно, некоторые другие более сложные причины.
Гар

1
Да, они могут содержать @, и я думаю, что это главная причина. msdn.microsoft.com/en-us/library/ms175874.aspx
Павел Тайс

1
@ gazeranco Поверьте мне, любой, кто работает с SQL Server, несомненно, желает, чтобы больше команд принимали переменные вместо постоянных идентификаторов.
db2
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.