Existe uma maneira de tornar uma variável TSQL constante?

88

Existe uma maneira de tornar uma variável TSQL constante?

TheEmirOfGroofunkistan
fonte

Respostas:

60

Não, mas você pode criar uma função e codificá-la lá e usá-la.

Aqui está um exemplo:

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

SELECT dbo.fnConstant()
SQLMenace
fonte
13
WITH SCHEMABINDING deve transformar isso em uma constante 'real' (um requisito para uma UDF ser vista como determinística em SQL). Ou seja, ele deve acabar sendo armazenado em cache. Ainda assim, +1.
Jonathan Dickinson
essa resposta é boa, só por curiosidade, as colunas da tabela em sqlserver podem fazer referência a uma função como um valor padrão. não consegui fazer isso funcionar
Ab Bennett,
1
@JonathanDickinson Para ser claro, sua sugestão é usar WITH SCHEMABINDINGna CREATE FUNCTIONinstrução (ao contrário de em um procedimento armazenado que pode estar chamando a função) - está certo?
Holistic Developer
1
Sim, na função. WITH SCHEMABINDING permite que o SQL inline "funções com valor de tabela embutidas" - portanto, também precisa estar neste formato: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . O analisador de consulta não embutirá nada sem SCHEMABINDING ou nada com BEGIN.
Jonathan Dickinson
Implicações do uso de UDFs não determinísticos: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
Ochoto
28

Uma solução oferecida por Jared Ko é usar pseudo-constantes .

Conforme explicado em SQL Server: variáveis, parâmetros ou literais? Ou… constantes? :

As pseudo-constantes não são variáveis ​​ou parâmetros. Em vez disso, eles são simplesmente visualizações com uma linha e colunas suficientes para suportar suas constantes. Com essas regras simples, o SQL Engine ignora completamente o valor da visualização, mas ainda cria um plano de execução com base em seu valor. O plano de execução nem mostra uma junção com a vista!

Crie assim:

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]

Então use assim:

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

Ou assim:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
mbobka
fonte
1
Esta é uma solução MUITO melhor do que a resposta aceita. Inicialmente, descemos a rota da função escalar e ela tem um desempenho péssimo. Muito melhor é esta resposta e o link acima para o artigo de Jared Ko.
David Coster
No entanto, adicionar WITH SCHEMABINDING a uma função escalar parece melhorar seu desempenho significativamente.
David Coster
O link está morto agora.
Matthieu Cormier
1
@MatthieuCormier: Eu atualizei o link, embora pareça que o MSDN adicionou um redirecionamento do URL antigo para o novo de qualquer maneira.
Ilmari Karonen
23

Minha solução alternativa para constans ausentes é fornecer dicas sobre o valor para o otimizador.

DECLARE @Constant INT = 123;

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

Isso diz ao compilador de consulta para tratar a variável como se fosse uma constante ao criar o plano de execução. O lado ruim é que você precisa definir o valor duas vezes.

John Nilsson
fonte
3
Ajuda, mas também anula o propósito de uma definição única.
MikeJRamsey56
9

Não, mas as boas e velhas convenções de nomenclatura devem ser usadas.

declare @MY_VALUE as int
jason saldo
fonte
@VictorYarema porque às vezes convenção é tudo que você precisa. E porque às vezes você não tem outra boa escolha. Agora, deixando isso de lado, a resposta do SQLMenace parece melhor, eu concordo com você. Mesmo assim, o nome das funções deve seguir a convenção para constantes, IMO. Deve ser nomeado FN_CONSTANT(). Assim fica claro o que está acontecendo.
tfrascaroli
Isso por si só não ajudará quando você deseja o benefício de desempenho. Experimente as respostas de Michal D. e John Nilsson para aumentar o desempenho também.
WonderWorker de
8

