有没有办法使 SQL 变量成为常量?

Posted

技术标签:

【中文标题】有没有办法使 SQL 变量成为常量?【英文标题】:Is there a way to make a TSQL variable constant? 【发布时间】:2010-09-06 19:12:13 【问题描述】:

有没有办法让 TSQL 变量成为常量?

【问题讨论】:

【参考方案1】:

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

这是一个例子:

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

SELECT dbo.fnConstant()

【讨论】:

WITH SCHEMABINDING 应该将其转换为“真实”常量(在 SQL 中将 UDF 视为确定性的要求)。 IE。它应该被缓存起来。不过,+1。 这个答案很好,只是好奇sqlserver中的表列可以引用一个函数作为默认值。我无法让它工作 @JonathanDickinson 明确地说,您的建议是在CREATE FUNCTION 语句中使用WITH SCHEMABINDING(而不是在可能调用该函数的存储过程中)——对吗? 是的,在函数中。 WITH SCHEMABINDING 允许 SQL 内联“内联表值函数” - 所以它需要采用这种形式:gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb。查询分析器不会在没有 SCHEMABINDING 或任何带有 BEGIN 的情况下内联任何内容。 使用非确定性 UDF 的含义:docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…【参考方案2】:

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

如SQL Server: Variables, Parameters or Literals? Or… Constants?中所述:

伪常数不是变量或参数。相反,它们只是具有一行的视图,并且有足够的列来支持您的常量。通过这些简单的规则,SQL 引擎完全忽略了视图的值,但仍然根据其值构建执行计划。执行计划甚至没有显示视图的连接!

像这样创建:

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)

【讨论】:

这是一个比公认答案更好的解决方案。最初我们走的是标量函数路线,它的性能很差。这个答案和上面的 Jared Ko 文章的链接要好得多。 但是,将 WITH SCHEMABINDING 添加到标量函数似乎可以显着提高其性能。 链接已失效。 @MatthieuCormier:我已经更新了链接,尽管 MSDN 似乎已经添加了从旧 URL 到新 URL 的重定向。【参考方案3】:

我缺少常量的解决方法是向优化器提供有关值的提示。

DECLARE @Constant INT = 123;

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

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

【讨论】:

它有帮助,但也违背了单一定义的目的。 我发现OPTION(RECOMPILE)OPTION(OPTIMIZE FOR ...) 的效果是一样的——然后你就不必指定两次了。见this question。这使得这个答案成为我解决这个问题的首选方法。【参考方案4】:

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

declare @MY_VALUE as int

【讨论】:

@VictorYarema 因为有时您只需要约定。因为有时你没有其他好的选择。现在,除此之外,SQLMenace 的答案看起来更好,我同意你的看法。即便如此,函数名称仍应遵循常量约定 IMO。它应该命名为FN_CONSTANT()。这样一来,它在做什么就很清楚了。 当您想要获得性能优势时,仅此一项是无济于事的。试试 Michal D. 和 John Nilsson 的答案来提升性能。【参考方案5】:

T-SQL 中没有对常量的内置支持。您可以使用 SQLMenace 的方法来模拟它(尽管您永远无法确定是否有人重写了该函数以返回其他内容……),或者可能编写一个包含常量的表,as suggested over here。也许编写一个触发器来回滚对ConstantValue 列的任何更改?

【讨论】:

【参考方案6】:

在使用 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

【讨论】:

这已经很老了,但作为参考,这是在我的服务器上执行时的结果:| 2760ms elapsed, using function | 2300ms elapsed, using local variable | 2286ms elapsed, using hard coded values | 在开发笔记本电脑上,具有两个没有模式绑定的附加功能。 5570 elapsed, using function | 406 elapsed, using local variable | 383 elapsed, using hard coded values | 3893 elapsed, using function without schemabinding 为了比较,一个简单的 select 语句花费了 4110 毫秒,其中 select 语句在 select top 1 @m = cv_val from code_values where cv_id = 'C101' 和相同的 ... 'C201' 之间交替,其中 code_values 是具有 250 个变量的字典表,都在 SQL-Server 2016 上跨度> 【参考方案7】:

如果您有兴趣为变量中的某个值获取最佳执行计划,您可以使用动态 sql 代码。它使变量保持不变。

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

【讨论】:

我就是这样做的,它极大地提升了涉及常量的查询的性能。【参考方案8】:

对于枚举或简单常量,具有单行的视图具有出色的性能和编译时检查/依赖跟踪(因为它是列名)

查看 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  )

【讨论】:

【参考方案9】:

好的,让我们看看

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

这意味着您永远不能在 SQL Server 中拥有常量

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

刚刚改变的值

【讨论】:

【参考方案10】:

由于没有内置对常量的支持,我的解决方案非常简单。

由于不支持:

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

我会把它转换成

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

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

【讨论】:

一个问题是它只能在本地使用【参考方案11】:

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

DECLARE @two INT
SET @two = 2

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

【讨论】:

试试 Michal D. 和 John Nilsson 的答案以提高性能。 根据定义,字面量是常量。 ascii/unicode(取决于编辑器)字符2 在“编译时”分配时被转换为二进制值。编码的实际值取决于分配给它的数据类型(int、char、...)。【参考方案12】:

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

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

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

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

例如:

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

【讨论】:

以上是关于有没有办法使 SQL 变量成为常量?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法隐式地使按钮成为多行?

执行包任务 - 有没有办法将Child Package参数绑定到常量?

有没有办法在 scanf 中使用变量/常量而不是数字来格式化 %s?

有没有办法自动使库成为静态或动态的?

有没有办法使 AM/PM 成为 owl-date-time 选择器的要求?

TensorFlow:有没有办法将冻结图转换为检查点模型?