创建数据库级别的常量(枚举)而不使用CLR?


9

我有几个SQL对象,它们需要根据请求的期望状态采取其他操作。有没有一种方法可以创建数据库级别的常量(枚举),这些常量可以传递给存储过程,表值函数并可以在查询中使用(不使用CLR)?

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

然后使用它:

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

哪里myEnumType将保留一些枚举值。

在该过程中,我将能够使用@EnumValue它并针对其中的值进行测试myEnumType以完成所需的工作。我将myEnumType考虑的情况下使用位掩码的值。

举一个简单的例子,考虑一个昂贵的过程,该过程需要一个庞大的数据集并将其缩减为较小但仍然非常大的数据集。在此过程中,您需要在过程的中间进行一些调整,以免影响结果。说这是基于归约内中间计算的某些状态来过滤(或针对)某些类型的记录。该@EnumValue类型的myEnumType可以用来测试此

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

在不使用CLR的情况下,SQL Server中是否可能存在这类数据库级别的常量?

我正在寻找数据库级别的枚举或常量集,这些枚举或常量集可以作为参数传递给存储过程,函数等。

Answers:


9

您可以使用XML架构在SQL Server中创建枚举类型。

例如颜色。

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

这样就可以使用类型的变量或参数xml(dbo.ColorsEnum)

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

如果您尝试添加不是颜色的东西

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

你得到一个错误。

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

像这样构造XML可能有点乏味,因此您可以例如创建一个也包含允许值的帮助程序视图。

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;

并像这样使用它来创建枚举。

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

如果要从XML Schema动态创建视图,则可以使用此查询提取颜色。

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

枚举当然也可以用作功能和过程的参数。

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

由于您显然正在使用SQL Server 2016,因此我想抛出另一个“ 可能的 ”选项- SESSION_CONTEXT

Leonard Lobel的文章“ SQL Server 2016中的共享状态”SESSION_CONTEXT具有有关SQL Server 2016中此新功能的一些非常好的信息。

总结一些关键点:

如果您曾经想在数据库连接的整个生命周期中在所有存储过程和批处理之间共享会话状态,那您一定会喜欢上的SESSION_CONTEXT。连接到SQL Server 2016时,您会得到一个有状态的字典,或者通常称为状态包的地方,可以在其中存储诸如字符串和数字之类的值,然后通过分配的键对其进行检索。在的情况下SESSION_CONTEXT,键是任何字符串,并且值是sql_variant,这意味着它可以容纳各种类型。

一旦您将内容存储在中SESSION_CONTEXT,它将一直保留在那里直到连接关闭。只要连接保持活动状态,它就不会存储在数据库的任何表中,而仅存在于内存中。而且,在存储过程,触发器,函数或任何其他内部运行的任何T-SQL代码都可以共享您所追求的目标 SESSION_CONTEXT

到目前为止CONTEXT_INFO,与之最类似的事情是,它允许您存储和共享一个最大为128个字节长的二进制值,这远不如使用字典获得的灵活性SESSION_CONTEXT,后者支持不同数据的多个值类型。

SESSION_CONTEXT易于使用,只需调用sp_set_session_context即可通过所需的键存储值。这样做时,您当然会提供键和值,但是您也可以将read_only参数设置为true。这是将值锁定在会话上下文中,以便在连接的整个生命周期内都无法更改它。因此,例如,在建立数据库连接后,客户端应用程序很容易调用此存储过程来设置一些会话上下文值。如果应用程序在执行此操作时设置了read_only参数,则随后在服务器上执行的存储过程和其他T-SQL代码只能读取该值,它们不能更改客户端上运行的应用程序设置的值。

作为测试,我创建了一个服务器登录触发器,该触发器设置了一些CONTEXT_SESSION信息-的其中之一SESSION_CONTEXT被设置为@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;

我以全新用户身份登录,并能够提取以下SESSION_CONTEXT信息:

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

我什至试图更改“ read_only”上下文信息:

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

并收到一个错误:

消息15664,级别16,状态1,过程sp_set_session_context,第1行[批处理开始第8行]无法在会话上下文中设置键“ CannotChange”。该会话的密钥已设置为只读。

关于登录触发器的重要说明(来自本文)!

登录触发器可以有效地阻止所有用户(包括sysadmin固定服务器角色的成员)成功连接到数据库引擎。当登录触发器阻止连接时,sysadmin固定服务器角色的成员可以通过使用专用管理员连接或以最小配置模式(-f)启动数据库引擎来进行连接。


一个潜在的缺点是,这会填充整个会话上下文实例(而不是每个数据库)。在这一点上,我唯一能想到的选择是:

  1. 命名Session_Context与数据库名称前缀它们,以免引起对在另一个数据库中同一类型名称冲突名称-值对。这不能解决Session_Context为所有用户预定义所有名称值的问题。
  2. 触发登录触发器时,您可以访问EventData(xml),可用于提取登录用户,并基于此创建特定的Session_Context名称/值对。

4

在SQL Server中,没有(尽管我回想起1998年在Oracle软件包中创建常量,并且有点想念在SQL Server中包含常量)。

而且,我刚刚进行测试,发现您甚至无法使用SQLCLR做到这一点,至少在某种意义上说它不能在所有情况下都有效。延迟是对存储过程参数的限制。看来您既不能拥有.::在参数名称中或。我试过了:

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

-- and:

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

在这两种情况下,它甚至都没有超过解析阶段(使用 SET PARSEONLY ON;由于以下原因,):

消息102,第15级,状态1,行xxxxx
“。”附近的语法不正确。

另一方面,这两种方法对用户定义的函数参数均有效:

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

-- and:

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

因此,您真正能做的最好的事情就是使用SQLCLR将其直接与UDF,TVF,UDA(我假设)和查询一起使用,然后在需要与存储过程一起使用时将其分配给局部变量:

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

当有机会获得实际枚举值(而不是应该在特定于其用法/含义的查找表中的查找值)时,这就是我采用的方法。


关于尝试使用用户定义的函数(UDF)吐出“常量” /“枚举”值,就将其作为存储过程参数传递而言,我都无法使它起作用:

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

返回“语法错误”错误,即使我将字符串替换为数字,SSMS也会突出显示括号内的所有内容;如果没有参数可传递,则返回正确的括号。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.