Czy tworzyć stałe na poziomie bazy danych (wyliczenia) bez korzystania z CLR?

9

Mam kilka obiektów SQL, które muszą podjąć alternatywne działania w zależności od pożądanego stanu żądania. Czy istnieje sposób tworzenia stałych na poziomie bazy danych (wyliczeń), które można przekazywać do procedur przechowywanych, funkcji o wartościach przechowywanych w tabeli i używać w zapytaniach (bez korzystania z CLR)?

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

a następnie użyj go:

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

Gdzie myEnumTypemieści się kilka wartości wyliczenia.

W procedurze byłbym w stanie użyć @EnumValuei przetestować go pod kątem wartości, myEnumTypeaby wykonać wymaganą pracę. Zrobiłbym wartości myEnumTypemaski bitowej dla rozważanego przypadku.

Jako prosty przykład rozważ kosztowny proces, który wymaga ogromnego zestawu danych i redukuje go do mniejszego, ale wciąż bardzo dużego zestawu danych. W tym procesie musisz wprowadzić pewne poprawki w trakcie tego procesu, które wpłyną na wynik. Powiedzmy, że jest to filtr dla (lub przeciw) niektórych typów rekordów w oparciu o pewien status pośrednich obliczeń w ramach redukcji. Do przetestowania tego można użyć @EnumValuetypumyEnumType

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

Czy tego rodzaju stałe na poziomie bazy danych są możliwe w SQL Server bez użycia CLR?

Szukam wyliczenia na poziomie bazy danych lub zestawu stałych, które można przekazać jako parametry do procedur składowanych, funkcji itd.

Edmund
źródło

Odpowiedzi:

9

Możesz utworzyć typ wyliczenia w SQL Server przy użyciu schematu XML.

Na przykład kolory.

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>';

Pozwala to na użycie zmiennej lub parametru tego typu xml(dbo.ColorsEnum).

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

Jeśli spróbujesz dodać coś, co nie jest kolorem

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

pojawia się błąd.

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

Konstruowanie takiego XML-a może być nieco żmudne, więc możesz na przykład stworzyć widok pomocnika, który będzie również zawierał dozwolone wartości.

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;

I użyj tego w ten sposób, aby stworzyć enumrację.

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

Jeśli chcesz dynamicznie utworzyć widok ze schematu XML, możesz wyodrębnić kolory za pomocą tego zapytania.

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);

Wyliczenie można oczywiście również wykorzystać jako parametry funkcji i procedur.

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
źródło
6

Ponieważ najwyraźniej używasz SQL Server 2016, chciałbym wyrzucić kolejną „ możliwą ” opcję - SESSION_CONTEXT.

Artykuł Leonarda Lobela „ Stan udostępniania w SQL Server 2016”SESSION_CONTEXT zawiera bardzo dobre informacje na temat tej nowej funkcji w SQL Server 2016.

Podsumowując niektóre kluczowe punkty:

Jeśli kiedykolwiek chciałeś współużytkować stan sesji we wszystkich procedurach przechowywanych i partiach przez cały okres połączenia z bazą danych, pokochasz SESSION_CONTEXT. Po nawiązaniu połączenia z programem SQL Server 2016 otrzymujesz słownik stanowy lub coś, co jest często określane jako torba stanu, miejsce, w którym możesz przechowywać wartości, takie jak ciągi i liczby, a następnie odzyskiwać je za pomocą przypisanego klucza. W przypadku SESSION_CONTEXTklucza kluczem jest dowolny ciąg znaków, a wartością jest zmienna sql_variant, co oznacza, że ​​może pomieścić różne typy.

Gdy coś zapiszesz SESSION_CONTEXT, pozostanie tam do momentu zamknięcia połączenia. Nie jest przechowywany w żadnej tabeli w bazie danych, po prostu żyje w pamięci, dopóki połączenie pozostaje aktywne. I każdy kod T-SQL, który działa w procedurach przechowywanych, wyzwalaczach, funkcjach lub czymkolwiek, może współdzielić to, w co się wpakuje SESSION_CONTEXT.

Do tej pory mieliśmy do czynienia z taką najbliższą rzeczą CONTEXT_INFO, która pozwala przechowywać i udostępniać pojedynczą wartość binarną o długości do 128 bajtów, która jest znacznie mniej elastyczna niż słownik, który masz SESSION_CONTEXT, który obsługuje wiele wartości różnych danych typy.

