¿Hay alguna forma de hacer que una variable TSQL sea constante?

88

¿Hay alguna forma de hacer que una variable TSQL sea constante?

TheEmirOfGroofunkistan
fuente

Respuestas:

60

No, pero puede crear una función y codificarla allí y usarla.

Aquí hay un ejemplo:

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

SELECT dbo.fnConstant()
SQLMenace
fuente
13
WITH SCHEMABINDING debería convertir esto en una constante 'real' (un requisito para que una UDF se considere determinista en SQL). Es decir, debería aterrizar en caché. Aún así, +1.
Jonathan Dickinson
esta respuesta es buena, solo por curiosidad, las columnas de la tabla en sqlserver hacen referencia a una función como valor predeterminado. No pude hacer que esto funcione
Ab Bennett
1
@JonathanDickinson Para ser claros, su sugerencia es usar WITH SCHEMABINDINGen la CREATE FUNCTIONdeclaración (en lugar de en un procedimiento almacenado que podría estar llamando a la función), ¿es así?
Desarrollador holístico
1
Sí, en la función. WITH SCHEMABINDING permite que SQL inserte "funciones alineadas con valores de tabla", por lo que también debe tener este formato: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . El analizador de consultas no integra nada sin SCHEMABINDING ni nada con BEGIN.
Jonathan Dickinson
Implicaciones del uso de UDF no deterministas: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
Ochoto
28

Una solución que ofrece Jared Ko es utilizar pseudoconstantes .

Como se explica en SQL Server: ¿variables, parámetros o literales? ¿O… constantes? :

Las pseudoconstantes no son variables ni parámetros. En cambio, son simplemente vistas con una fila y suficientes columnas para admitir sus constantes. Con estas reglas simples, el motor SQL ignora por completo el valor de la vista, pero aún crea un plan de ejecución basado en su valor. ¡El plan de ejecución ni siquiera muestra una unión a la vista!

Crea así:

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]

Entonces usa así:

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

O así:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
mbobka
fuente
1
Esta es una solución MUCHO mejor que la respuesta aceptada. Inicialmente tomamos la ruta de la función escalar y tiene un rendimiento terrible. Mucho mejor es esta respuesta y el enlace anterior al artículo de Jared Ko.
David Coster
Sin embargo, la adición de WITH SCHEMABINDING a una función escalar parece mejorar significativamente su rendimiento.
David Coster
El vínculo está muerto ahora.
Matthieu Cormier
1
@MatthieuCormier: He actualizado el enlace, aunque parece que MSDN había agregado una redirección de la URL anterior a la nueva de todos modos.
Ilmari Karonen
23

Mi solución a las constantes faltantes es dar pistas sobre el valor del optimizador.

DECLARE @Constant INT = 123;

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

Esto le dice al compilador de consultas que trate la variable como si fuera una constante al crear el plan de ejecución. La desventaja es que tienes que definir el valor dos veces.

John Nilsson
fuente
3
Ayuda pero también frustra el propósito de una definición única.
MikeJRamsey56
9

No, pero se deben usar las buenas convenciones de nomenclatura.

declare @MY_VALUE as int
jason saldo
fuente
@VictorYarema porque a veces la convención es todo lo que necesitas. Y porque a veces no tienes otra opción buena. Ahora, aparte de eso, la respuesta de SQLMenace se ve mejor, estoy de acuerdo contigo. Aun así, el nombre de las funciones debe seguir la convención para constantes, IMO. Debería ser nombrado FN_CONSTANT(). De esa forma queda claro lo que está haciendo.
tfrascaroli
Esto por sí solo no ayudará cuando desee el beneficio de rendimiento. Pruebe también las respuestas de Michal D. y John Nilsson para aumentar el rendimiento.
WonderWorker
8

