Creare costanti (enumerazioni) a livello di database senza usare CLR?


9

Ho diversi oggetti SQL che devono eseguire azioni alternative in base allo stato desiderato della richiesta. Esiste un modo per creare costanti (enumerazioni) a livello di database che possono essere passate a stored procedure, funzioni con valori di tabella e utilizzate nelle query (senza utilizzare CLR)?

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

e poi usalo:

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

Dove myEnumTypecontenere alcuni valori di enumerazione.

Nella procedura sarei in grado di usarlo @EnumValuee testarlo contro i valori myEnumTypeper fare il lavoro richiesto. Vorrei fare i valori di myEnumTypeuna maschera di bit per il caso che sto prendendo in considerazione.

Per un semplice esempio, prendere in considerazione un processo costoso che richiede un set di dati enorme e lo riduce a un set di dati più piccolo ma comunque molto grande. In questo processo è necessario apportare alcune modifiche nel mezzo di quel processo che influiranno sul risultato. Supponiamo che questo sia un filtro per (o contro) alcuni tipi di record in base allo stato di un calcolo intermedio all'interno della riduzione. Il @EnumValuetipo myEnumTypepotrebbe essere usato per testare questo

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

Questo tipo di costanti a livello di database è possibile in SQL Server senza l'uso di CLR?

Sto cercando un elenco a livello di database o un insieme di costanti che possono essere passati come parametri a stored procedure, funzioni e così via.

Risposte:


9

È possibile creare un tipo di enumerazione in SQL Server utilizzando uno schema XML.

Ad esempio Colori.

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

Ciò consente di utilizzare una variabile o un parametro del tipo xml(dbo.ColorsEnum).

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

Se provi ad aggiungere qualcosa che non è un colore

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

ricevi un errore.

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

Costruire XML in questo modo può essere un po 'noioso, ad esempio puoi creare una vista helper che contiene anche i valori consentiti.

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;

E usalo in questo modo per creare l'enumrazione.

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

Se si desidera creare la vista in modo dinamico dallo schema XML, è possibile estrarre i colori con questa query.

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

L'enumerazione può ovviamente essere utilizzata anche come parametro per funzioni e procedure.

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;

6

Dal momento che apparentemente stai usando SQL Server 2016, vorrei buttare via un'altra opzione ' possibile ' - SESSION_CONTEXT.

L'articolo di Leonard Lobel, Condivisione dello stato in SQL Server 2016 conSESSION_CONTEXT alcune ottime informazioni su questa nuova funzionalità in SQL Server 2016.

Riassumendo alcuni punti chiave:

Se hai mai desiderato condividere lo stato della sessione attraverso tutte le procedure e i batch archiviati per tutta la durata di una connessione al database, ti piacerà SESSION_CONTEXT. Quando ci si connette a SQL Server 2016, si ottiene un dizionario con stato, o ciò che viene spesso indicato come un sacchetto di stato, un luogo in cui è possibile memorizzare valori, come stringhe e numeri, e quindi recuperarlo con una chiave assegnata. Nel caso di SESSION_CONTEXT, la chiave è qualsiasi stringa e il valore è sql_variant, il che significa che può ospitare una varietà di tipi.

Una volta archiviato qualcosa SESSION_CONTEXT, rimane lì fino alla chiusura della connessione. Non è memorizzato in alcuna tabella nel database, rimane solo nella memoria finché la connessione rimane attiva. E qualsiasi codice T-SQL in esecuzione all'interno di stored procedure, trigger, funzioni o altro, può condividere qualsiasi cosa tu faccia SESSION_CONTEXT.

La cosa più simile a questa che abbiamo avuto finora è stata CONTEXT_INFO, che consente di archiviare e condividere un singolo valore binario lungo fino a 128 byte, che è molto meno flessibile del dizionario che si ottiene SESSION_CONTEXT, che supporta più valori di dati diversi tipi.

