Чи є спосіб зробити змінну TSQL постійною?


Відповіді:


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 SCHEMABINDINGв CREATE FUNCTIONоператорі (на відміну від збереженої процедури, яка може викликати функцію) - це правильно?
Холістичний розробник

1
Так, у функції. WITH SCHEMABINDING дозволяє SQL вбудовувати "вбудовані функції, що мають значення таблиці" - тому він також повинен бути у такій формі: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . Аналізатор запитів не вбудовує нічого без СХЕМБІНДИНГУ або чогось іншого з ПОЧАТКОМ.
Джонатан Дікінсон,

Наслідки використання недетермінованих UDF: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
квітня

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

-1

Найкраща відповідь від SQLMenace відповідно до вимоги, якщо це створити тимчасову константу для використання в сценаріях, тобто в декількох операторах GO / пакетах.

Просто створіть процедуру в tempdb, тоді ви не вплинете на цільову базу даних.

Одним з практичних прикладів цього є сценарій створення бази даних, який записує контрольне значення в кінці сценарію, що містить версію логічної схеми. У верхній частині файлу є кілька коментарів з історією змін тощо ... Але на практиці більшість розробників забудуть прокрутити вниз та оновити версію схеми внизу файлу.

Використання вищевказаного коду дозволяє визначити константу видимої версії схеми вгорі перед тим, як сценарій бази даних (скопійований із функції створення сценаріїв СУБД) створює базу даних, але використовується в кінці. Це прямо перед обличчям розробника поруч із історією змін та іншими коментарями, тому вони, швидше за все, оновлять її.

Наприклад:

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.