SESSION_CONTEXTjest łatwy w użyciu, wystarczy wywołać sp_set_session_context, aby zapisać wartość według pożądanego klucza. Gdy to zrobisz, podajesz oczywiście klucz i wartość, ale możesz również ustawić parametr read_only na true. Blokuje to wartość w kontekście sesji, dzięki czemu nie można jej zmienić przez resztę życia połączenia. Na przykład aplikacja kliencka może łatwo wywołać tę procedurę przechowywaną w celu ustawienia niektórych wartości kontekstu sesji zaraz po ustanowieniu połączenia z bazą danych. Jeśli aplikacja ustawi parametr read_only, gdy to zrobi, wówczas procedury składowane i inny kod T-SQL, który następnie wykonuje się na serwerze, mogą tylko odczytać wartość, nie mogą zmienić tego, co zostało ustawione przez aplikację uruchomioną na kliencie.

Jako test stworzyłem wyzwalacz logowania do serwera, który ustawia pewne CONTEXT_SESSIONinformacje - jedna z nich SESSION_CONTEXTzostała ustawiona na @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;

Zalogowałem się jako zupełnie nowy użytkownik i udało mi się wyodrębnić SESSION_CONTEXTinformacje:

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

Próbowałem nawet zmienić informacje kontekstowe „tylko do odczytu”:

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

i otrzymał błąd:

Msg 15664, poziom 16, stan 1, procedura sp_set_session_context, wiersz 1 [wiersz wsadowy 8] Nie można ustawić klucza „CannotChange” w kontekście sesji. Klucz został ustawiony jako tylko do odczytu dla tej sesji.

Ważna uwaga na temat wyzwalaczy logowania ( z tego postu )!

Wyzwalacz logowania może skutecznie uniemożliwić pomyślne połączenia z aparatem bazy danych wszystkim użytkownikom, w tym członkom stałej roli serwera sysadmin. Gdy wyzwalacz logowania uniemożliwia połączenia, członkowie stałej roli serwera sysadmin mogą łączyć się za pomocą dedykowanego połączenia administratora lub uruchamiając aparat bazy danych w trybie konfiguracji minimalnej (-f)


Jedną z potencjalnych wad jest to, że wypełnia to całą instancję kontekstu sesji (nie dla bazy danych). W tym momencie jedyne opcje, o których mogę myśleć, to:

  1. Nazwij Session_Contextpary nazwa-wartość, poprzedzając je nazwą bazy danych, aby nie spowodować kolizji dla nazwy tego samego typu w innej bazie danych. To nie rozwiązuje problemu wstępnego definiowania WSZYSTKICH Session_Contextwartości dla wszystkich użytkowników.
  2. Po uruchomieniu wyzwalacza logowania masz dostęp do EventData(xml), którego możesz użyć do wyodrębnienia loginu i na tej podstawie możesz utworzyć określone Session_Contextpary nazwa-wartość.
Scott Hodgin
źródło
4

W SQL Server nie (choć przypominam sobie tworzenie stałych w pakietach Oracle w 1998 roku i trochę mi ich brakowało w SQL Server).

I właśnie przetestowałem i stwierdziłem, że nie można tego zrobić nawet z SQLCLR, przynajmniej w tym sensie, że zadziałałoby we wszystkich przypadkach. Wstrzymanie to ograniczenia parametrów procedury składowanej. Wydaje się, że nie można mieć albo .czy ::w nazwie parametru. Próbowałem:

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

-- and:

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

W obu przypadkach nawet nie przeszedł on przez fazę analizy (zweryfikowany przy użyciu SET PARSEONLY ON;) z powodu:

Msg 102, poziom 15, stan 1, wiersz xxxxx
Niepoprawna składnia w pobliżu „.”.

Z drugiej strony obie metody działały dla parametrów funkcji zdefiniowanej przez użytkownika:

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

-- and:

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

Zatem najlepsze, co możesz naprawdę zrobić, to użyć SQLCLR, aby mieć coś, co działa bezpośrednio z UDF, TVF, UDA (jak zakładam) i zapytaniami, a następnie przypisać do zmiennych lokalnych, gdy trzeba użyć procedur przechowywanych:

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

Takie podejście zastosowałem, gdy istnieje możliwość uzyskania rzeczywistej wartości wyliczeniowej (w przeciwieństwie do wartości odnośnika, która powinna znajdować się w tabeli odnośników specyficznej dla jej użycia / znaczenia).


Jeśli chodzi o próbę zrobienia tego za pomocą funkcji zdefiniowanej przez użytkownika (UDF) w celu wyplucia wartości „stałej” / „wyliczenia”, nie mogłem sprawić, by działała pod względem przekazania jej jako parametru procedury składowanej:

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

zwraca błąd „Niepoprawna składnia”, przy czym SSMS podświetla wszystko w nawiasach, nawet jeśli zastąpię ciąg liczbą lub prawy nawias, jeśli nie ma parametru do przekazania.

Solomon Rutzky
źródło