Existe-t-il un moyen de rendre constante une variable TSQL?

88

Existe-t-il un moyen de rendre constante une variable TSQL?

TheEmirOfGroofunkistan
la source

Réponses:

60

Non, mais vous pouvez créer une fonction et la coder en dur et l'utiliser.

Voici un exemple:

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

SELECT dbo.fnConstant()
SQLMenace
la source
13
WITH SCHEMABINDING devrait transformer cela en une constante «réelle» (une exigence pour qu'un UDF soit considéré comme déterministe en SQL). Ie il devrait atterrir en cache. Pourtant, +1.
Jonathan Dickinson
cette réponse est bonne, les colonnes de table dans sqlserver peuvent simplement faire référence à une fonction comme valeur par défaut. Je ne pouvais pas faire fonctionner cela
Ab Bennett
1
@JonathanDickinson Pour être clair, votre suggestion est d'utiliser WITH SCHEMABINDINGdans l' CREATE FUNCTIONinstruction (par opposition à une procédure stockée qui pourrait appeler la fonction) - est-ce exact?
Développeur holistique
1
Oui, dans la fonction. WITH SCHEMABINDING permet à SQL d'insérer des "fonctions table-valued inlined" - il doit donc également être sous cette forme: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . L'analyseur de requêtes n'inline rien sans SCHEMABINDING ou quoi que ce soit avec BEGIN.
Jonathan Dickinson
Implications de l'utilisation des UDF non déterministes: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability
...
28

Une solution proposée par Jared Ko est d'utiliser des pseudo-constantes .

Comme expliqué dans SQL Server: variables, paramètres ou littéraux? Ou… des constantes? :

Les pseudo-constantes ne sont pas des variables ou des paramètres. Au lieu de cela, ce sont simplement des vues avec une ligne et suffisamment de colonnes pour prendre en charge vos constantes. Avec ces règles simples, le moteur SQL ignore complètement la valeur de la vue, mais construit toujours un plan d'exécution basé sur sa valeur. Le plan d'exécution n'affiche même pas de jointure à la vue!

Créez comme ceci:

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]

Ensuite, utilisez comme ceci:

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

Ou comme ça:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
mbobka
la source
1
C'est une solution BEAUCOUP meilleure que la réponse acceptée. Au départ, nous avons emprunté la route des fonctions scalaires et les performances sont terribles. Cette réponse et le lien ci-dessus vers l'article de Jared Ko sont bien meilleurs.
David Coster
Cependant, l'ajout de WITH SCHEMABINDING à une fonction scalaire semble améliorer ses performances de manière significative.
David Coster
Le lien est mort maintenant.
Matthieu Cormier
1
@MatthieuCormier: J'ai mis à jour le lien, bien qu'il semble que MSDN ait ajouté une redirection de l'ancienne URL vers la nouvelle de toute façon.
Ilmari Karonen
23

Ma solution de contournement aux constans manquants est de donner des conseils sur la valeur à l'optimiseur.

DECLARE @Constant INT = 123;

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

Cela indique au compilateur de requêtes de traiter la variable comme s'il s'agissait d'une constante lors de la création du plan d'exécution. L'inconvénient est que vous devez définir la valeur deux fois.

John Nilsson
la source
3
Cela aide, mais cela va également à l'encontre de l'objectif d'une définition unique.
MikeJRamsey56
9

Non, mais de bonnes vieilles conventions de dénomination devraient être utilisées.

declare @MY_VALUE as int
Jason Saldo
la source
@VictorYarema parce que parfois la convention est tout ce dont vous avez besoin. Et parce que parfois vous n'avez pas d'autre bon choix. Maintenant, cela mis à part, la réponse de SQLMenace semble meilleure, je suis d'accord avec vous. Même ainsi, le nom des fonctions doit suivre la convention pour les constantes, IMO. Il devrait être nommé FN_CONSTANT(). De cette façon, ce qu'il fait est clair.
tfrascaroli
Cela seul n'aidera pas lorsque vous souhaitez bénéficier des performances. Essayez également les réponses de Michal D. et John Nilsson pour l'amélioration des performances.
WonderWorker
8

