Czy istnieje sposób na uczynienie zmiennej TSQL stałą?

88

Czy istnieje sposób na uczynienie zmiennej TSQL stałą?

TheEmirOfGroofunkistan
źródło

Odpowiedzi:

60

Nie, ale możesz utworzyć funkcję, zakodować ją tam i użyć tego.

Oto przykład:

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

SELECT dbo.fnConstant()
SQLMenace
źródło
13
WITH SCHEMABINDING powinien przekształcić to w „rzeczywistą” stałą (wymóg, aby UDF był postrzegany jako deterministyczny w SQL). Oznacza to, że powinien zostać zapisany w pamięci podręcznej. Mimo to +1.
Jonathan Dickinson
ta odpowiedź jest dobra, po prostu ciekawi, czy kolumny w sqlserver mogą odwoływać się do funkcji jako wartości domyślnej. nie mogłem zmusić tego do pracy
Ab Bennett
1
@JonathanDickinson Aby było jasne, sugerujesz użycie WITH SCHEMABINDINGw CREATE FUNCTIONinstrukcji (w przeciwieństwie do procedury składowanej, która może wywoływać funkcję) - czy to prawda?
Holistic Developer
1
Tak, w funkcji. Z SCHEMABINDING pozwala SQL inline „inlined funkcje wycenione tabela” - dlatego też musi być w tej formie: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . Analizator zapytań nie wstawi niczego bez SCHEMABINDING ani niczego z BEGIN.
Jonathan Dickinson
Implikacje używania niedeterministycznych UDF: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/ ...
Ochoto,
28

Jednym z rozwiązań zaproponowanych przez Jareda Ko jest użycie pseudostałych .

Jak wyjaśniono w SQL Server: zmienne, parametry czy literały? Albo… Stałe? :

Pseudo-stałe nie są zmiennymi ani parametrami. Zamiast tego są po prostu widokami z jednym wierszem i wystarczającą liczbą kolumn do obsługi stałych. Dzięki tym prostym regułom silnik SQL całkowicie ignoruje wartość widoku, ale nadal tworzy plan wykonania na podstawie jego wartości. Plan wykonania nie pokazuje nawet połączenia do widoku!

Utwórz w ten sposób:

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]

Następnie użyj w ten sposób:

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

Lub tak:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
mbobka
źródło
1
To ZNACZNIE lepsze rozwiązanie niż przyjęta odpowiedź. Początkowo zeszliśmy na ścieżkę funkcji skalarnych i ma ona fatalną wydajność. O wiele lepsza jest ta odpowiedź i powyższy link do artykułu Jareda Ko.
David Coster,
Jednak dodanie WITH SCHEMABINDING do funkcji skalarnej wydaje się znacznie poprawić jej wydajność.
David Coster,
Link jest teraz martwy.
Matthieu Cormier
1
@MatthieuCormier: Zaktualizowałem łącze, chociaż wygląda na to, że MSDN i tak dodało przekierowanie ze starego adresu URL do nowego.
Ilmari Karonen
23

Moim obejściem braku stałych jest udzielenie wskazówek dotyczących wartości optymalizatorowi.

DECLARE @Constant INT = 123;

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

To mówi kompilatorowi zapytań, aby podczas tworzenia planu wykonania traktował zmienną tak, jakby była stałą. Wadą jest to, że musisz dwukrotnie zdefiniować wartość.

John Nilsson
źródło
3
Pomaga, ale też przeczy celowi jednej definicji.
MikeJRamsey56,
9

Nie, ale należy używać starych, dobrych konwencji nazewnictwa.

declare @MY_VALUE as int
jason saldo
źródło
@VictorYarema, ponieważ czasami wystarczy konwencja. A ponieważ czasami nie masz innego dobrego wyboru. Pomijając to, odpowiedź SQLMenace wygląda lepiej, zgadzam się z tobą. Mimo to nazwa funkcji powinna być zgodna z konwencją dla stałych IMO. Powinien zostać nazwany FN_CONSTANT(). W ten sposób jest jasne, co robi.
tfrascaroli
Samo to nie pomoże, jeśli chcesz uzyskać korzyści z wydajności. Wypróbuj również odpowiedzi Michała D. i Johna Nilssona, aby zwiększyć wydajność.
WonderWorker
8

W T-SQL nie ma wbudowanej obsługi stałych. Możesz użyć metody SQLMenace, aby to zasymulować (chociaż nigdy nie możesz być pewien, czy ktoś inny nadpisał funkcję, aby zwrócić coś innego…) lub napisać tabelę zawierającą stałe, jak sugerowano tutaj . Może napisać wyzwalacz, który cofnie wszelkie zmiany w ConstantValuekolumnie?

