C'è un modo per rendere costante una variabile TSQL?


Risposte:


60

No, ma puoi creare una funzione e codificarla lì e usarla.

Ecco un esempio:

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

SELECT dbo.fnConstant()

13
WITH SCHEMABINDING dovrebbe trasformarlo in una costante "reale" (un requisito affinché una UDF sia vista come deterministica in SQL). Vale a dire che dovrebbe finire per essere memorizzato nella cache. Tuttavia, +1.
Jonathan Dickinson

questa risposta è buona, solo per curiosità le colonne della tabella in sqlserver fanno riferimento a una funzione come valore predefinito. Non sono riuscito a farlo funzionare
Ab Bennett

1
@JonathanDickinson Per essere chiari, il suo suggerimento è quello di utilizzare WITH SCHEMABINDINGnella CREATE FUNCTIONdichiarazione (al contrario di in una stored procedure che potrebbe essere chiamata la funzione) - è giusto?
Sviluppatore olistico

1
Sì, nella funzione. CON SCHEMABINDING permette SQL per inline "funzioni con valori di tabella inline" - così è anche bisogno di essere in questa forma: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . L'analizzatore di query non incorporerà nulla senza SCHEMABINDING o nulla con BEGIN.
Jonathan Dickinson,

Implicazioni dell'utilizzo di UDF non deterministiche: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
Ochoto

28

Una soluzione, offerta da Jared Ko, consiste nell'utilizzare pseudo-costanti .

Come spiegato in SQL Server: variabili, parametri o valori letterali? Oppure ... Costanti? :

Le pseudo-costanti non sono variabili o parametri. Invece, sono semplicemente visualizzazioni con una riga e abbastanza colonne per supportare le tue costanti. Con queste semplici regole, il motore SQL ignora completamente il valore della vista ma crea comunque un piano di esecuzione basato sul suo valore. Il piano di esecuzione non mostra nemmeno un'unione alla vista!

Crea in questo modo:

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]

Quindi usa in questo modo:

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

O in questo modo:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)

1
Questa è una soluzione MOLTO migliore rispetto alla risposta accettata. Inizialmente abbiamo seguito il percorso della funzione scalare e ha prestazioni terribili. Molto meglio è questa risposta e il link sopra all'articolo di Jared Ko.
David Coster,

Tuttavia, l'aggiunta di WITH SCHEMABINDING a una funzione scalare sembra migliorare le sue prestazioni in modo significativo.
David Coster,

Il collegamento ora è morto.
Matthieu Cormier

1
@ MatthieuCormier: ho aggiornato il collegamento, anche se sembra che MSDN abbia comunque aggiunto un reindirizzamento dal vecchio URL a quello nuovo.
Ilmari Karonen

23

La mia soluzione alternativa per le costanti mancanti è fornire suggerimenti sul valore all'ottimizzatore.

DECLARE @Constant INT = 123;

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

Ciò indica al compilatore di query di trattare la variabile come se fosse una costante durante la creazione del piano di esecuzione. Il lato negativo è che devi definire il valore due volte.


3
Aiuta ma sconfigge anche lo scopo di un'unica definizione.
MikeJRamsey56

10

No, ma dovrebbero essere usate le buone vecchie convenzioni di denominazione.

declare @MY_VALUE as int

@VictorYarema perché a volte le convenzioni sono tutto ciò di cui hai bisogno. E perché a volte non hai altra buona scelta. A parte questo, la risposta di SQLMenace sembra migliore, sarò d'accordo con te. Anche così, il nome della funzione dovrebbe seguire la convenzione per le costanti, IMO. Dovrebbe essere nominato FN_CONSTANT(). In questo modo è chiaro cosa sta facendo.
tfrascaroli

Questo da solo non aiuta quando vuoi il vantaggio in termini di prestazioni. Prova anche le risposte di Michal D. e John Nilsson per l'aumento delle prestazioni.
WonderWorker

8

