为什么我不能像我想象的那样在T-SQL中使用变量?


18

请原谅,我是一名开发人员,已经转移到SQL领域。我以为我可以通过添加变量来改进一些SQL,但它的功能不像我预期的那样。有人可以告诉我为什么这行不通吗?我不想变通,我想知道为什么此方法不起作用的原因,就像我想像的那样,因为我确信这是有充分的理由的,但是目前还没有跳出来。

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

CREATE DATABASE @DatabaseName
GO

USE @DatabaseName
GO

您需要为此使用动态sql。mssqltips.com/sqlservertip/1160/...
斯泰恩Wynants

3
嘿,谢谢您的评论,但是根据我的问题,这不是我想要的答案。我想知道是否有人知道为什么我不能像我展示的那样做。
加雷斯

2
因为不可能将USE命令与参数一起使用。
TT。

2
除了已经给出的答案(但不足以单独回答)之外,变量的作用域范围为当前批次的最大值。(我不知道是否有可能比SQL Server更狭窄地限制变量的范围。)因此,一旦有了GO,先前声明的变量就会消失。您可能需要研究SQLCMD变量,该变量可能适用于您的方案,也可能不适用于您的方案。
CVn

1
我们倾向于将数据库名称和列名称视为字符串值,但是在SQL的上下文中,它们是标识符。您尝试的与期望x = 7相同;与'x'= 7相同;用其他语言。正如可以创建一种处理与x = 7相同的'x'= 7的计算机语言一样,可以创建一个将Create Table X与Create Table'X'相同的RDBMS。但这不是SQL。
user1008646

Answers:


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。它eval类似于在过程语言(如JavaScript和Python)中使用函数的SQL模拟。这是创建安全漏洞的快速方法。
jpmc26 2013年

1
@ jpmc26:不涉及动态SQL的更安全的方法是什么?
罗伯特·哈维

1
@RobertHarvey仅仅因为最好避免了,并不意味着总会有功能完全相同的替代方法。;)通常,部分答案是“使用完全不同的解决方案”。有时,这是最好的做法,但要经过大量的考虑,并确保您没有忽略任何替代方法,即使如此,它也应带有健康的注意事项。
jpmc26 2013年

2
@ jpmc26:OP的示例看起来像“代码优先” ORM可以做什么来在数据库中设置表。尽管动态SQL原则上是不安全的,但最终用户永远不会触摸该特定代码。
罗伯特·哈维

@RobertHarvey取决于您认为谁是“最终用户”。对于部署数据库的脚本,我认为开发人员和某些系统管理员可能是“最终用户”。在这种情况下,我仍然会设计系统拒绝不安全的输入,除非是出于其他原因,以避免发生意外。此外,至于“永不触摸”,OP正在触摸此代码,因此...
jpmc26,2013年

17

在SQL语句中使用变量的限制源于SQL的体系结构。

SQL语句的处理分为三个阶段:

  1. 准备-解析该语句并编译执行计划,指定访问哪些数据库对象,如何访问它们以及如何将它们关联。执行计划保存在计划缓存中
  2. 绑定-语句中的所有变量都将替换为实际值。
  3. 执行-使用绑定的值执行缓存的计划。

SQL Server对程序员隐藏了准备步骤,并且比Oracle和DB2等传统数据库执行起来要快得多。出于性能原因,SQL可能花费大量时间来确定最佳执行计划,但是仅在重新启动后第一次遇到该语句时才这样做。

因此,在静态SQL中,变量只能在不会使执行计划无效的地方使用,而不能用于表名,列名(包括WHERE条件下的列名)等。

对于无法解决这些限制的情况,存在动态SQL,并且程序员知道执行它会花费更长的时间。动态SQL可能容易受到恶意代码注入的影响,因此请当心!


7

如您所见,“为什么”问题需要不同的回答,包括历史依据和语言的基本假设,我不确定我是否真的可以做到这一点。

SQL MVP Erland Sommarskog撰写的这篇综合文章确实试图提供一些基本原理以及机制:

动态SQL的诅咒和祝福

缓存查询计划

您在SQL Server中运行的每个查询都需要一个查询计划。首次运行查询时,SQL Server会为其构建查询计划(或者按照术语进行),它将编译查询。SQL Server将计划保存在缓存中,并且下次您运行查询时,将重用该计划。

这(和安全性,请参见下文)可能是最大的原因。

SQL操作的前提是查询不是一次性操作,而是将反复使用它们。如果未在查询中实际指定表(或数据库!),则它无法生成和保存执行计划以备将来使用。

是的,不是我们运行的每个查询都会被重用,但这是SQL的默认操作前提,因此“ exceptions”是例外。

Erland列出了其他一些原因(请注意,他明确列出了使用存储过程的优点,但是其中许多也是参数化(非动态)查询的优点):

  • 权限系统:如果SQL引擎不知道您要操作的表(或数据库),则SQL引擎无法预测您是否有权运行查询。使用动态SQL的“权限链”是一个麻烦。
  • 减少网络流量:通过网络传递存储的proc的名称和一些参数值比长查询语句短。
  • 封装逻辑:您应该熟悉来自其他编程环境的封装逻辑的优点。
  • 跟踪使用的内容:如果需要更改列定义,如何找到所有调用它的代码?存在系统过程来查找SQL数据库中的依赖关系,但前提是代码在存储过程中。
  • 易于编写SQL代码:创建或修改存储过程时会进行语法检查,因此希望产生的错误更少。
  • 解决错误和问题:与不断变化的动态SQL相比,DBA可以更轻松地跟踪和衡量单个存储过程的性能。

再说一次,这些中的每一个都有一百个细微差别,我将不在这里介绍。


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
是的,谢谢,我知道,但是我想知道是否有人知道您为什么不能只使用变量?
gareth 16'Dec

T-SQL解析器将引发语法错误。它不是解析器识别的有效T-SQL。
Kin Shah

谢谢Kin,我相信一定有充分的理由。可能是因为数据库名称可以包含“ @”,以及其他一些更复杂的原因。
Gareth

1
是的,它们可以包含@,我认为这是主要原因。msdn.microsoft.com/en-us/library/ms175874.aspx
帕维尔Tajs

1
@gazeranco相信我,任何使用SQL Server的人无疑都希望更多的命令将接受变量代替常量标识符。
db2
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.