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





В литературе по базам данных нет такого понятия, как «создание константы». Константы существуют как есть и часто называются значениями. Можно объявить переменную и присвоить ей значение (константу). Со схоластической точки зрения:
DECLARE @two INT
SET @two = 2
Здесь @two - переменная, а 2 - значение / константа.
Литералы постоянны по определению. Символ ascii / unicode (в зависимости от редактора) 2 преобразуется в двоичное значение при назначении "во время компиляции". Фактическое закодированное значение зависит от типа данных, которому оно присваивается (int, char, ...).
Нет, но вы можете создать функцию, жестко закодировать ее и использовать.
Вот пример:
CREATE FUNCTION fnConstant()
RETURNS INT
AS
BEGIN
RETURN 2
END
GO
SELECT dbo.fnConstant()
WITH SCHEMABINDINGдолжен превращает это в «реальную» константу (требование для того, чтобы UDF рассматривалась как детерминированная в SQL). Т.е. он должен оказаться в кэше. Тем не менее, +1.
этот ответ хорош, просто любопытно, могут ли столбцы таблицы в sqlserver ссылаться на функцию как на значение по умолчанию. я не мог заставить это работать
@JonathanDickinson Для ясности, вы предлагаете использовать WITH SCHEMABINDING в операторе CREATE FUNCTION (в отличие от хранимой процедуры, которая может вызывать функцию) - это правильно?
Да, в функции. WITH SCHEMABINDING позволяет SQL встраивать «встроенные функции, возвращающие табличное значение», поэтому также должен быть в такой форме: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb. Анализатор запросов не будет ничего встраивать без SCHEMABINDING или чего-либо с BEGIN.
Последствия использования недетерминированных UDF: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
Нет, но следует использовать старые добрые соглашения об именах.
declare @MY_VALUE as int
@VictorYarema, потому что иногда соглашения - это все, что вам нужно. И потому, что иногда у тебя нет другого хорошего выбора. Теперь, помимо этого, ответ SQLMenace выглядит лучше, я с вами согласен. Даже в этом случае имя функции должно соответствовать соглашению для констант, IMO. Он должен называться FN_CONSTANT(). Так понятно, что он делает.
Само по себе это не поможет, если вы хотите повысить производительность. Попробуйте также ответы Михала Д. и Джона Нильссона на повышение производительности.
Хорошо, давай посмотрим
Константы - это неизменяемые значения, которые известны во время компиляции и не меняются в течение жизни программы.
это означает, что у вас никогда не может быть константы в SQL Server
declare @myvalue as int
set @myvalue = 5
set @myvalue = 10--oops we just changed it
значение только что изменилось
В T-SQL нет встроенной поддержки констант. Вы можете использовать подход SQLMenace для моделирования этого (хотя вы никогда не можете быть уверены, что кто-то перезаписал функцию, чтобы вернуть что-то еще…), или, возможно, написать таблицу, содержащую константы, как предлагается здесь. Может быть, написать триггер, который откатывает любые изменения в столбце ConstantValue?
Мой обходной путь к отсутствию констант - дать подсказку о значении оптимизатору.
DECLARE @Constant INT = 123;
SELECT *
FROM [some_relation]
WHERE [some_attribute] = @Constant
OPTION( OPTIMIZE FOR (@Constant = 123))
Это указывает компилятору запроса обрабатывать переменную, как если бы она была константой при создании плана выполнения. Обратной стороной является то, что вам нужно определить значение дважды.
Это помогает, но также сводит на нет цель одного определения.
Лучший ответ - от 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
Перед использованием функции 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 vars, все они были на SQL-Server 2016.
Одним из решений, предложенных Джаредом Ко, является использование псевдоконстанты.
Как поясняется в SQL Server: переменные, параметры или литералы? Или… константы?:
Pseudo-Constants are not variables or parameters. Instead, they're simply views with one row, and enough columns to support your constants. With these simple rules, the SQL Engine completely ignores the value of the view but still builds an execution plan based on its value. The execution plan doesn't even show a join to the view!
Create like this:
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]Then use like this:
SELECT h.* FROM Sales.SalesOrderHeader h JOIN ShipMethod.ShipMethodID const ON h.ShipMethodID = const.[OVERNIGHT J-FAST]Or like this:
SELECT h.* FROM Sales.SalesOrderHeader h WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
Это НАМНОГО лучшее решение, чем принятый ответ. Сначала мы пошли по пути скалярных функций, и у них ужасная производительность. Намного лучше этот ответ и приведенная выше ссылка на статью Джареда Ко.
Однако добавление WITH SCHEMABINDING к скалярной функции, по-видимому, значительно улучшает ее производительность.
Ссылка сейчас мертва.
@MatthieuCormier: Я обновил ссылку, хотя кажется, что MSDN все равно добавила перенаправление со старого URL-адреса на новый.
Если вы заинтересованы в получении оптимального плана выполнения для значения в переменной, вы можете использовать динамический код sql. Это делает переменную постоянной.
DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)
Вот как я это делаю, и это дает огромный прирост производительности для запросов, содержащих константы.
Для перечислений или простых констант представление с одной строкой имеет отличную производительность и проверку времени компиляции / отслеживание зависимостей (потому что это имя столбца)
См. Сообщение в блоге Джареда Ко 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 )
Поскольку нет встроенной поддержки констант, мое решение очень простое.
Поскольку это не поддерживается:
Declare Constant @supplement int = 240
SELECT price + @supplement
FROM what_does_it_cost
Я бы просто преобразовал его в
SELECT price + 240/*CONSTANT:supplement*/
FROM what_does_it_cost
Очевидно, это зависит от уникальности всего объекта (значения без конечного пробела и комментария). Изменить его можно с помощью глобального поиска и замены.
Одна проблема в том, что он доступен только локально
Попробуйте также ответы Михала Д. и Джона Нильссона для повышения производительности.