Konstanten (Aufzählungen) auf Datenbankebene ohne Verwendung von CLR erstellen?

9

Ich habe mehrere SQL-Objekte, die basierend auf dem gewünschten Status der Anforderung alternative Aktionen ausführen müssen. Gibt es eine Möglichkeit, Konstanten (Aufzählungen) auf Datenbankebene zu erstellen, die an gespeicherte Prozeduren und Funktionen mit Tabellenwerten übergeben und in Abfragen verwendet werden können (ohne Verwendung von CLR)?

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

und dann benutze es:

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

Wo myEnumTypewürde ein paar Aufzählungswerte halten.

In der Prozedur könnte ich es verwenden @EnumValueund gegen Werte testen myEnumType, um die erforderliche Arbeit zu erledigen. Ich würde die Werte myEnumTypeeiner Bitmaske für den Fall machen, den ich in Betracht ziehe.

Stellen Sie sich als einfaches Beispiel einen teuren Prozess vor, bei dem ein großer Datensatz auf einen kleineren, aber immer noch sehr großen Datensatz reduziert wird. In diesem Prozess müssen Sie in der Mitte dieses Prozesses einige Anpassungen vornehmen, die sich auf das Ergebnis auswirken. Angenommen, dies ist ein Filter für (oder gegen) einige Arten von Datensätzen, basierend auf dem Status einer Zwischenberechnung innerhalb der Reduzierung. Der @EnumValueTyp myEnumTypekönnte verwendet werden, um dies zu testen

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

Sind solche Konstanten auf Datenbankebene in SQL Server ohne die Verwendung von CLR möglich?

Ich suche eine Aufzählung auf Datenbankebene oder einen Satz von Konstanten, die als Parameter an gespeicherte Prozeduren, Funktionen usw. übergeben werden können.

Edmund
quelle

Antworten:

9

Sie können einen Aufzählungstyp in SQL Server mithilfe eines XML-Schemas erstellen.

Zum Beispiel Farben.

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

Auf diese Weise können Sie eine Variable oder einen Parameter des Typs verwenden xml(dbo.ColorsEnum).

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

Wenn Sie versuchen, etwas hinzuzufügen, das keine Farbe ist

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

Sie erhalten eine Fehlermeldung.

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

Das Erstellen einer solchen XML kann etwas mühsam sein, sodass Sie beispielsweise eine Hilfsansicht erstellen können, die auch die zulässigen Werte enthält.

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;

Und verwenden Sie es so, um die Aufzählung zu erstellen.

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

Wenn Sie die Ansicht dynamisch aus dem XML-Schema erstellen möchten, können Sie die Farben mit dieser Abfrage extrahieren.

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

Die Aufzählung kann natürlich auch als Parameter für Funktionen und Prozeduren verwendet werden.

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
quelle
6

Da Sie anscheinend SQL Server 2016 verwenden, möchte ich eine weitere " mögliche " Option streichen - SESSION_CONTEXT.

Leonard Lobels Artikel " Status in SQL Server 2016 mitSESSION_CONTEXT teilen" enthält einige sehr gute Informationen zu dieser neuen Funktionalität in SQL Server 2016.

Einige wichtige Punkte zusammenfassen:

Wenn Sie schon immer den Sitzungsstatus für alle gespeicherten Prozeduren und Stapel während der gesamten Lebensdauer einer Datenbankverbindung freigeben wollten, werden Sie begeistert sein SESSION_CONTEXT. Wenn Sie eine Verbindung zu SQL Server 2016 herstellen, erhalten Sie ein Stateful-Wörterbuch oder eine so genannte State-Bag-Datei, in der Sie Werte wie Zeichenfolgen und Zahlen speichern und dann über einen von Ihnen zugewiesenen Schlüssel abrufen können. Im Fall von SESSION_CONTEXTist der Schlüssel eine beliebige Zeichenfolge, und der Wert ist eine sql_variant, was bedeutet, dass er eine Vielzahl von Typen aufnehmen kann.

Sobald Sie etwas gespeichert haben SESSION_CONTEXT, bleibt es dort, bis die Verbindung geschlossen wird. Es wird in keiner Tabelle in der Datenbank gespeichert, sondern lebt nur im Speicher, solange die Verbindung besteht. Und jeder T-SQL-Code, der in gespeicherten Prozeduren, Triggern, Funktionen oder was auch immer ausgeführt wird, kann alles teilen, worauf Sie sich einlassen SESSION_CONTEXT.

Das Nächste, was wir bisher hatten CONTEXT_INFO, war das Speichern und Freigeben eines einzelnen Binärwerts mit einer Länge von bis zu 128 Byte. Dies ist weitaus weniger flexibel als das Wörterbuch, mit SESSION_CONTEXTdem Sie mehrere Werte verschiedener Daten unterstützen Typen.

