Gibt es eine Möglichkeit, eine TSQL-Variable konstant zu machen?

85

Gibt es eine Möglichkeit, eine TSQL-Variable konstant zu machen?

TheEmirOfGroofunkistan
quelle

Antworten:

58

Nein, aber Sie können eine Funktion erstellen und dort fest codieren und verwenden.

Hier ist ein Beispiel:

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

SELECT dbo.fnConstant()
SQLMenace
quelle
13
WITH SCHEMABINDING sollte dies in eine 'echte' Konstante umwandeln (eine Voraussetzung dafür, dass eine UDF in SQL als deterministisch angesehen wird). Dh es sollte landen, wenn es zwischengespeichert wird. Trotzdem +1.
Jonathan Dickinson
Diese Antwort ist gut, nur neugierig können Tabellenspalten in SQL Server eine Funktion als Standardwert referenzieren. Ich konnte das nicht zum Laufen bringen
Ab Bennett
1
@JonathanDickinson Um klar zu sein, ist Ihr Vorschlag, WITH SCHEMABINDINGin der CREATE FUNCTIONAnweisung zu verwenden (im Gegensatz zu einer gespeicherten Prozedur, die möglicherweise die Funktion aufruft) - ist das richtig?
Ganzheitlicher Entwickler
1
Ja, in der Funktion. Mit SCHEMABINDING kann SQL "inline-Funktionen mit Tabellenwerten" einbinden - daher muss es auch in folgender Form vorliegen : gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . Der Abfrageanalysator wird nichts ohne SCHEMABINDING oder irgendetwas mit BEGIN inline.
Jonathan Dickinson
Auswirkungen der Verwendung nicht deterministischer UDFs: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
Ochoto
27

Eine von Jared Ko angebotene Lösung ist die Verwendung von Pseudokonstanten .

Wie in SQL Server erläutert : Variablen, Parameter oder Literale? Oder ... Konstanten? ::

Pseudokonstanten sind keine Variablen oder Parameter. Stattdessen handelt es sich lediglich um Ansichten mit einer Zeile und genügend Spalten, um Ihre Konstanten zu unterstützen. Mit diesen einfachen Regeln ignoriert die SQL Engine den Wert der Ansicht vollständig, erstellt jedoch weiterhin einen Ausführungsplan basierend auf ihrem Wert. Der Ausführungsplan zeigt nicht einmal einen Join zur Ansicht!

Erstellen Sie wie folgt:

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]

Dann verwenden Sie wie folgt:

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

Oder so:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
mbobka
quelle
1
Dies ist eine viel bessere Lösung als die akzeptierte Antwort. Anfangs sind wir den Weg der Skalarfunktion gegangen und es hat eine schreckliche Leistung. Viel besser ist diese Antwort und der Link oben zu Jared Ko's Artikel.
David Coster
Das Hinzufügen von WITH SCHEMABINDING zu einer Skalarfunktion scheint jedoch die Leistung erheblich zu verbessern.
David Coster
Der Link ist jetzt tot.
Matthieu Cormier
1
@MatthieuCormier: Ich habe den Link aktualisiert, obwohl es den Anschein hat, dass MSDN ohnehin eine Weiterleitung von der alten zur neuen URL hinzugefügt hat.
Ilmari Karonen
22

Meine Problemumgehung für fehlende Konstanten besteht darin, dem Optimierer Hinweise zum Wert zu geben.

DECLARE @Constant INT = 123;

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

Dies weist den Abfrage-Compiler an, die Variable beim Erstellen des Ausführungsplans so zu behandeln, als wäre sie eine Konstante. Der Nachteil ist, dass Sie den Wert zweimal definieren müssen.

John Nilsson
quelle
3
Es hilft, aber es macht auch den Zweck einer einzelnen Definition zunichte.
MikeJRamsey56
9

Nein, aber es sollten gute alte Namenskonventionen verwendet werden.

declare @MY_VALUE as int
Jason Saldo
quelle
@ VictorYarema, weil manchmal Konvention alles ist, was Sie brauchen. Und weil du manchmal keine andere gute Wahl hast. Abgesehen davon sieht die Antwort von SQLMenace besser aus, ich stimme Ihnen zu. Trotzdem sollte der Funktionsname der Konvention für Konstanten IMO entsprechen. Es sollte benannt werden FN_CONSTANT(). Auf diese Weise ist klar, was es tut.
Tfrascaroli
Dies allein hilft nicht, wenn Sie den Leistungsvorteil wünschen. Probieren Sie auch die Antworten von Michal D. und John Nilsson für die Leistungssteigerung.
WonderWorker
7

Es gibt keine integrierte Unterstützung für Konstanten in T-SQL. Sie können den SQLMenace-Ansatz verwenden, um ihn zu simulieren (obwohl Sie nie sicher sein können, ob jemand anderes die Funktion überschrieben hat, um etwas anderes zurückzugeben ...) oder möglicherweise eine Tabelle mit Konstanten schreiben, wie hier vorgeschlagen . Vielleicht einen Trigger schreiben, der alle Änderungen an der ConstantValueSpalte rückgängig macht?

