有没有办法使TSQL变量恒定?


Answers:


60

否,但是您可以在其中创建一个函数并对其进行硬编码并使用它。

这是一个例子:

CREATE FUNCTION fnConstant()
RETURNS INT
AS
BEGIN
    RETURN 2
END
GO

SELECT dbo.fnConstant()

13
WITH SCHEMABINDING 应该将其转换为“实际”常量(要求UDF在SQL中被视为确定性的)。即它应该登陆被缓存。不过,+ 1。
乔纳森·迪金森

这个答案很好,只是好奇sqlserver中的表列可以引用一个函数作为默认值。我无法
使它

1
@JonathanDickinson要清楚,您的建议是WITH SCHEMABINDINGCREATE FUNCTION语句中使用(而不是在可能正在调用该函数的存储过程中使用)–是吗?
整体开发人员

1
是的,在功能上。WITH SCHEMABINDING允许SQL内联“内联表值函数”-因此它必须采用以下形式:gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb。没有SCHEMABINDING或带有BEGIN的任何内容,查询分析器都不会内联任何内容。
乔纳森·迪金森


28

Jared Ko提供的一种解决方案是使用伪常量

SQL Server中所述:变量,参数还是文字?还是……常数?

伪常量不是变量或参数。取而代之的是,它们只是视图,只有一行,并且有足够的列来支持您的常量。使用这些简单的规则,SQL Engine会完全忽略视图的值,但仍会基于其值构建执行计划。执行计划甚至没有显示对视图的连接!

像这样创建:

CREATE SCHEMA ShipMethod
GO
-- Each view can only have one row.
-- Create one column for each desired constant.
-- Each column is restricted to a single value.
CREATE VIEW ShipMethod.ShipMethodID AS
SELECT CAST(1 AS INT) AS [XRQ - TRUCK GROUND]
      ,CAST(2 AS INT) AS [ZY - EXPRESS]
      ,CAST(3 AS INT) AS [OVERSEAS - DELUXE]
      ,CAST(4 AS INT) AS [OVERNIGHT J-FAST]
      ,CAST(5 AS INT) AS [CARGO TRANSPORT 5]

然后像这样使用:

SELECT h.*
FROM Sales.SalesOrderHeader h
JOIN ShipMethod.ShipMethodID const
    ON h.ShipMethodID = const.[OVERNIGHT J-FAST]

或像这样:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)

1
这是比接受的答案更好的解决方案。最初,我们沿标量函数路线走了,它的性能糟透了。这个答案以及上面的Jared Ko文章的链接要好得多。
戴维·科斯特

但是,将WITH SCHEMABINDING添加到标量函数似乎可以显着改善其性能。
大卫·科斯特

现在链接已死。
Matthieu Cormier,

1
@MatthieuCormier:我已经更新了链接,尽管MSDN似乎仍然增加了从旧URL到新URL的重定向。
伊万里

23

对于丢失的常量,我的解决方法是为优化器提供有关该值的提示。

DECLARE @Constant INT = 123;

SELECT * 
FROM [some_relation] 
WHERE [some_attribute] = @Constant
OPTION( OPTIMIZE FOR (@Constant = 123))

这告诉查询编译器在创建执行计划时将变量视为常量。缺点是您必须两次定义该值。


3
它有帮助,但也违反了单一定义的目的。
MikeJRamsey56 '18

9

不,但是应该使用良好的旧命名约定。

declare @MY_VALUE as int

@VictorYarema,因为有时您只需要约定。而且因为有时候您别无选择。现在,除此之外,SQLMenace的答案看起来更好,我会同意你的看法。即使这样,函数名称也应遵循常量的约定IMO。它应命名为FN_CONSTANT()。这样,很清楚它在做什么。
tfrascaroli

当您想要性能方面的好处时,仅此一点是无济于事的。也可以尝试Michal D.和John Nilsson的答案来提高性能。
WonderWorker

8

T-SQL中没有对常量的内置支持。您可以使用SQLMenace的方法对其进行仿真(尽管您无法确定是否有人重写了该函数以返回其他内容……),或者可能编写了包含常量的表,如此处所建议。也许编写一个触发器以回滚对ConstantValue列的任何更改?


7

在使用SQL函数之前,请运行以下脚本以查看性能差异:

IF OBJECT_ID('fnFalse') IS NOT NULL
DROP FUNCTION fnFalse
GO

IF OBJECT_ID('fnTrue') IS NOT NULL
DROP FUNCTION fnTrue
GO

CREATE FUNCTION fnTrue() RETURNS INT WITH SCHEMABINDING
AS
BEGIN
RETURN 1
END
GO

CREATE FUNCTION fnFalse() RETURNS INT WITH SCHEMABINDING
AS
BEGIN
RETURN ~ dbo.fnTrue()
END
GO

DECLARE @TimeStart DATETIME = GETDATE()
DECLARE @Count INT = 100000
WHILE @Count > 0 BEGIN
SET @Count -= 1

DECLARE @Value BIT
SELECT @Value = dbo.fnTrue()
IF @Value = 1
    SELECT @Value = dbo.fnFalse()
END
DECLARE @TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, @TimeStart, @TimeEnd) AS VARCHAR) + ' elapsed, using function'
GO