SESSION_CONTEXTè facile da usare, basta chiamare sp_set_session_context per memorizzare il valore con una chiave desiderata. Quando lo fai, fornisci la chiave e il valore ovviamente, ma puoi anche impostare il parametro read_only su true. Questo blocca il valore nel contesto della sessione, in modo che non possa essere modificato per il resto della durata della connessione. Quindi, ad esempio, è facile per un'applicazione client chiamare questa procedura memorizzata per impostare alcuni valori di contesto di sessione subito dopo aver stabilito la connessione al database. Se l'applicazione imposta il parametro read_only quando lo fa, le procedure memorizzate e l'altro codice T-SQL che viene quindi eseguito sul server possono solo leggere il valore, non possono cambiare ciò che è stato impostato dall'applicazione in esecuzione sul client.

Come test, ho creato un trigger di accesso al server che imposta alcune CONTEXT_SESSIONinformazioni - una delle quali SESSION_CONTEXTera impostata su @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;

Ho effettuato l'accesso come utente completamente nuovo ed è stato in grado di estrarre le SESSION_CONTEXTinformazioni:

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

Ho anche tentato di modificare le informazioni di contesto "read_only":

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

e ha ricevuto un errore:

Messaggio 15664, livello 16, stato 1, procedura sp_set_session_context, riga 1 [riga iniziale batch 8] Impossibile impostare la chiave "CannotChange" nel contesto della sessione. La chiave è stata impostata come read_only per questa sessione.

Una nota importante sui trigger di accesso ( da questo post )!

Un trigger di accesso può impedire efficacemente connessioni riuscite al Motore di database per tutti gli utenti, inclusi i membri del ruolo predefinito del server sysadmin. Quando un trigger di accesso impedisce le connessioni, i membri del ruolo predefinito del server sysadmin possono connettersi utilizzando la connessione di amministratore dedicata o avviando il Motore di database in modalità di configurazione minima (-f)


Un potenziale svantaggio è che questo riempie l'istanza del contesto della sessione (non per database). A questo punto, le uniche opzioni che mi vengono in mente sono:

  1. Assegna un Session_Contextnome alle coppie nome-valore aggiungendole al prefisso con il nome del database in modo da non provocare una collisione per lo stesso nome di tipo in un altro database. Questo non risolve il problema di pre-definire TUTTI Session_Contexti valori dei nomi per tutti gli utenti.
  2. Quando viene attivato il trigger di accesso, si ha accesso a EventData(xml) che è possibile utilizzare per estrarre l'utente di accesso e in base a ciò, è possibile creare Session_Contextcoppie nome-valore specifiche .

4

In SQL Server, no (anche se ricordo di aver creato costanti nei pacchetti Oracle nel 1998 e mi sono perso un po 'di averle in SQL Server).

E, ho appena testato e ho scoperto che non puoi nemmeno farlo con SQLCLR, almeno non nel senso che funzionerebbe in tutti i casi. Il blocco è costituito dalle restrizioni sui parametri Stored procedure. Sembra che non puoi avere né a .::il nome del parametro. Provai:

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

-- and:

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

In entrambi i casi non ha nemmeno superato la fase di analisi (verificato utilizzando SET PARSEONLY ON;) a causa di:

Messaggio 102, livello 15, stato 1, riga xxxxx
Sintassi errata vicino a ".".

D'altra parte, entrambi i metodi hanno funzionato per i parametri della funzione definita dall'utente:

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

-- and:

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

Quindi, il meglio che puoi davvero fare è usare SQLCLR per avere qualcosa che funzioni direttamente con UDF, TVF, UDA (presumo) e query, e quindi assegnare alle variabili locali quando è necessario utilizzare con Stored Procedures:

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

Questo è l'approccio che ho adottato quando c'è l'opportunità di avere un valore enum effettivo (al contrario di un valore di ricerca che dovrebbe essere in una tabella di ricerca specifica per il suo utilizzo / significato).


Per quanto riguarda il tentativo di questo con una funzione definita dall'utente (UDF) per sputare il valore "costante" / "enum", non sono riuscito a farlo funzionare neanche in termini di passaggio come parametro Stored procedure:

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

restituisce l'errore "Sintassi errata", con SSMS che evidenzia tutto tra parentesi, anche se sostituisco la stringa con un numero o la parentesi corretta se non è presente alcun parametro da passare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.