Membuat konstanta tingkat basis data (enumerasi) tanpa menggunakan CLR?

9

Saya memiliki beberapa objek SQL yang perlu mengambil tindakan alternatif berdasarkan kondisi permintaan yang diinginkan. Apakah ada cara untuk membuat konstanta tingkat basis data (enumerasi) yang dapat diteruskan ke prosedur tersimpan, fungsi bernilai tabel, dan digunakan dalam kueri (tanpa menggunakan CLR)?

CREATE PROCEDURE dbo.DoSomeWork(@param1 INTEGER, ..., @EnumValue myEnumType)  AS ...;

dan kemudian menggunakannya:

EXEC doSomeWork 85, ..., (myEnumType.EnumValue1 + myEnumType.EnumValue2);

Di mana myEnumTypeakan menyimpan beberapa nilai enumerasi.

Dalam prosedur saya akan dapat menggunakan @EnumValuedan mengujinya terhadap nilai-nilai myEnumTypeuntuk melakukan pekerjaan yang diperlukan. Saya akan membuat nilai myEnumTypebitmask untuk kasus yang saya pertimbangkan.

Untuk contoh sederhana, pertimbangkan proses mahal yang membutuhkan dataset besar dan menguranginya menjadi dataset yang lebih kecil namun masih sangat besar. Dalam proses ini Anda perlu melakukan beberapa penyesuaian di tengah proses yang akan mempengaruhi hasilnya. Katakanlah ini adalah filter untuk (atau menentang) beberapa jenis catatan berdasarkan pada beberapa status dari perhitungan antara dalam pengurangan. The @EnumValuejenis myEnumTypedapat digunakan untuk menguji untuk ini

SELECT   ...
FROM     ...
WHERE       (@EnumValue & myEnumType.EnumValue1 = myEnumType.EnumValue1 AND ...)
        OR  (@EnumValue & myEnumType.EnumValue2 = myEnumType.EnumValue2 AND ...)
        OR  ...

Apakah konstanta tingkat basis data semacam ini dimungkinkan dalam SQL Server tanpa menggunakan CLR?

Saya mencari enumerasi tingkat database atau seperangkat konstanta yang dapat dilewatkan sebagai parameter untuk prosedur tersimpan, fungsi, dan sebagainya.

Edmund
sumber

Jawaban:

9

Anda bisa membuat tipe enumerasi di SQL Server menggunakan XML Schema.

Misalnya Warna.

create xml schema collection ColorsEnum as '
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Color">
        <xs:simpleType>
            <xs:restriction base="xs:string"> 
                <xs:enumeration value="Red"/>
                <xs:enumeration value="Green"/>
                <xs:enumeration value="Blue"/>
                <xs:enumeration value="Yellow"/>
            </xs:restriction> 
        </xs:simpleType>
    </xs:element>
</xs:schema>';

Itu memungkinkan Anda untuk menggunakan variabel atau parameter dari tipe tersebut xml(dbo.ColorsEnum).

declare @Colors xml(dbo.ColorsEnum);
set @Colors = '<Color>Red</Color><Color>Green</Color>'

Jika Anda mencoba menambahkan sesuatu yang bukan warna

set @Colors = '<Color>Red</Color><Color>Ferrari</Color>';

Anda mendapatkan kesalahan.

