Создать константы уровня базы данных (перечисления) без использования 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  ...

Возможны ли такого рода константы уровня базы данных в SQL Server без использования CLR?

Я ищу перечисление на уровне базы данных или набор констант, которые можно передать в качестве параметров хранимым процедурам, функциям и т. Д.

Эдмунд
источник

Ответы:

9

Вы можете создать тип перечисления в SQL Server, используя схему XML.

Например, Цвета.

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, вы можете извлечь цвета с помощью этого запроса.

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.

В статье Леонарда Лобеля « Совместное использование состояния в 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' в контексте сеанса. Ключ был установлен как read_only для этого сеанса.

Важное замечание о триггерах входа в систему ( из этого поста )!

Триггер входа в систему может эффективно предотвратить успешные подключения к компоненту Database Engine для всех пользователей, включая членов предопределенной роли сервера sysadmin. Когда триггер входа в систему запрещает подключения, члены предопределенной роли сервера sysadmin могут подключаться с помощью выделенного подключения администратора или путем запуска компонента Database Engine в режиме минимальной конфигурации (-f)


Один потенциальный недостаток заключается в том, что это заполняет весь экземпляр контекста сеанса (не для базы данных). На данный момент единственные варианты, которые я могу придумать:

  1. Назовите ваши Session_Contextпары имя-значение, добавив к ним префикс с именем базы данных, чтобы не вызывать коллизию для того же имени типа в другой базе данных. Это не решает проблему предопределения ВСЕХ Session_Contextзначений имен для всех пользователей.
  2. Когда срабатывает триггер входа в систему, у вас есть доступ к EventData(xml), который вы можете использовать для извлечения имени пользователя для входа в систему, и на основании этого вы можете создавать конкретные Session_Contextпары имя-значение.
Скотт Ходжин
источник
4

В SQL Server нет (хотя я вспоминаю о создании констант в пакетах Oracle еще в 1998 году и, возможно, пропустил их использование в SQL Server).

И, я только что проверил и обнаружил, что вы даже не можете сделать это с SQLCLR, по крайней мере, в том смысле, что он будет работать во всех случаях. Задержка - это ограничения на параметры хранимой процедуры. Кажется, что вы не можете иметь ни a, .ни ::в имени параметра. Я старался:

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 выделяет все в круглых скобках, даже если я заменяю строку на число, или в правой круглой скобке, если нет параметра для передачи.

Соломон Руцкий
источник