¿Crear constantes de nivel de base de datos (enumeraciones) sin usar CLR?


9

Tengo varios objetos SQL que necesitan tomar acciones alternativas en función del estado deseado de la solicitud. ¿Hay alguna manera de crear constantes de nivel de base de datos (enumeraciones) que puedan pasarse a procedimientos almacenados, funciones con valores de tabla y usarse en consultas (sin usar CLR)?

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

y luego úsalo:

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

Donde myEnumTypetendría algunos valores de enumeración.

En el procedimiento, podría usarlo @EnumValuey probarlo con valores myEnumTypepara hacer el trabajo requerido. Haría los valores de myEnumTypeuna máscara de bits para el caso que estoy considerando.

Para un ejemplo simple, considere un proceso costoso que toma un gran conjunto de datos y lo reduce a un conjunto de datos más pequeño pero aún muy grande. En este proceso, debe realizar algunos ajustes en el medio de ese proceso que afectarán el resultado. Digamos que este es un filtro para (o contra) algunos tipos de registros basados ​​en algún estado de un cálculo intermedio dentro de la reducción. El @EnumValuetipo de myEnumTypepodría usarse para probar esto

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

¿Son posibles este tipo de constantes de nivel de base de datos en SQL Server sin el uso de CLR?

Estoy buscando una enumeración a nivel de base de datos o un conjunto de constantes que se puedan pasar como parámetros a procedimientos almacenados, funciones, etc.

Respuestas:


9

Puede crear un tipo de enumeración en SQL Server utilizando un esquema XML.

Por ejemplo colores.

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

Eso le permite usar una variable o parámetro del tipo xml(dbo.ColorsEnum).

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

Si intentas agregar algo que no es un color

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

te sale un error

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

Construir el XML así puede ser un poco tedioso, por lo que puede crear, por ejemplo, una vista auxiliar que también contenga los valores permitidos.

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;

Y úsalo así para crear la enumeración.

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

Si desea crear la vista dinámicamente desde el Esquema XML, puede extraer los colores con esta consulta.

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

La enumeración, por supuesto, también puede usarse como parámetros para funciones y procedimientos.

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

Puesto que usted está aparentemente utilizando SQL Server 2016, me gustaría que tirar otra ' posible opción' - SESSION_CONTEXT.

El artículo de Leonard Lobel, Sharing State in SQL Server 2016 withSESSION_CONTEXT tiene muy buena información sobre esta nueva funcionalidad en SQL Server 2016.

Resumiendo algunos puntos clave:

Si alguna vez ha querido compartir el estado de la sesión en todos los procedimientos y lotes almacenados durante la vida útil de una conexión de base de datos, le encantará SESSION_CONTEXT. Cuando se conecta a SQL Server 2016, obtiene un diccionario con estado, o lo que a menudo se conoce como bolsa de estado, un lugar donde puede almacenar valores, como cadenas y números, y luego recuperarlo con una clave que asigne. En el caso de SESSION_CONTEXT, la clave es cualquier cadena, y el valor es una variable sql_, lo que significa que puede acomodar una variedad de tipos.

Una vez que guarda algo SESSION_CONTEXT, permanece allí hasta que se cierra la conexión. No se almacena en ninguna tabla de la base de datos, solo vive en la memoria mientras la conexión permanezca activa. Y todos y cada uno de los códigos T-SQL que se ejecutan dentro de procedimientos almacenados, disparadores, funciones o lo que sea, pueden compartir lo que sea que ingreses SESSION_CONTEXT.

Lo más parecido a esto que hemos tenido hasta ahora ha sido CONTEXT_INFO, lo que le permite almacenar y compartir un único valor binario de hasta 128 bytes de longitud, que es mucho menos flexible que el diccionario que obtiene SESSION_CONTEXT, que admite múltiples valores de datos diferentes tipos.