No hay soporte integrado para constantes en T-SQL. Puede usar el enfoque de SQLMenace para simularlo (aunque nunca puede estar seguro de si alguien más ha sobrescrito la función para devolver algo más ...), o posiblemente escribir una tabla que contenga constantes, como se sugiere aquí . ¿Quizás escribir un disparador que revierte cualquier cambio en la ConstantValuecolumna?

Sören Kuklau
fuente
7

Antes de utilizar una función de SQL, ejecute el siguiente script para ver las diferencias en el rendimiento:

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
fuente
4
Esto es bastante antiguo, pero como referencia, aquí está el resultado cuando se ejecuta en mi servidor: | 2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
z00l
2
En una computadora portátil de desarrollo, con dos funciones adicionales sin enlace de esquema. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
Monkeyhouse
A modo de comparación, una declaración de selección simple tomó 4110ms donde las declaraciones de selección se alternaron entre select top 1 @m = cv_val from code_values where cv_id = 'C101' y lo mismo ... 'C201' donde code_values ​​es una tabla de diccionario con 250 vars, todos estaban en SQL-Server 2016
monkeyhouse
6

Si está interesado en obtener un plan de ejecución óptimo para un valor en la variable, puede usar un código SQL dinámico. Hace que la variable sea constante.

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)
Michal D.
fuente
1
Así es como lo hago y le da un gran impulso al rendimiento a las consultas que involucran constantes.
WonderWorker
5

Para enumeraciones o constantes simples, una vista con una sola fila tiene un gran rendimiento y control de tiempo de compilación / seguimiento de dependencia (porque es un nombre de columna)

Consulte la publicación del blog de Jared Ko https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

crear 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  )
casa de los monos
fuente
3

Esta bien, veamos

Las constantes son valores inmutables que se conocen en el momento de la compilación y no cambian durante la vida del programa.

eso significa que nunca puede tener una constante en SQL Server

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

el valor acaba de cambiar

SQLMenace
fuente
1

Dado que no hay soporte integrado para constantes, mi solución es muy simple.

Dado que esto no es compatible:

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

Simplemente lo convertiría a

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

Obviamente, esto se basa en que todo (el valor sin espacio final y el comentario) sea único. Cambiarlo es posible con una búsqueda y reemplazo global.

Gert-Jan
fuente
Un problema es que solo está disponible localmente
Bernardo Dal Corno
0

No existe tal cosa como "crear una constante" en la literatura de bases de datos. Las constantes existen tal como son y, a menudo, se las llama valores. Se puede declarar una variable y asignarle un valor (constante). Desde un punto de vista escolástico:

DECLARE @two INT
SET @two = 2

Aquí @two es una variable y 2 es un valor / constante.

Greg Hurlman
fuente
Pruebe también las respuestas de Michal D. y John Nilsson para aumentar el rendimiento.
WonderWorker
Los literales son constantes por definición. El carácter ascii / unicode (según el editor) 2se traduce a un valor binario cuando se asigna en el "tiempo de compilación". El valor real codificado depende del tipo de datos al que se está asignando (int, char, ...).
samis
-1

La mejor respuesta es de SQLMenace de acuerdo con el requisito si se trata de crear una constante temporal para su uso dentro de los scripts, es decir, en múltiples sentencias / lotes de GO.

Simplemente cree el procedimiento en tempdb, entonces no tendrá ningún impacto en la base de datos de destino.

Un ejemplo práctico de esto es un script de creación de base de datos que escribe un valor de control al final del script que contiene la versión del esquema lógico. En la parte superior del archivo hay algunos comentarios con el historial de cambios, etc. Pero en la práctica, la mayoría de los desarrolladores se olvidarán de desplazarse hacia abajo y actualizar la versión del esquema en la parte inferior del archivo.

El uso del código anterior permite definir una constante de versión de esquema visible en la parte superior antes de que el script de la base de datos (copiado de la función de generar scripts de SSMS) cree la base de datos pero se use al final. Esto está justo en la cara del desarrollador junto al historial de cambios y otros comentarios, por lo que es muy probable que lo actualicen.

Por ejemplo:

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
fuente