SESSION_CONTEXTist einfach zu bedienen, rufen Sie einfach sp_set_session_context auf, um den Wert mit einem gewünschten Schlüssel zu speichern. Wenn Sie dies tun, geben Sie natürlich den Schlüssel und den Wert an, aber Sie können auch den Parameter read_only auf true setzen. Dadurch wird der Wert im Sitzungskontext gesperrt, sodass er für den Rest der Lebensdauer der Verbindung nicht geändert werden kann. So ist es beispielsweise für eine Clientanwendung einfach, diese gespeicherte Prozedur aufzurufen, um einige Sitzungskontextwerte direkt nach dem Herstellen der Datenbankverbindung festzulegen. Wenn die Anwendung dabei den Parameter read_only festlegt, können die gespeicherten Prozeduren und anderer T-SQL-Code, der dann auf dem Server ausgeführt wird, nur den Wert lesen. Sie können nicht ändern, was von der auf dem Client ausgeführten Anwendung festgelegt wurde.

Als Test habe ich einen Server-Login-Trigger erstellt, der einige CONTEXT_SESSIONInformationen festlegt - einer der SESSION_CONTEXTwar auf gesetzt @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;

Ich habe mich als völlig neuer Benutzer angemeldet und konnte die folgenden SESSION_CONTEXTInformationen extrahieren :

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

Ich habe sogar versucht, die Kontextinformationen 'read_only' zu ändern:

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

und erhielt einen Fehler:

Nachricht 15664, Ebene 16, Status 1, Prozedur sp_set_session_context, Zeile 1 [Stapelstartzeile 8] Der Schlüssel 'CannotChange' kann im Sitzungskontext nicht festgelegt werden. Der Schlüssel wurde für diese Sitzung als read_only festgelegt.

Ein wichtiger Hinweis zu Login-Triggern ( aus diesem Beitrag )!

Ein Anmeldetrigger kann erfolgreiche Verbindungen zum Datenbankmodul für alle Benutzer, einschließlich Mitglieder der festen Serverrolle sysadmin, effektiv verhindern. Wenn ein Anmeldetrigger Verbindungen verhindert, können Mitglieder der festen Serverrolle sysadmin über die dedizierte Administratorverbindung oder durch Starten des Datenbankmoduls im minimalen Konfigurationsmodus (-f) eine Verbindung herstellen.


Ein möglicher Nachteil besteht darin, dass dies den Sitzungskontext instanzweit ausfüllt (nicht pro Datenbank). An diesem Punkt sind die einzigen Optionen, die mir einfallen, folgende:

  1. Benennen Sie Ihre Session_ContextName-Wert-Paare, indem Sie ihnen den Datenbanknamen voranstellen, um keine Kollision für denselben Typnamen in einer anderen Datenbank zu verursachen. Dies löst nicht das Problem, ALLE Session_ContextNamenswerte für alle Benutzer vorab zu definieren .
  2. Wenn der Anmeldeauslöser ausgelöst wird, haben Sie Zugriff auf EventData(xml), mit dem Sie den Anmeldebenutzer extrahieren können. Auf dieser Grundlage können Sie bestimmte Session_ContextName-Wert-Paare erstellen .
Scott Hodgin
quelle
4

In SQL Server nein (obwohl ich mich daran erinnere, dass ich 1998 Konstanten in Oracle-Paketen erstellt habe und sie irgendwie in SQL Server übersehen habe).

UND, ich habe gerade getestet und festgestellt, dass Sie dies mit SQLCLR nicht einmal tun können, zumindest nicht in dem Sinne, dass es in allen Fällen funktionieren würde. Die Verzögerung ist die Einschränkung der Parameter für gespeicherte Prozeduren. Es scheint, dass Sie weder einen .noch einen ::Parameternamen haben können. Ich habe es versucht:

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

-- and:

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

In beiden Fällen hat es die Analysephase (verifiziert mit SET PARSEONLY ON;) nicht einmal hinter sich gebracht, weil:

Meldung 102, Ebene 15, Status 1, Zeile xxxxx
Falsche Syntax in der Nähe von '.'.

Auf der anderen Seite funktionierten beide Methoden für benutzerdefinierte Funktionsparameter:

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

-- and:

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

Das Beste, was Sie wirklich tun können, ist, SQLCLR zu verwenden, um etwas zu haben, das direkt mit UDFs, TVFs, UDAs (nehme ich an) und Abfragen funktioniert, und dann lokalen Variablen zuzuweisen, wenn Sie es mit gespeicherten Prozeduren verwenden müssen:

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

Dies ist der Ansatz, den ich gewählt habe, wenn die Möglichkeit besteht, einen tatsächlichen Aufzählungswert zu erhalten (im Gegensatz zu einem Nachschlagewert, der in einer Nachschlagetabelle enthalten sein sollte, die für seine Verwendung / Bedeutung spezifisch ist).


In Bezug auf den Versuch, dies mit einer benutzerdefinierten Funktion (UDF) zu tun, um den Wert "Konstante" / "Aufzählung" auszuspucken, konnte ich dies auch nicht erreichen, indem ich ihn als Parameter für gespeicherte Prozeduren übergab:

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

Gibt den Fehler "Falsche Syntax" zurück, wobei SSMS alles in den Klammern hervorhebt, auch wenn ich die Zeichenfolge durch eine Zahl ersetze, oder die rechte Klammer, wenn kein Parameter zum Übergeben vorhanden ist.

Solomon Rutzky
quelle