Sören Kuklau
quelle
7

Führen Sie vor der Verwendung einer SQL-Funktion das folgende Skript aus, um die Leistungsunterschiede festzustellen:

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
quelle
4
Dies ist ziemlich alt, aber als Referenz ist hier das Ergebnis, wenn es auf meinem Server ausgeführt wird: | 2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
z00l
2
Auf einem Entwickler-Laptop mit zwei zusätzlichen Funktionen ohne Schema-Bindung. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
Affenhaus
Zum Vergleich dauerte eine einfache select-Anweisung 4110 ms, wobei die select-Anweisungen zwischen select top 1 @m = cv_val from code_values where cv_id = 'C101' und dieselbe wechselten, ... 'C201' wobei code_values ​​eine Wörterbuchtabelle mit 250 Variablen ist. Alle waren auf SQL-Server 2016
Affenhaus
6

Wenn Sie einen optimalen Ausführungsplan für einen Wert in der Variablen erhalten möchten, können Sie einen dynamischen SQL-Code verwenden. Es macht die Variable konstant.

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)
Michal D.
quelle
1
So mache ich das und es gibt Abfragen, die Konstanten beinhalten, einen enormen Leistungsschub.
WonderWorker
5

Bei Aufzählungen oder einfachen Konstanten bietet eine Ansicht mit einer einzelnen Zeile eine hervorragende Leistung und Überprüfung der Kompilierungszeit / Abhängigkeitsverfolgung (da es sich um einen Spaltennamen handelt).

Siehe den Blog-Beitrag von Jared Ko unter https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

Erstellen Sie die Ansicht

 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]

Verwenden Sie die Ansicht

SELECT h.*
FROM Sales.SalesOrderHeader 
WHERE ShipMethodID = ( select [OVERNIGHT J-FAST] from ShipMethods  )
Affenhaus
quelle
3

Okay, mal sehen

Konstanten sind unveränderliche Werte, die zur Kompilierungszeit bekannt sind und sich während der Laufzeit des Programms nicht ändern

Das bedeutet, dass Sie in SQL Server niemals eine Konstante haben können

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

Der Wert hat sich gerade geändert

SQLMenace
quelle
1

Da es keine eingebaute Unterstützung für Konstanten gibt, ist meine Lösung sehr einfach.

Da dies nicht unterstützt wird:

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

Ich würde es einfach umwandeln

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

Dies hängt natürlich davon ab, dass das Ganze (der Wert ohne Leerzeichen und der Kommentar) eindeutig ist. Das Ändern ist mit einem globalen Suchen und Ersetzen möglich.

Gert-Jan
quelle
Ein Problem ist, dass es nur lokal verfügbar ist
Z. Khullah
0

In der Datenbankliteratur gibt es kein "Erstellen einer Konstante". Konstanten existieren so wie sie sind und werden oft als Werte bezeichnet. Man kann eine Variable deklarieren und ihr einen Wert (Konstante) zuweisen. Aus schulischer Sicht:

DECLARE @two INT
SET @two = 2

Hier ist @two eine Variable und 2 ist ein Wert / eine Konstante.

Greg Hurlman
quelle
Probieren Sie auch die Antworten von Michal D. und John Nilsson aus, um die Leistung zu steigern.
WonderWorker
Literale sind per Definition konstant. Das ASCII / Unicode-Zeichen (abhängig vom Editor) 2wird bei Zuweisung zur "Kompilierungszeit" in einen Binärwert übersetzt. Der tatsächlich codierte Wert hängt vom Datentyp ab, dem er zugewiesen ist (int, char, ...).
Samis
-1

Die beste Antwort kommt von SQLMenace, je nachdem, ob eine temporäre Konstante für die Verwendung in Skripten erstellt werden soll, dh über mehrere GO-Anweisungen / Stapel hinweg.

Erstellen Sie einfach die Prozedur in der Tempdb, dann haben Sie keine Auswirkungen auf die Zieldatenbank.

Ein praktisches Beispiel hierfür ist ein Datenbankerstellungsskript, das am Ende des Skripts, das die logische Schemaversion enthält, einen Steuerwert schreibt. Am Anfang der Datei befinden sich einige Kommentare mit Änderungsverlauf usw. In der Praxis werden die meisten Entwickler jedoch vergessen, nach unten zu scrollen und die Schemaversion am Ende der Datei zu aktualisieren.

Mit dem obigen Code kann oben eine sichtbare Schema-Versionskonstante definiert werden, bevor das Datenbankskript (kopiert von der Funktion zum Generieren von Skripten von SSMS) die Datenbank erstellt, aber am Ende verwendet wird. Dies steht dem Entwickler direkt neben dem Änderungsverlauf und anderen Kommentaren gegenüber, sodass es sehr wahrscheinlich ist, dass er aktualisiert wird.

Beispielsweise:

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
quelle