Créer des constantes de niveau base de données (énumérations) sans utiliser CLR?


9

J'ai plusieurs objets SQL qui doivent effectuer des actions alternatives en fonction de l'état souhaité de la demande. Existe-t-il un moyen de créer des constantes de niveau base de données (énumérations) qui peuvent être transmises aux procédures stockées, aux fonctions table et utilisées dans les requêtes (sans utiliser CLR)?

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

puis l'utiliser:

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

myEnumTypecontiendrait quelques valeurs d'énumération.

Dans la procédure, je serais en mesure de l'utiliser @EnumValueet de le tester par rapport aux valeurs myEnumTypepour effectuer le travail requis. Je ferais les valeurs d' myEnumTypeun masque pour le cas que je considère.

Pour un exemple simple, considérons un processus coûteux qui prend un énorme ensemble de données et le réduit à un ensemble de données plus petit mais toujours très grand. Dans ce processus, vous devez effectuer un ajustement au milieu de ce processus qui affectera le résultat. Supposons qu'il s'agit d'un filtre pour (ou contre) certains types d'enregistrements en fonction du statut d'un calcul intermédiaire dans la réduction. Le @EnumValuetype myEnumTypepourrait être utilisé pour tester cette

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

Ces types de constantes de niveau de base de données sont-ils possibles dans SQL Server sans l'utilisation de CLR?

Je recherche une énumération au niveau de la base de données ou un ensemble de constantes pouvant être transmises en tant que paramètres aux procédures stockées, aux fonctions, etc.

Réponses:


9

Vous pouvez créer un type d'énumération dans SQL Server à l'aide d'un schéma XML.

Par exemple Couleurs.

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

Cela vous permet d'utiliser une variable ou un paramètre du type xml(dbo.ColorsEnum).

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

Si vous essayez d'ajouter quelque chose qui n'est pas une couleur

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

vous obtenez une erreur.

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

Construire le XML comme ça peut être un peu fastidieux, vous pouvez par exemple créer une vue d'aide qui contient également les valeurs autorisées.

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;

Et utilisez-le comme ceci pour créer l'énumération.

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

Si vous souhaitez créer la vue dynamiquement à partir du schéma XML, vous pouvez extraire les couleurs avec cette requête.

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'énumération peut bien entendu également être utilisée comme paramètres de fonctions et de procédures.

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

Puisque vous utilisez apparemment SQL Server 2016, je voudrais jeter une autre option « possible » - SESSION_CONTEXT.

L'article de Leonard Lobel, Partage de l'état dans SQL Server 2016 avecSESSION_CONTEXT contient de très bonnes informations sur cette nouvelle fonctionnalité dans SQL Server 2016.

Résumant certains points clés:

Si vous avez toujours voulu partager l'état de session entre toutes les procédures stockées et les lots tout au long de la durée de vie d'une connexion à une base de données, vous allez adorer SESSION_CONTEXT. Lorsque vous vous connectez à SQL Server 2016, vous obtenez un dictionnaire avec état, ou ce que l'on appelle souvent un sac d'état, un endroit où vous pouvez stocker des valeurs, comme des chaînes et des nombres, puis les récupérer à l'aide d'une clé que vous attribuez. Dans le cas de SESSION_CONTEXT, la clé est n'importe quelle chaîne et la valeur est un sql_variant, ce qui signifie qu'elle peut accueillir une variété de types.

Une fois que vous avez stocké quelque chose SESSION_CONTEXT, il y reste jusqu'à la fermeture de la connexion. Il n'est stocké dans aucune table de la base de données, il vit juste en mémoire tant que la connexion reste active. Et tout et tout code T-SQL qui s'exécute dans des procédures stockées, des déclencheurs, des fonctions ou quoi que ce soit, peut partager tout ce que vous introduisez SESSION_CONTEXT.

La chose la plus proche que nous ayons eu jusqu'à présent a été CONTEXT_INFO, ce qui vous permet de stocker et de partager une seule valeur binaire jusqu'à 128 octets de long, ce qui est beaucoup moins flexible que le dictionnaire que vous obtenez SESSION_CONTEXT, qui prend en charge plusieurs valeurs de données différentes les types.