Sören Kuklau
źródło
7

Przed użyciem funkcji SQL uruchom następujący skrypt, aby zobaczyć różnice w wydajności:

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
Robert
źródło
4
To jest dość stare, ale w celach informacyjnych oto wynik po uruchomieniu na moim serwerze: | 2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
z00l
2
Na laptopie deweloperskim z dwiema dodatkowymi funkcjami bez wiązania schematu. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
Monkeyhouse
Dla porównania prosta instrukcja select zajęła 4110 ms, gdzie instrukcje select były naprzemiennie między select top 1 @m = cv_val from code_values where cv_id = 'C101' i tym samym, ... 'C201' gdzie code_values ​​jest tabelą słownika z 250 zmiennymi, wszystkie były na SQL-Server 2016
monkeyhouse
6

Jeśli chcesz uzyskać optymalny plan wykonania wartości w zmiennej, możesz użyć dynamicznego kodu sql. Sprawia, że ​​zmienna jest stała.

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)
Michał D.
źródło
1
Tak to robię i daje to ogromny wzrost wydajności zapytaniom, które obejmują stałe.
WonderWorker
5

W przypadku wyliczeń lub prostych stałych widok z pojedynczym wierszem ma doskonałą wydajność i sprawdzanie czasu kompilacji / śledzenie zależności (ponieważ jest to nazwa kolumny)

Zobacz wpis na blogu Jareda Ko: https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

utwórz widok

 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]

użyj widoku

SELECT h.*
FROM Sales.SalesOrderHeader 
WHERE ShipMethodID = ( select [OVERNIGHT J-FAST] from ShipMethods  )
małpia
źródło
3

Dobra, zobaczmy

Stałe są niezmiennymi wartościami, które są znane w czasie kompilacji i nie zmieniają się przez cały okres istnienia programu

oznacza to, że nigdy nie możesz mieć stałej w SQL Server

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

wartość właśnie się zmieniła

SQLMenace
źródło
1

Ponieważ nie ma wbudowanej obsługi stałych, moje rozwiązanie jest bardzo proste.

Ponieważ nie jest to obsługiwane:

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

Po prostu przekonwertowałbym to na

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

Oczywiście wszystko to (wartość bez spacji na końcu i komentarz) ma być unikalne. Zmiana jest możliwa dzięki globalnemu wyszukiwaniu i zamianie.

Gert-Jan
źródło
Jednym problemem jest to, że jest dostępny tylko lokalnie
Bernardo Dal Corno
0

W literaturze dotyczącej baz danych nie ma czegoś takiego jak „tworzenie stałej”. Stałe istnieją takie, jakie są i często nazywane są wartościami. Można zadeklarować zmienną i przypisać jej wartość (stałą). Ze scholastycznego punktu widzenia:

DECLARE @two INT
SET @two = 2

Tutaj @two jest zmienną, a 2 jest wartością / stałą.

Greg Hurlman
źródło
Wypróbuj również odpowiedzi Michała D. i Johna Nilssona, aby zwiększyć wydajność.
WonderWorker
Literały są z definicji stałe. Znak ascii / unicode (w zależności od edytora) 2jest tłumaczony na wartość binarną, gdy jest przypisywany w „czasie kompilacji”. Rzeczywista zakodowana wartość zależy od typu danych, do którego jest przypisana (int, char, ...).
samis
-1

Najlepszą odpowiedzią jest SQLMenace zgodnie z wymaganiami, jeśli chodzi o utworzenie tymczasowej stałej do użytku w skryptach, tj. W wielu instrukcjach / partiach GO.

Po prostu utwórz procedurę w tempdb, a nie będziesz mieć wpływu na docelową bazę danych.

Jednym z praktycznych przykładów jest skrypt tworzenia bazy danych, który zapisuje wartość kontrolną na końcu skryptu zawierającego wersję schematu logicznego. Na górze pliku znajdują się komentarze z historią zmian itp. Ale w praktyce większość programistów zapomina przewinąć w dół i zaktualizować wersję schematu na dole pliku.

Użycie powyższego kodu pozwala na zdefiniowanie widocznej stałej wersji schematu u góry, zanim skrypt bazy danych (skopiowany z funkcji generowania skryptów SSMS) utworzy bazę danych, ale zostanie użyta na końcu. Dzieje się tak w obliczu dewelopera, obok historii zmian i innych komentarzy, więc jest bardzo prawdopodobne, że zaktualizują go.

Na przykład:

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
Tony Wall
źródło