Não há suporte embutido para constantes no T-SQL. Você poderia usar a abordagem do SQLMenace para simulá-lo (embora você nunca possa ter certeza se alguém sobrescreveu a função para retornar outra coisa ...), ou possivelmente escrever uma tabela contendo constantes, como sugerido aqui . Talvez escreva um gatilho que reverta quaisquer alterações na ConstantValuecoluna?

Sören Kuklau
fonte
7

Antes de usar uma função SQL, execute o seguinte script para ver as diferenças de desempenho:

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
fonte
4
Isso é bastante antigo, mas para referência aqui está o resultado quando executado no meu servidor: | 2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
z00l
2
Em um laptop dev, com duas funções adicionais sem vinculação de esquema. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
monkeyhouse
Para comparação, uma instrução select simples levou 4110ms, onde as instruções select alternaram entre select top 1 @m = cv_val from code_values where cv_id = 'C101' e mesmo ... 'C201' onde code_values ​​é a tabela de dicionário com 250 vars, existiam todos no SQL-Server 2016
monkeyhouse
6

Se estiver interessado em obter o plano de execução ideal para um valor na variável, você pode usar um código sql dinâmico. Isso torna a variável constante.

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)
Michal D.
fonte
1
É assim que eu faço e dá um grande impulso de desempenho para consultas que envolvem constantes.
WonderWorker
5

Para enums ou constantes simples, uma visualização com uma única linha tem ótimo desempenho e verificação de tempo de compilação / rastreamento de dependência (porque é um nome de coluna)

Consulte a postagem do blog de Jared Ko https://blogs.msdn.microsoft.com/sql_server_apethoscope_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

crie a 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]

use a vista

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

Ok vamos ver

Constantes são valores imutáveis ​​que são conhecidos em tempo de compilação e não mudam durante a vida do programa

isso significa que você nunca pode ter uma constante no SQL Server

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

o valor acabou de mudar

SQLMenace
fonte
1

Como não há suporte para constantes, minha solução é muito simples.

Como isso não é compatível:

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

Eu simplesmente converteria para

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

Obviamente, isso depende de que tudo (o valor sem espaço à direita e o comentário) seja único. Alterá-lo é possível com uma pesquisa e substituição globais.

Gert-Jan
fonte
Um problema é que está disponível apenas localmente
Bernardo Dal Corno
0

Não existe algo como "criar uma constante" na literatura de banco de dados. As constantes existem como são e freqüentemente são chamadas de valores. Pode-se declarar uma variável e atribuir um valor (constante) a ela. Do ponto de vista escolar:

DECLARE @two INT
SET @two = 2

Aqui @two é uma variável e 2 é um valor / constante.

Greg Hurlman
fonte
Experimente as respostas de Michal D. e John Nilsson para aumentar o desempenho também.
WonderWorker
Literais são constantes por definição. O caractere ascii / unicode (dependendo do editor) 2é traduzido em um valor binário quando atribuído em "tempo de compilação". O valor real codificado depende do tipo de dados ao qual está sendo atribuído (int, char, ...).
samis
-1

A melhor resposta é do SQLMenace de acordo com o requisito se for para criar uma constante temporária para uso em scripts, ou seja, em várias instruções / lotes GO.

Basta criar o procedimento no tempdb e você não terá nenhum impacto no banco de dados de destino.

Um exemplo prático disso é um script de criação de banco de dados que grava um valor de controle no final do script contendo a versão do esquema lógico. Na parte superior do arquivo estão alguns comentários com o histórico de alterações, etc ... Mas, na prática, a maioria dos desenvolvedores se esquecerá de rolar para baixo e atualizar a versão do esquema na parte inferior do arquivo.

Usar o código acima permite que uma constante de versão de esquema visível seja definida na parte superior antes que o script de banco de dados (copiado do recurso de geração de scripts do SSMS) crie o banco de dados, mas usado no final. Isso está bem na cara do desenvolvedor ao lado do histórico de alterações e outros comentários, então é muito provável que eles o atualizem.

Por exemplo:

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
fonte