Msg 6926, Level 16, State 1, Line 43
XML Validation: Invalid simple type value: 'Ferrari'. Location: /*:Color[2]

Membangun XML seperti itu bisa sedikit membosankan sehingga Anda bisa misalnya membuat tampilan pembantu yang juga memegang nilai yang diizinkan.

create view dbo.ColorsConst as
select cast('<Color>Red</Color>' as varchar(100)) as Red,
       cast('<Color>Green</Color>' as varchar(100)) as Green,
       cast('<Color>Blue</Color>' as varchar(100)) as Blue,
       cast('<Color>Yellow</Color>' as varchar(100)) as Yellow;

Dan gunakan seperti ini untuk membuat enumration.

set @Colors = (select Red+Blue+Green from dbo.ColorsConst);

Jika Anda ingin membuat tampilan secara dinamis dari Skema XML, Anda dapat mengekstraksi warna dengan kueri ini.

select C.Name
from (select xml_schema_namespace('dbo','ColorsEnum')) as T(X)
  cross apply T.X.nodes('//*:enumeration') as E(X)
  cross apply (select E.X.value('@value', 'varchar(100)')) as C(Name);

Pencacahan tentu saja dapat juga digunakan sebagai parameter untuk fungsi dan prosedur.

create function dbo.ColorsToString(@Colors xml(ColorsEnum))
returns varchar(100)
as
begin
  declare @T table(Color varchar(100));

  insert into @T(Color)
  select C.X.value('.', 'varchar(100)')
  from @Colors.nodes('Color') as C(X);

  return stuff((select ','+T.Color
                from @T as T
                for xml path('')), 1, 1, '');
end
create procedure dbo.GetColors
  @Colors xml(ColorsEnum)
as
select C.X.value('.', 'varchar(100)') as Color
from @Colors.nodes('Color') as C(X);
declare @Colors xml(ColorsEnum) = '
<Color>Red</Color>
<Color>Blue</Color>
';

select dbo.ColorsToString(@Colors);

set @Colors = (select Red+Blue+Green from dbo.ColorsConst);
exec dbo.GetColors @Colors;
Mikael Eriksson
sumber
6

Karena Anda tampaknya menggunakan SQL Server 2016, saya ingin membuang opsi ' mungkin ' lainnya - SESSION_CONTEXT.

Artikel Leonard Lobel, Berbagi Negara di SQL Server 2016 denganSESSION_CONTEXT memiliki beberapa informasi yang sangat baik tentang fungsi baru ini di SQL Server 2016.

Meringkas beberapa poin utama:

Jika Anda pernah ingin berbagi status sesi di semua prosedur dan kumpulan yang tersimpan sepanjang masa koneksi database, Anda akan menyukainya SESSION_CONTEXT. Ketika Anda tersambung ke SQL Server 2016, Anda mendapatkan kamus stateful, atau apa yang sering disebut sebagai tas negara, tempat di mana Anda dapat menyimpan nilai, seperti string dan angka, dan kemudian mengambilnya dengan kunci yang Anda tetapkan. Dalam kasus SESSION_CONTEXT, kuncinya adalah string apa pun, dan nilainya adalah sql_variant, artinya dapat mengakomodasi berbagai jenis.

Setelah Anda menyimpan sesuatu SESSION_CONTEXT, itu tetap di sana sampai koneksi ditutup. Itu tidak disimpan dalam tabel apa pun di database, itu hanya tinggal di memori selama koneksi tetap hidup. Dan setiap dan semua kode T-SQL yang berjalan di dalam prosedur tersimpan, pemicu, fungsi, atau apa pun, dapat membagikan apa pun yang Anda dorong SESSION_CONTEXT.

Hal terdekat yang kami miliki hingga saat ini adalah CONTEXT_INFO, yang memungkinkan Anda untuk menyimpan dan berbagi nilai biner tunggal hingga 128 byte, yang jauh lebih fleksibel daripada kamus yang Anda dapatkan SESSION_CONTEXT, yang mendukung banyak nilai dari data yang berbeda jenis.

SESSION_CONTEXTmudah digunakan, cukup panggil sp_set_session_context untuk menyimpan nilai dengan kunci yang diinginkan. Ketika Anda melakukannya, Anda memberikan kunci dan nilai tentu saja, tetapi Anda juga dapat mengatur parameter read_only menjadi true. Ini mengunci nilai dalam konteks sesi, sehingga tidak dapat diubah selama sisa koneksi. Jadi, misalnya, mudah bagi aplikasi klien untuk memanggil prosedur tersimpan ini untuk menetapkan beberapa nilai konteks sesi setelah membuat koneksi database. Jika aplikasi menetapkan parameter read_only ketika melakukan ini, maka prosedur tersimpan dan kode T-SQL lain yang kemudian dieksekusi di server hanya dapat membaca nilai, mereka tidak dapat mengubah apa yang ditetapkan oleh aplikasi yang berjalan pada klien.

Sebagai ujian, saya membuat pemicu masuk server yang menetapkan beberapa CONTEXT_SESSIONinformasi - salah SESSION_CONTEXTsatunya ditetapkan @read_only.

DROP TRIGGER IF EXISTS [InitializeSessionContext] ON ALL SERVER
GO
CREATE TRIGGER InitializeSessionContext ON ALL SERVER
FOR LOGON AS

BEGIN

    --Initialize context information that can be altered in the session
    EXEC sp_set_session_context @key = N'UsRegion'
        ,@value = N'Southeast'

    --Initialize context information that cannot be altered in the session
    EXEC sp_set_session_context @key = N'CannotChange'
        ,@value = N'CannotChangeThisValue'
        ,@read_only = 1

END;

Saya masuk sebagai pengguna yang sama sekali baru dan dapat mengekstraksi SESSION_CONTEXTinformasi:

DECLARE @UsRegion varchar(20)
SET @UsRegion = CONVERT(varchar(20), SESSION_CONTEXT(N'UsRegion'))
SELECT DoThat = @UsRegion

DECLARE @CannotChange varchar(20)
SET @CannotChange = CONVERT(varchar(20), SESSION_CONTEXT(N'CannotChange'))
SELECT DoThat = @CannotChange

Saya bahkan berusaha mengubah informasi konteks 'read_only':

EXEC sp_set_session_context @key = N'CannotChange'
    ,@value = N'CannotChangeThisValue'

dan menerima kesalahan:

Msg 15664, Level 16, Status 1, Prosedur sp_set_session_context, Line 1 [Batch Start Line 8] Tidak dapat mengatur kunci 'CannotChange' dalam konteks sesi. Kunci telah ditetapkan sebagai read_only untuk sesi ini.

Catatan penting tentang pemicu masuk ( dari pos ini )!

Pemicu masuk secara efektif dapat mencegah koneksi yang berhasil ke Mesin Database untuk semua pengguna, termasuk anggota peran server tetap sysadmin. Ketika pemicu masuk mencegah koneksi, anggota peran server tetap sysadmin dapat terhubung dengan menggunakan koneksi administrator khusus, atau dengan memulai Mesin Database dalam mode konfigurasi minimal (-f)


Salah satu kelemahan potensial adalah bahwa ini mengisi konteks konteks sesi luas (tidak per database). Pada titik ini, satu-satunya pilihan yang dapat saya pikirkan adalah:

  1. Namai Session_Contextpasangan nama-nilai Anda dengan mengawalinya dengan nama basis data agar tidak menyebabkan tabrakan untuk nama jenis yang sama di basis data lain. Ini tidak memecahkan masalah pra-mendefinisikan SEMUA Session_Contextnilai-nama untuk semua pengguna.
  2. Ketika pemicu login menyala, Anda memiliki akses ke EventData(xml) yang bisa Anda gunakan untuk mengekstrak pengguna login dan berdasarkan itu, Anda bisa membuat Session_Contextpasangan nilai-nama tertentu .
Scott Hodgin
sumber
4

Dalam SQL Server, tidak (meskipun saya ingat membuat konstanta dalam paket Oracle kembali pada tahun 1998 dan agak ketinggalan memilikinya dalam SQL Server).

DAN, saya baru saja menguji dan menemukan bahwa Anda bahkan tidak dapat melakukan ini dengan SQLCLR, setidaknya tidak dalam arti bahwa itu akan berhasil dalam semua kasus. Hold up adalah pembatasan pada parameter Stored Procedure. Tampaknya Anda tidak dapat memiliki a .atau ::dalam nama parameter. Saya mencoba:

EXEC MyStoredProc @ParamName = SchemaName.UdtName::[StaticField];

-- and:

DECLARE @Var = SchemaName.UdtName = 'something';
EXEC MyStoredProc @ParamName = @Var.[InstanceProperty];

Dalam kedua kasus itu bahkan tidak bisa melewati fase parsing (diverifikasi menggunakan SET PARSEONLY ON;) karena:

Msg 102, Level 15, Negara 1, Baris xxxxx
Sintaks salah dekat '.

Di sisi lain, kedua metode berhasil untuk parameter Fungsi yang Ditentukan Pengguna:

SELECT MyUDF(SchemaName.UdtName::[StaticField]);

-- and:

DECLARE @Var = SchemaName.UdtName = N'something';
SELECT MyUDF(@Var.[InstanceProperty]);

Jadi, hal terbaik yang dapat Anda lakukan adalah menggunakan SQLCLR untuk memiliki sesuatu yang bekerja langsung dengan UDF, TVFs, UDAs (saya berasumsi), dan kueri, dan kemudian menetapkan ke variabel lokal ketika perlu menggunakan dengan Stored Procedures:

DECLARE @VarInt = SchemaName.UdtName::[StaticField];
EXEC MyStoredProc @ParamName = @VarInt;

Ini adalah pendekatan yang saya ambil ketika ada kesempatan untuk memiliki nilai enum aktual (sebagai lawan dari nilai pencarian yang harus dalam tabel pencarian khusus untuk penggunaan / artinya).


Sehubungan dengan mencoba ini dengan User-Defined Function (UDF) Function untuk mengeluarkan nilai "konstan" / "enum", saya tidak bisa membuatnya berfungsi baik dalam hal melewatinya sebagai parameter Prosedur Tersimpan:

EXEC MyStoredProc @ParamName = FunctionName(N'something');

mengembalikan kesalahan "Sintaks salah", dengan SSMS menyorot segala sesuatu di dalam tanda kurung, bahkan jika saya mengganti string dengan angka, atau tanda kurung yang tepat jika tidak ada parameter untuk dilewatkan.

Solomon Rutzky
sumber