DECLARE @TimeStart DATETIME = GETDATE()
DECLARE @Count INT = 100000
DECLARE @FALSE AS BIT = 0
DECLARE @TRUE AS BIT = ~ @FALSE

WHILE @Count > 0 BEGIN
SET @Count -= 1

DECLARE @Value BIT
SELECT @Value = @TRUE
IF @Value = 1
    SELECT @Value = @FALSE
END
DECLARE @TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, @TimeStart, @TimeEnd) AS VARCHAR) + ' elapsed, using local variable'
GO

DECLARE @TimeStart DATETIME = GETDATE()
DECLARE @Count INT = 100000

WHILE @Count > 0 BEGIN
SET @Count -= 1

DECLARE @Value BIT
SELECT @Value = 1
IF @Value = 1
    SELECT @Value = 0
END
DECLARE @TimeEnd DATETIME = GETDATE()
PRINT CAST(DATEDIFF(ms, @TimeStart, @TimeEnd) AS VARCHAR) + ' elapsed, using hard coded values'
GO

4
这已经很老了,但是作为参考,这是在我的服务器上执行时的结果:2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
2015年

2
在开发型笔记本电脑上,具有两个没有模式绑定的附加功能。5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
猴子屋

为了进行比较,一个简单的select语句花费了4110ms的时间,其中select语句之间交替出现,select top 1 @m = cv_val from code_values where cv_id = 'C101' 并且相同... 'C201' ,其中code_values是具有250个var的字典表,所有这些都在SQL-Server 2016上
Monkeyhouse

6

如果您有兴趣获取变量值的最佳执行计划,则可以使用动态sql代码。它使变量恒定。

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)

1
这就是我的方法,它极大地提高了涉及常量的查询的性能。
WonderWorker

5

对于枚举或简单常量,只有一行的视图具有出色的性能,并且可以进行编译时检查/依赖项跟踪(导致其为列名)

请参阅Jared Ko的博客文章https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

创建视图

 CREATE VIEW ShipMethods AS
 SELECT CAST(1 AS INT) AS [XRQ - TRUCK GROUND]
   ,CAST(2 AS INT) AS [ZY - EXPRESS]
   ,CAST(3 AS INT) AS [OVERSEAS - DELUXE]
  , CAST(4 AS INT) AS [OVERNIGHT J-FAST]
   ,CAST(5 AS INT) AS [CARGO TRANSPORT 5]

使用视图

SELECT h.*
FROM Sales.SalesOrderHeader 
WHERE ShipMethodID = ( select [OVERNIGHT J-FAST] from ShipMethods  )

3

好吧,让我们看看

常量是不变的值,在编译时是已知的,并且在程序生命周期内不会更改

这意味着您永远无法在SQL Server中拥有常量

declare @myvalue as int
set @myvalue = 5
set @myvalue = 10--oops we just changed it

价值刚刚改变


1

由于没有建立对常量的支持,因此我的解决方案非常简单。

由于不支持此操作:

Declare Constant @supplement int = 240
SELECT price + @supplement
FROM   what_does_it_cost

我只是将其转换为

SELECT price + 240/*CONSTANT:supplement*/
FROM   what_does_it_cost

显然,这依赖于整个事物(没有尾随空格和注释的值)是唯一的。通过全局搜索和替换可以进行更改。


一个问题是它仅在本地可用
Bernardo Dal Corno

0

数据库文献中没有“创建常量”之类的东西。常量按原样存在,通常称为值。可以声明一个变量并为其分配一个值(常量)。从学校的角度来看:

DECLARE @two INT
SET @two = 2

这里@two是变量,2是值/常数。


也可以尝试Michal D.和John Nilsson的答案来提高性能。
WonderWorker

根据定义,文字是恒定的。2在“编译时”分配时,ascii / unicode(取决于编辑器)字符将转换为二进制值。编码后的实际值取决于为其分配的数据类型(int,char,...)。
samis

-1

最好的答案是根据要求从SQLMenace获得,如果要创建一个临时常量以在脚本内使用,即跨多个GO语句/批处理使用。

只需在tempdb中创建该过程,就不会对目标数据库产生影响。

一个实际的例子是一个数据库创建脚本,该脚本在包含逻辑模式版本的脚本末尾写入一个控制值。文件顶部是带有更改历史记录的注释等。但是实际上,大多数开发人员会忘记向下滚动并更新文件底部的架构版本。

使用以上代码,可以在数据库脚本(从SSMS的生成脚本功能复制)创建数据库但在最后使用数据库之前在顶部定义一个可见的架构版本常量。这是开发人员面对更改历史记录和其他注释时的正确做法,因此他们很可能会对其进行更新。

例如:

use tempdb
go
create function dbo.MySchemaVersion()
returns int
as
begin
    return 123
end
go

use master
go

-- Big long database create script with multiple batches...
print 'Creating database schema version ' + CAST(tempdb.dbo.MySchemaVersion() as NVARCHAR) + '...'
go
-- ...
go
-- ...
go
use MyDatabase
go

-- Update schema version with constant at end (not normally possible as GO puts
-- local @variables out of scope)
insert MyConfigTable values ('SchemaVersion', tempdb.dbo.MySchemaVersion())
go

-- Clean-up
use tempdb
drop function MySchemaVersion
go
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.