Il n'y a pas de prise en charge intégrée des constantes dans T-SQL. Vous pouvez utiliser l'approche de SQLMenace pour le simuler (bien que vous ne puissiez jamais être sûr si quelqu'un d'autre a écrasé la fonction pour renvoyer quelque chose d'autre…), ou éventuellement écrire une table contenant des constantes, comme suggéré ici . Peut-être écrire un déclencheur qui annule les modifications apportées à la ConstantValuecolonne?

Sören Kuklau
la source
7

Avant d'utiliser une fonction SQL, exécutez le script suivant pour voir les différences de performances:

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
la source
4
C'est assez ancien, mais pour référence, voici le résultat lorsqu'il est exécuté sur mon serveur: | 2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
z00l
2
Sur un ordinateur portable de développement, avec deux fonctions supplémentaires sans liaison de schéma. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
monkeyhouse
À titre de comparaison, une simple instruction select a pris 4110 ms où les instructions select alternaient entre select top 1 @m = cv_val from code_values where cv_id = 'C101' et même ... 'C201' où code_values ​​est une table de dictionnaire avec 250 vars, il y en avait tous sur SQL-Server 2016
monkeyhouse
6

Si vous souhaitez obtenir un plan d'exécution optimal pour une valeur de la variable, vous pouvez utiliser un code SQL dynamique. Cela rend la variable constante.

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)
Michal D.
la source
1
C'est ainsi que je le fais et cela donne une énorme amélioration des performances aux requêtes qui impliquent des constantes.
WonderWorker
5

Pour les énumérations ou les constantes simples, une vue avec une seule ligne a d'excellentes performances et la vérification du temps de compilation / le suivi des dépendances (cause son nom de colonne)

Voir l'article de blog de Jared Ko https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

créer la vue

 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]

utiliser la vue

SELECT h.*
FROM Sales.SalesOrderHeader 
WHERE ShipMethodID = ( select [OVERNIGHT J-FAST] from ShipMethods  )
maison de singe
la source
3

D'accord, voyons voir

Les constantes sont des valeurs immuables qui sont connues au moment de la compilation et qui ne changent pas pendant la durée de vie du programme

cela signifie que vous ne pouvez jamais avoir de constante dans SQL Server

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

la valeur vient de changer

SQLMenace
la source
1

Puisqu'il n'y a pas de support intégré pour les constantes, ma solution est très simple.

Puisque cela n'est pas pris en charge:

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

Je le convertirais simplement en

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

Évidemment, cela repose sur le fait que l'ensemble (la valeur sans espace de fin et le commentaire) soit unique. Le changer est possible avec une recherche globale et un remplacement.

Gert-Jan
la source
Un problème est qu'il est disponible uniquement localement
Bernardo Dal Corno
0

Il n'existe pas de "création d'une constante" dans la littérature des bases de données. Les constantes existent telles qu'elles sont et sont souvent appelées valeurs. On peut déclarer une variable et lui attribuer une valeur (constante). D'un point de vue scolaire:

DECLARE @two INT
SET @two = 2

Ici, @two est une variable et 2 est une valeur / constante.

Greg Hurlman
la source
Essayez également les réponses de Michal D. et John Nilsson pour améliorer vos performances.
WonderWorker
Les littéraux sont constants par définition. Le caractère ascii / unicode (selon l'éditeur) 2est traduit en valeur binaire lorsqu'il est attribué au "moment de la compilation". La valeur réelle encodée dépend du type de données auquel elle est affectée (int, char, ...).
samis
-1

La meilleure réponse vient de SQLMenace selon l'exigence de créer une constante temporaire à utiliser dans les scripts, c'est-à-dire sur plusieurs instructions / lots GO.

Créez simplement la procédure dans tempdb, vous n'aurez aucun impact sur la base de données cible.

Un exemple pratique de ceci est un script de création de base de données qui écrit une valeur de contrôle à la fin du script contenant la version du schéma logique. En haut du fichier se trouvent des commentaires avec l'historique des modifications, etc ... Mais en pratique, la plupart des développeurs oublieront de faire défiler vers le bas et de mettre à jour la version du schéma en bas du fichier.

L'utilisation du code ci-dessus permet de définir une constante de version de schéma visible en haut avant que le script de base de données (copié à partir de la fonction de génération de scripts de SSMS) ne crée la base de données mais utilisé à la fin. C'est juste en face du développeur à côté de l'historique des modifications et d'autres commentaires, il est donc très susceptible de le mettre à jour.

Par exemple:

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
la source