Есть ли способ сделать переменную TSQL постоянной?


Ответы:


60

Нет, но вы можете создать функцию, жестко закодировать ее и использовать.

Вот пример:

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

SELECT dbo.fnConstant()

13
WITH SCHEMABINDING должен превратить это в «реальную» константу (требование, чтобы UDF рассматривалась как детерминированная в SQL). Т.е. он должен попасть в кеш. Тем не менее, +1.
Джонатан Дикинсон

этот ответ хорош, просто любопытно, могут ли столбцы таблицы в sqlserver ссылаться на функцию как на значение по умолчанию. я не мог заставить это работать
Ab Bennett

1
@JonathanDickinson Для ясности, ваше предложение - использовать WITH SCHEMABINDINGв CREATE FUNCTIONоператоре (в отличие от хранимой процедуры, которая может вызывать функцию) - это правильно?
Holistic Developer

1
Да, в функции. WITH SCHEMABINDING позволяет SQL встраивать «встроенные функции с табличным значением», поэтому он также должен быть в такой форме: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . Анализатор запросов не будет ничего встраивать без SCHEMABINDING или чего-либо с BEGIN.
Джонатан Дикинсон

Последствия использования недетерминированных UDF: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
Ochoto,

28

Одним из решений, предложенных Джаредом Ко, является использование псевдоконстант .

Как объяснено в 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
Это НАМНОГО лучшее решение, чем принятый ответ. Сначала мы пошли по пути скалярных функций, и у них ужасная производительность. Намного лучше этот ответ и ссылка выше на статью Джареда Ко.
Дэвид Костер

Однако добавление WITH SCHEMABINDING к скалярной функции, по-видимому, значительно улучшает ее производительность.
Дэвид Костер

Ссылка сейчас мертва.
Matthieu Cormier

1
@MatthieuCormier: Я обновил ссылку, хотя кажется, что MSDN все равно добавила перенаправление со старого URL-адреса на новый.
Илмари

23

Мой обходной путь к отсутствию констант - дать подсказку о значении оптимизатору.

DECLARE @Constant INT = 123;

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

Это указывает компилятору запроса обрабатывать переменную, как если бы она была константой при создании плана выполнения. Обратной стороной является то, что вам нужно определить значение дважды.


3
Это помогает, но также сводит на нет цель одного определения.
MikeJRamsey56

9

Нет, но следует использовать старые добрые соглашения об именах.

declare @MY_VALUE as int

@VictorYarema, потому что иногда соглашения - это все, что вам нужно. И потому, что иногда у тебя нет другого хорошего выбора. Теперь, помимо этого, ответ SQLMenace выглядит лучше, я с вами согласен. Даже в этом случае имя функции должно соответствовать соглашению для констант, IMO. Его следует назвать FN_CONSTANT(). Так понятно, что он делает.
tfrascaroli

Одно это не поможет, если вы хотите повысить производительность. Попробуйте также ответы Михала Д. и Джона Нильссона на повышение производительности.
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|
z00l

2
На ноутбуке разработчика с двумя дополнительными функциями без привязки схемы. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
monkeyhouse

Для сравнения, простой оператор select занял 4110 мс, где операторы select чередовались select top 1 @m = cv_val from code_values where cv_id = 'C101' и такие же, ... 'C201' где code_values ​​- таблица словаря с 250 варами, все они были на 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

Для перечислений или простых констант представление с одной строкой имеет отличную производительность и проверку времени компиляции / отслеживание зависимостей (потому что это имя столбца)

См. Сообщение в блоге Джареда Ко 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

Очевидно, это зависит от уникальности всего (значение без конечного пробела и комментария). Изменить его можно с помощью глобального поиска и замены.


Одна проблема в том, что он доступен только на местном уровне
Бернардо Даль Корно

0

В литературе по базам данных нет такого понятия, как «создание константы». Константы существуют как есть и часто называются значениями. Можно объявить переменную и присвоить ей значение (константу). Со схоластической точки зрения:

DECLARE @two INT
SET @two = 2

Здесь @two - переменная, а 2 - значение / константа.


Попробуйте также ответы Михала Д. и Джона Нильссона для повышения производительности.
WonderWorker

Литералы постоянны по определению. Символ ascii / unicode (в зависимости от редактора) 2преобразуется в двоичное значение при назначении "во время компиляции". Фактическое закодированное значение зависит от типа данных, которому оно присваивается (int, char, ...).
samis 02

-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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.