SESSION_CONTEXTes fácil de usar, solo llame a sp_set_session_context para almacenar el valor con la clave deseada. Cuando hace eso, proporciona la clave y el valor, por supuesto, pero también puede establecer el parámetro read_only en true. Esto bloquea el valor en el contexto de la sesión, por lo que no se puede cambiar durante el resto de la vida útil de la conexión. Entonces, por ejemplo, es fácil para una aplicación cliente llamar a este procedimiento almacenado para establecer algunos valores de contexto de sesión justo después de establecer la conexión de la base de datos. Si la aplicación establece el parámetro read_only cuando hace esto, los procedimientos almacenados y otro código T-SQL que luego se ejecuta en el servidor solo pueden leer el valor, no pueden cambiar lo que la aplicación ejecutó en el cliente.

Como prueba, creé un disparador de inicio de sesión del servidor que establece cierta CONTEXT_SESSIONinformación, una de las cuales SESSION_CONTEXTestaba configurada @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;

Ingresé como usuario completamente nuevo y pude extraer la SESSION_CONTEXTinformación:

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

Incluso intenté cambiar la información de contexto 'read_only':

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

y recibió un error:

Mensaje 15664, Nivel 16, Estado 1, Procedimiento sp_set_session_context, Línea 1 [Batch Start Line 8] No se puede establecer la clave 'CannotChange' en el contexto de la sesión. La clave se ha configurado como read_only para esta sesión.

¡Una nota importante sobre los disparadores de inicio de sesión ( de esta publicación )!

Un desencadenador de inicio de sesión puede prevenir eficazmente las conexiones exitosas al Motor de base de datos para todos los usuarios, incluidos los miembros de la función fija de servidor sysadmin. Cuando un desencadenador de inicio de sesión impide conexiones, los miembros de la función fija de servidor sysadmin pueden conectarse mediante la conexión de administrador dedicada o iniciando el Motor de base de datos en modo de configuración mínima (-f)


Un inconveniente potencial es que esto llena la instancia de contexto de sesión en toda la instancia (no por base de datos). En este punto, las únicas opciones que se me ocurren son:

  1. Asigne un Session_Contextnombre a sus pares de nombre-valor prefijándolos con el nombre de la base de datos para no causar una colisión para el mismo tipo de nombre en otra base de datos. Esto no resuelve el problema de predefinir TODOS Session_Contextlos valores de nombre para todos los usuarios.
  2. Cuando se activa el desencadenador de inicio de sesión, tiene acceso a EventData(xml) que puede utilizar para extraer el usuario de inicio de sesión y, en función de eso, puede crear Session_Contextpares específicos de nombre-valor.

4

En SQL Server, no (aunque recuerdo haber creado constantes en paquetes de Oracle en 1998 y he echado de menos tenerlas en SQL Server).

Y, acabo de probar y descubrí que ni siquiera puedes hacer esto con SQLCLR, al menos no en el sentido de que funcionaría en todos los casos. El retraso son las restricciones en los parámetros del Procedimiento almacenado. Parece que no puede tener ni a .ni ::en el nombre del parámetro. Lo intenté:

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

-- and:

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

En ambos casos, ni siquiera pasó la fase de análisis (verificado usando SET PARSEONLY ON;) debido a:

Mensaje 102, Nivel 15, Estado 1, Línea xxxxx
Sintaxis incorrecta cerca de '.'.

Por otro lado, ambos métodos funcionaron para los parámetros de la función definida por el usuario:

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

-- and:

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

Por lo tanto, lo mejor que puede hacer realmente es usar SQLCLR para tener algo que funcione directamente con UDF, TVF, UDA (supongo) y consultas, y luego asignar a variables locales cuando necesite usar con procedimientos almacenados:

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

Este es el enfoque que he tomado cuando existe la oportunidad de tener un valor de enumeración real (a diferencia de un valor de búsqueda que debería estar en una tabla de búsqueda específica para su uso / significado).


Con respecto a intentar esto con una función definida por el usuario (UDF) para escupir el valor "constante" / "enumeración", no pude hacer que eso funcione en términos de pasarlo como un parámetro de procedimiento almacenado:

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

devuelve el error "Sintaxis incorrecta", con SSMS resaltando todo entre paréntesis, incluso si reemplazo la cadena con un número, o el paréntesis correcto si no hay ningún parámetro para pasar.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.