SESSION_CONTEXTest facile à utiliser, il suffit d'appeler sp_set_session_context pour stocker la valeur par une clé souhaitée. Lorsque vous faites cela, vous fournissez la clé et la valeur bien sûr, mais vous pouvez également définir le paramètre read_only sur true. Ceci verrouille la valeur dans le contexte de la session, de sorte qu'elle ne peut pas être modifiée pour le reste de la durée de vie de la connexion. Ainsi, par exemple, il est facile pour une application cliente d'appeler cette procédure stockée pour définir des valeurs de contexte de session juste après avoir établi la connexion à la base de données. Si l'application définit le paramètre read_only lorsqu'elle le fait, les procédures stockées et tout autre code T-SQL qui s'exécute ensuite sur le serveur ne peuvent lire que la valeur, ils ne peuvent pas modifier ce qui a été défini par l'application exécutée sur le client.

À titre de test, j'ai créé un déclencheur de connexion au serveur qui définit certaines CONTEXT_SESSIONinformations - l'un des a SESSION_CONTEXTété défini sur @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;

Je me suis connecté en tant qu'utilisateur complètement nouveau et j'ai pu extraire les SESSION_CONTEXTinformations:

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

J'ai même tenté de modifier les informations de contexte «read_only»:

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

et a reçu une erreur:

Msg 15664, niveau 16, état 1, procédure sp_set_session_context, ligne 1 [ligne de démarrage par lots 8] Impossible de définir la clé «CannotChange» dans le contexte de la session. La clé a été définie en lecture seule pour cette session.

Une note importante sur les déclencheurs de connexion ( de ce post )!

Un déclencheur d'ouverture de session peut empêcher efficacement les connexions réussies au moteur de base de données pour tous les utilisateurs, y compris les membres du rôle serveur fixe sysadmin. Lorsqu'un déclencheur d'ouverture de session empêche les connexions, les membres du rôle serveur fixe sysadmin peuvent se connecter en utilisant la connexion administrateur dédiée ou en démarrant le moteur de base de données en mode de configuration minimale (-f)


Un inconvénient potentiel est que cela remplit l'instance de contexte de session à l'échelle (pas par base de données). À ce stade, les seules options auxquelles je peux penser sont:

  1. Nommez vos Session_Contextpaires nom-valeur en les préfixant avec le nom de la base de données afin de ne pas provoquer de collision pour le même nom de type dans une autre base de données. Cela ne résout pas le problème de la prédéfinition de TOUTES les Session_Contextvaleurs de nom pour tous les utilisateurs.
  2. Lorsque le déclencheur de connexion se déclenche, vous avez accès à EventData(xml) que vous pouvez utiliser pour extraire l'utilisateur de connexion et en fonction de cela, vous pouvez créer des Session_Contextpaires nom-valeur spécifiques .

4

Dans SQL Server, non (bien que je me souvienne d'avoir créé des constantes dans des packages Oracle en 1998 et que je les ai un peu ratés dans SQL Server).

ET, je viens de tester et j'ai découvert que vous ne pouvez même pas le faire avec SQLCLR, du moins pas dans le sens où cela fonctionnerait dans tous les cas. Le blocage correspond aux restrictions sur les paramètres de procédure stockée. Il semble que vous ne puissiez pas avoir un .ou ::dans le nom du paramètre. J'ai essayé:

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

-- and:

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

Dans les deux cas, il n'a même pas dépassé la phase d'analyse (vérifié à l'aide de SET PARSEONLY ON;) en raison de:

Msg 102, niveau 15, état 1, ligne xxxxx
Syntaxe incorrecte près de '.'.

D'un autre côté, les deux méthodes ont fonctionné pour les paramètres de la fonction définie par l'utilisateur:

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

-- and:

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

Donc, le mieux que vous puissiez vraiment faire est d'utiliser SQLCLR pour avoir quelque chose qui fonctionne directement avec les UDF, les TVF, les UDA (je suppose) et les requêtes, puis les affecter aux variables locales lorsque vous devez les utiliser avec les procédures stockées:

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

C'est l'approche que j'ai adoptée lorsqu'il est possible d'avoir une valeur d'énumération réelle (par opposition à une valeur de recherche qui devrait être dans une table de recherche spécifique à son utilisation / signification).


En ce qui concerne la tentative avec une fonction définie par l'utilisateur (UDF) pour cracher la valeur "constante" / "enum", je n'ai pas pu faire fonctionner cela non plus en termes de transmission en tant que paramètre de procédure stockée:

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

renvoie l'erreur "syntaxe incorrecte", SSMS mettant tout en surbrillance entre parenthèses, même si je remplace la chaîne par un nombre, ou la parenthèse droite s'il n'y a pas de paramètre à transmettre.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.