Apakah ada cara untuk membuat variabel TSQL konstan?

89

Apakah ada cara untuk membuat variabel TSQL konstan?

TheEmirOfGroofunkistan
sumber

Jawaban:

60

Tidak, tetapi Anda dapat membuat fungsi dan membuat kode keras di sana dan menggunakannya.

Berikut ini contohnya:

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

SELECT dbo.fnConstant()
SQLMenace
sumber
13
WITH SCHEMABINDING harus mengubahnya menjadi konstanta 'nyata' (persyaratan untuk UDF untuk dilihat sebagai deterministik dalam SQL). Yaitu harus mendarat di cache. Tetap saja, +1.
Jonathan Dickinson
jawaban ini bagus, hanya penasaran dapat kolom tabel di sqlserver mereferensikan fungsi sebagai nilai default. saya tidak bisa mendapatkan ini untuk bekerja
Ab Bennett
1
@JonathanDickinson Agar jelas, saran Anda adalah untuk digunakan WITH SCHEMABINDINGdalam CREATE FUNCTIONpernyataan (sebagai lawan dalam prosedur tersimpan yang mungkin memanggil fungsi) - apakah itu benar?
Pengembang Holistik
1
Ya, dalam fungsinya. DENGAN SCHEMABINDING memungkinkan SQL untuk menyebariskan "fungsi nilai tabel sebaris" - jadi SQL juga harus dalam bentuk ini: gist.github.com/jcdickinson/61a38dedb84b35251da301b128535ceb . Penganalisis kueri tidak akan menyebariskan apa pun tanpa SCHEMABINDING atau apa pun dengan BEGIN.
Jonathan Dickinson
Implikasi penggunaan UDF non deterministik: docs.microsoft.com/es-es/archive/blogs/sqlprogrammability/…
Ochoto
28

Salah satu solusi yang ditawarkan Jared Ko adalah dengan menggunakan konstanta semu .

Seperti yang dijelaskan di SQL Server: Variabel, Parameter, atau Literal? Atau… Konstanta? :

Pseudo-Constants bukanlah variabel atau parameter. Sebaliknya, mereka hanya dilihat dengan satu baris, dan kolom yang cukup untuk mendukung konstanta Anda. Dengan aturan sederhana ini, SQL Engine sepenuhnya mengabaikan nilai tampilan tetapi masih membuat rencana eksekusi berdasarkan nilainya. Rencana eksekusi bahkan tidak menunjukkan gabungan tampilan!

Buat seperti ini:

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]

Kemudian gunakan seperti ini:

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

Atau seperti ini:

SELECT h.*
FROM Sales.SalesOrderHeader h
WHERE h.ShipMethodID = (SELECT TOP 1 [OVERNIGHT J-FAST] FROM ShipMethod.ShipMethodID)
mbobka.dll
sumber
1
Ini adalah solusi yang JAUH lebih baik daripada jawaban yang diterima. Awalnya kami mengikuti rute fungsi skalar dan memiliki kinerja yang buruk. Jauh lebih baik jawaban ini dan tautan di atas ke artikel Jared Ko.
David Coster
Namun, menambahkan WITH SCHEMABINDING ke fungsi skalar tampaknya meningkatkan kinerjanya secara signifikan.
David Coster
Tautannya sudah mati sekarang.
Matthieu Cormier
1
@MatthieuCormier: Saya telah memperbarui tautan, meskipun tampaknya MSDN telah menambahkan pengalihan dari URL lama ke yang baru.
Ilmari Karonen
23

Solusi saya untuk kehilangan konstanta adalah memberikan petunjuk tentang nilai kepada pengoptimal.

DECLARE @Constant INT = 123;

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

Ini memberi tahu compiler kueri untuk memperlakukan variabel seolah-olah itu adalah konstanta saat membuat rencana eksekusi. Sisi negatifnya adalah Anda harus mendefinisikan nilainya dua kali.

John Nilsson
sumber
3
Ini membantu tetapi juga mengalahkan tujuan definisi tunggal.
MikeJRamsey56
10

Tidak, tapi konvensi penamaan lama yang baik harus digunakan.

declare @MY_VALUE as int
jason saldo
sumber
@VictorYarema karena terkadang hanya konvensi yang Anda butuhkan. Dan karena terkadang Anda tidak punya pilihan lain yang baik. Sekarang, selain itu, jawaban SQLMenace terlihat lebih baik, saya setuju dengan Anda. Meski begitu, nama fungsi harus mengikuti konvensi konstanta, IMO. Ini harus diberi nama FN_CONSTANT(). Dengan begitu, jelas apa yang dilakukannya.
tfrascaroli
Ini saja tidak akan membantu bila Anda menginginkan manfaat kinerja. Coba juga jawaban Michal D. dan John Nilsson untuk peningkatan kinerja.
WonderWorker
8