Non c'è supporto integrato per le costanti in T-SQL. Potresti usare l'approccio di SQLMenace per simularlo (anche se non puoi mai essere sicuro che qualcun altro abbia sovrascritto la funzione per restituire qualcos'altro ...), o possibilmente scrivere una tabella contenente costanti, come suggerito qui . Forse scrivere un trigger che ripristini le modifiche alla ConstantValuecolonna?


7

Prima di utilizzare una funzione SQL, eseguire il seguente script per vedere le differenze nelle prestazioni:

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
Questo è abbastanza vecchio, ma per riferimento ecco il risultato quando eseguito sul mio server: | 2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
z00l

2
Su un laptop di sviluppo, con due funzioni aggiuntive senza associazione di schemi. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
Monkeyhouse

Per confronto, una semplice istruzione select ha impiegato 4110 ms in cui le istruzioni select si alternavano tra select top 1 @m = cv_val from code_values where cv_id = 'C101' e lo stesso ... 'C201' dove code_values ​​è la tabella del dizionario con 250 variabili, c'erano tutte su SQL-Server 2016
monkeyhouse

6

Se sei interessato a ottenere un piano di esecuzione ottimale per un valore nella variabile, puoi utilizzare un codice SQL dinamico. Rende la variabile costante.

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)

1
Questo è il modo in cui lo faccio e offre un enorme aumento delle prestazioni alle query che coinvolgono costanti.
WonderWorker

5

Per enumerazioni o costanti semplici, una vista con una singola riga ha ottime prestazioni e controllo del tempo di compilazione / rilevamento delle dipendenze (perché è un nome di colonna)

Vedi il post del blog di Jared Ko https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

creare la vista

 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]

usa la vista

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

3

Va bene, vediamo

Le costanti sono valori immutabili che sono noti in fase di compilazione e non cambiano per la vita del programma

ciò significa che non puoi mai avere una costante in SQL Server

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

il valore è appena cambiato


1

Poiché non esiste una build a supporto delle costanti, la mia soluzione è molto semplice.

Poiché questo non è supportato:

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

Lo convertirò semplicemente in

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

Ovviamente, questo si basa sul fatto che l'intera cosa (il valore senza lo spazio finale e il commento) sia univoca. Cambiarlo è possibile con una ricerca e sostituzione globale.


Un problema è che è disponibile solo localmente
Bernardo Dal Corno

0

Non esistono cose come "creare una costante" nella letteratura del database. Le costanti esistono così come sono e spesso vengono chiamate valori. Si può dichiarare una variabile e assegnarle un valore (costante). Da un punto di vista scolastico:

DECLARE @two INT
SET @two = 2

Qui @two è una variabile e 2 è un valore / costante.


Prova anche le risposte di Michal D. e John Nilsson per aumentare le prestazioni.
WonderWorker

I letterali sono costanti per definizione. Il carattere ascii / unicode (a seconda dell'editor) 2viene tradotto in un valore binario quando viene assegnato in "fase di compilazione". Il valore effettivo codificato dipende dal tipo di dati a cui viene assegnato (int, char, ...).
samis

-1

La risposta migliore proviene da SQLMenace in base al requisito se si tratta di creare una costante temporanea da utilizzare all'interno degli script, ovvero attraverso più istruzioni / batch GO.

Basta creare la procedura nel tempdb, quindi non hai alcun impatto sul database di destinazione.

Un esempio pratico di ciò è uno script di creazione del database che scrive un valore di controllo alla fine dello script contenente la versione dello schema logico. Nella parte superiore del file ci sono alcuni commenti con la cronologia delle modifiche ecc ... Ma in pratica la maggior parte degli sviluppatori dimenticherà di scorrere verso il basso e aggiornare la versione dello schema in fondo al file.

L'utilizzo del codice precedente consente di definire una costante di versione dello schema visibile all'inizio prima che lo script del database (copiato dalla funzione di generazione degli script di SSMS) crei il database ma utilizzato alla fine. Questo è giusto di fronte allo sviluppatore accanto alla cronologia delle modifiche e ad altri commenti, quindi è molto probabile che lo aggiornino.

Per esempio:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.