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

Важлива примітка про тригери входу ( з цієї публікації )!

Тригер входу може ефективно запобігти успішному підключенню до Двигуна баз даних для всіх користувачів, включаючи членів ролі фіксованого сервера sysadmin. Коли тригер входу перешкоджає з'єднанням, члени ролі фіксованого сервера sysadmin можуть з'єднуватися за допомогою виділеного з'єднання адміністратора або запускаючи Database Engine в режимі мінімальної конфігурації (-f)


Один з потенційних недоліків полягає в тому, що це заповнює екземпляр контексту сеансу в широкому (не на базі даних). На даний момент я можу придумати лише такі варіанти:

  1. Назвіть Session_Contextпари своїх імен-значень, префіксуючи їх ім'ям бази даних, щоб не викликати зіткнення для того ж імені типу в іншій базі даних. Це не вирішує проблему попереднього визначення ВСІХ Session_Contextіменних значень для всіх користувачів.
  2. Коли спрацьовує тригер входу, у вас є доступ до EventData(xml), який ви можете використати для витягування користувача, який здійснює вхід, і виходячи з цього, ви можете створити конкретні Session_Contextпари імен-значення.

4

У SQL Server немає (хоча я пам'ятаю створення констант в пакетах Oracle ще в 1998 році, і я якось пропустив їх у 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 виділяє все в дужках, навіть якщо я замінюю рядок числом, або правильні дужки, якщо немає параметра, який потрібно передати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.