Tidak ada dukungan bawaan untuk konstanta di T-SQL. Anda bisa menggunakan pendekatan SQLMenace untuk mensimulasikannya (meskipun Anda tidak pernah bisa yakin apakah orang lain telah menimpa fungsi untuk mengembalikan sesuatu yang lain…), atau mungkin menulis tabel yang berisi konstanta, seperti yang disarankan di sini . Mungkin menulis pemicu yang mengembalikan setiap perubahan ke ConstantValuekolom?

Sören Kuklau
sumber
7

Sebelum menggunakan fungsi SQL, jalankan skrip berikut untuk melihat perbedaan kinerja:

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
sumber
4
Ini sudah cukup lama, tetapi untuk referensi inilah hasilnya saat dijalankan di server saya: | 2760ms elapsed, using function| 2300ms elapsed, using local variable| 2286ms elapsed, using hard coded values|
z00l
2
Di laptop pengembang, dengan dua fungsi tambahan tanpa pengikatan skema. 5570 elapsed, using function | 406 elapsed, using local variable| 383 elapsed, using hard coded values| 3893 elapsed, using function without schemabinding
monkeyhouse
Sebagai perbandingan, pernyataan pilih sederhana membutuhkan waktu 4110 md di mana pernyataan pilih bergantian antara select top 1 @m = cv_val from code_values where cv_id = 'C101' dan sama di ... 'C201' mana code_values ​​adalah tabel kamus dengan 250 vars, semuanya ada di SQL-Server 2016
monkeyhouse
6

Jika Anda tertarik untuk mendapatkan rencana eksekusi yang optimal untuk nilai dalam variabel, Anda dapat menggunakan kode sql dinamis. Itu membuat variabel konstan.

DECLARE @var varchar(100) = 'some text'
DECLARE @sql varchar(MAX)
SET @sql = 'SELECT * FROM table WHERE col = '''+@var+''''
EXEC (@sql)
Michal D.
sumber
1
Ini adalah cara saya melakukannya dan ini memberikan peningkatan kinerja yang sangat besar pada kueri yang melibatkan konstanta.
WonderWorker
5

Untuk enum atau konstanta sederhana, tampilan dengan satu baris memiliki performa yang bagus dan pemeriksaan waktu kompilasi / pelacakan ketergantungan (karena itu adalah nama kolom)

Lihat entri blog Jared Ko https://blogs.msdn.microsoft.com/sql_server_appendix_z/2013/09/16/sql-server-variables-parameters-or-literals-or-constants/

buat tampilan

 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]

gunakan tampilan

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

Oke, mari kita lihat

Konstanta adalah nilai yang tidak dapat diubah yang diketahui pada waktu kompilasi dan tidak berubah selama masa pakai program

itu berarti Anda tidak akan pernah memiliki konstanta di SQL Server

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

nilainya baru saja berubah

SQLMenace
sumber
1

Karena tidak ada build yang mendukung konstanta, solusi saya sangat sederhana.

Karena ini tidak didukung:

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

Saya hanya akan mengubahnya menjadi

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

Jelas, ini bergantung pada semuanya (nilai tanpa spasi dan komentar) agar unik. Mengubahnya dimungkinkan dengan pencarian dan penggantian global.

Gert-Jan
sumber
Satu masalah adalah bahwa itu hanya tersedia secara lokal
Bernardo Dal Corno
0

Tidak ada hal seperti "membuat konstanta" dalam literatur database. Konstanta ada sebagaimana adanya dan sering disebut nilai. Seseorang dapat mendeklarasikan sebuah variabel dan menetapkan nilai (konstanta) padanya. Dari pandangan skolastik:

DECLARE @two INT
SET @two = 2

Di sini @two adalah variabel dan 2 adalah nilai / konstanta.

Greg Hurlman
sumber
Coba juga jawaban Michal D. dan John Nilsson untuk meningkatkan kinerja.
WonderWorker
Literal konstan menurut definisi. Karakter ascii / unicode (bergantung pada editor) 2diterjemahkan ke dalam nilai biner saat ditetapkan pada "waktu kompilasi". Nilai sebenarnya yang dikodekan bergantung pada tipe data yang ditugaskan (int, char, ...).
samis
-1

Jawaban terbaik adalah dari SQLMenace sesuai dengan persyaratan jika itu adalah untuk membuat konstanta sementara untuk digunakan dalam skrip, yaitu di beberapa pernyataan / batch GO.

Buat saja prosedur di tempdb maka Anda tidak berdampak pada database target.

Salah satu contoh praktisnya adalah skrip buat basis data yang menulis nilai kontrol di akhir skrip yang berisi versi skema logis. Di bagian atas file terdapat beberapa komentar dengan riwayat perubahan, dll. Tetapi dalam praktiknya sebagian besar pengembang akan lupa untuk menggulir ke bawah dan memperbarui versi skema di bagian bawah file.

Menggunakan kode di atas memungkinkan konstanta versi skema yang terlihat untuk didefinisikan di atas sebelum skrip database (disalin dari fitur skrip pembuatan SSMS) membuat database tetapi digunakan di bagian akhir. Ini tepat di hadapan pengembang di samping riwayat perubahan dan komentar lain, jadi mereka sangat mungkin untuk memperbaruinya.

Sebagai contoh:

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
sumber