Блеск и нищета сводных таблиц
Часть 8
Неопределенные значения в многомерной среде
Важные правила обращения с наборами элементов при составлении многомерных отчетов
В предыдущей части статьи мы рассмотрели применение функции КУБПОРЭЛЕМЕНТ() для вывода одномерных наборов. Данная функция позволяет пользователям, даже не знающим языка MDX, показывать на листе Microsoft Excel большие и сложные наборы элементов из многомерного пространства. Но все функции КУБ() являются инструментами из арсенала бизнес-пользователя, поэтому они сделаны с расчетом на его стандартные потребности и с оглядкой на его типичные возможности. Это относится и к операторам КУБ() — в них функциональность также принесена в жертву простоте использования.
Однако на практике чаще всего встречаются ситуации, когда на одной оси сводного отчета нужно расположить сразу несколько измерений. Довольно обидно, имея в своем распоряжении мощные средства работы с многомерными данными, не использовать их в сложившихся обстоятельствах. Как уже говорилось, каждая из функций КУБЭЛЕМЕНТ() и КУБПОРЭЛЕМЕНТ() умеет показывать на листе Microsoft Excel только один элемент из кортежа. Но это ограничение можно с успехом обойти, если научиться использовать сразу несколько наборов в синхронном режиме. К сожалению, подобную настройку не удастся выполнить штатными средствами Microsoft Excel — она требует от пользователя знания основ MDX.
Кроме того, отчеты, созданные с помощью функций КУБМНОЖ() и КУБПОРЭЛЕМЕНТ(), можно сделать гораздо удобнее для пользователей, если применять в них различные стандартные элементы управления Microsoft Excel — флажки, переключатели, кнопки. Логика использования таких элементов управления предполагает, что пользователь имеет возможность как добавить с их помощью отдельный элемент, так и удалить его из набора. Далее будет показано, что проще всего исключать элементы из набора, просто меняя их на неопределенные значения куба. Для выполнения такой операции от пользователя требуется понимание того, что собственно представляют собой «пустые» значения куба, а также какими последствиями чревато их применение в вычислениях.
Настоящая статья призвана дать краткое теоретическое обоснование всем методам, которые используются в «продвинутых» режимах работы оператора КУБПОРЭЛЕМЕНТ(). При этом, на взгляд автора, рассматриваемые темы сами по себе представляют определенный практический интерес.
Неопределенные значения в многомерной среде
Мы начнем обсуждение с более простого второго вопроса — о неопределенных элементах в кубе. В качестве примера рассмотрим многомерное пространство, которое собирается на базе отношения «Периоды», представленного плоской таблицей на рис. 1.
Рис. 1. Табличное представление
отношения «Периоды»
Как видите, таблица содержит всего семь строк, соответственно отношение состоит из семи элементов. Причем у элемента № 6 «Июнь» значение атрибута «Сумма» не определено, а элемент № 7 определен не полностью.
Вычислим сначала при помощи обычных операторов SQL возможные агрегатные значения для элементов из таблицы (рис. 2).
Рис. 2. Расчет агрегатных значений
Число кортежей с непустыми значениями атрибута «Сумма» считается по формуле COUNT(Сумма) и равно 5.
Число элементов в домене атрибута «Месяц» считается по формуле COUNT(Месяц) и равно 6.
Сумма всех значений атрибута «Сумма», а также среднее значение атрибута считается по формулам AVG(Сумма) и SUM(Сумма) и равняется 100 и 500 единицам соответственно.
Результаты вычислений, приведенные на рис. 2, показывают, что кортеж (Июнь, Null), содержащий пустое (неопределенное) значение в колонке «Сумма», не влияет на значение агрегата по атрибуту «Сумма», но участвует в подсчете числа элементов в колонке «Месяц» — число месяцев в формуле COUNT(Месяц) ожидаемо равно 6. А вот пустой кортеж (Null, Null) вообще никак не влияет на результаты всех вычислений.
Посмотрим на отношение «Периоды» с позиций многомерного анализа. Строка 6 является наглядным примером пустой ячейки. Пустая ячейка — это такая ячейка куба, которая находится внутри его логического пространства, но имеет неопределенное значение меры. В нашем случае строка 6 определяет элемент с координатой «Июнь» и пустым значением в поле «Сумма».
При расчете агрегатов куба пустая ячейка обычно ведет себя аналогично кортежу со значением «Null» в реляционной среде. В частности, среднее значение меры для набора 1, вычисляемое по формуле Avg( [Набор 1]), равно 100, что соответствует значению показателя AVG(Сумма) для отношения «Периоды».
Набор 1
{
[Янв],
[Фев],
[Мар],
[Апр],
[Май],
[Июн]
}
А вот ситуация с числом элементов в Наборе1 не столь однозначная, как может показаться на первый взгляд. С одной стороны, элемент [Июнь] явно присутствует во множестве (Наборе 1), поэтому разумно учесть его в общем количестве. С другой стороны, значение меры для данной ячейки равно Null, а неопределенные значения, как известно, не участвуют в расчете агрегатов (по крайней мере, когда речь заходит о реляционных базах данных).
Оказывается, в OLAP-кубах возможна реализация обоих подходов. В зависимости от потребностей пользователя результат может быть равен как пяти, так и шести элементам. Ответ зависит от того, с каким ключом запущен оператор Count() — INCLUDEEMPTY или EXCLUDEEMPTY. По умолчанию он выполняется с параметром INCLUDEEMPTY, то есть с учетом элементов, имеющих неопределенные значения меры. При такой настройке его действие фактически аналогично работе оператора Count(Месяц) из приведенного ранее примера.
Если же выбрать альтернативный режим — EXCLUDEEMPTY, то в подсчете будут участвовать только строки, у которых имеются непустые значения в обоих столбцах — «Месяц» и «Сумма». В этом случае число элементов в Наборе 1 будет равно 5.
Вывод из всех этих рассуждений довольно простой: пустые ячейки могут влиять на величины отдельных показателей куба, поэтому использовать их нужно очень осмотрительно.
Но, кроме пустых ячеек, у кубов имеется еще одна интересная категория пустых элементов — так называемые неопределенные элементы. Они расширяют логическое пространство куба — добавляются в виде новых членов на оси измерений. При этом неопределенным у них является не только значение меры, но и все координаты.
В отношении «Периоды» есть строка № 7, содержащая кортеж (Null, Null). Условно его можно считать неопределенным элементом пространства. С учетом строки 7 Набор 1 дополнится новым элементом Null (Набор 2).
Набор 2
{
[Янв],
[Фев],
[Мар],
[Апр],
[Май],
[Июн],
Null
}
Действие неопределенного элемента на многомерное пространство похоже на действие пустого кортежа при расчете агрегатных значений в реляционном отношении. В отличие от пустых ячеек, неопределенный элемент не влияет на агрегатные значения, что делает его использование «безопасным» в прикладных расчетах.
Допустим, нам требуется на базе Набора 1 составить новый Набор 3, в котором нет элемента [Мар].
Набор 3
{
[Янв],
[Фев],
[Апр],
[Май],
[Июн]
}
Такую операцию можно выполнить двумя способами. Первый — явно определить Набор 3 путем перечисления входящих в него элементов. Второй — заменить в Наборе 1 элемент [Мар] на неопределенный «Null»:
Набор 4
{
[Янв],
[Фев],
Null,
[Апр],
[Май],
[Июн]
}
Хотя в Наборе 4 формально присутствует шесть элементов, его участие в групповых расчетах по своему действию неотличимо от Набора 3. Как уже говорилось, неопределенный элемент не изменит итоговые значения любой агрегатной функции. Поэтому в узком смысле Набор 3 и Набор 4 можно считать эквивалентными.
Понятно, что аналогичным способом создаются наборы, состоящие из любого подмножества исходного набора. Например, набор, содержащий только элементы [Янв] и [Май], может быть описан следующим образом:
Набор 5
{
[Янв],
Null,
Null,
Null,
[Май],
Null
}
У читателя может возникнуть вопрос, а в чем состоит практическая ценность обсуждаемого подхода? Центральная идея, которую хочется воплотить на практике, заключается в создании такой рабочей среды, которая позволяет пользователю гибко настраивать аналитические отчеты под собственные нужды. Для этого, в частности, у него должна быть возможность на лету формировать наборы для их последующего размещения на осях отчета.
Напомним, что основными инструментами по созданию наборов в среде Microsoft Excel являются функция КУБМНОЖ() и оператор конкатенации строк СЦЕПИТЬ(). Функция КУБМНОЖ() является обычной функцией листа Microsoft Excel со всеми вытекающими отсюда последствиями. В ней допускается указывать ссылку на диапазон ячеек, хранящих отдельные элементы набора. Но сам диапазон при этом должен иметь фиксированные границы. Если же пользователю потребуется набор, в котором число элементов меньше количества ячеек в исходном диапазоне, то функция КУБМНОЖ() будет отрабатывать его с ошибкой. Использование оператора СЦЕПИТЬ() приводит к схожей проблеме. Для его работы необходимо явно перечислить аргументы — указать все строки, требующие объединения в единый текст. Поэтому в случае, когда функция СЦЕПИТЬ() написана для слияния пяти текстовых фрагментов, а требуется составить набор только из трех элементов, ее придется прописывать заново. Понятно, что на практике такой подход совершенно неприемлем.
К счастью, ситуация легко исправляется, когда в наборе совместно со стандартными используются неопределенные элементы. Рассмотрим очередной практический пример.
Допустим, мы хотим предоставить пользователю возможность динамически составлять произвольные наборы на базе Набора 1. Для этого расположим на листе Microsoft Excel шесть элементов управления типа «флажок», каждому из которых дадим название одного из месяцев первого полугодия (рис. 3).
Рис. 3. Использование неопределенных элементов в форме
Далее в форме Формат элемента управления на закладке Элемент управления в поле Связь с ячейкой свяжем ее с ячейками из столбца D текущего листа книги. А для ячеек из «Диапазона 1» (столбец F) напишем формулы следующего вида: ЕСЛИ(D6;E6;»Null»). Указанная формула, в зависимости от текущего значения флажка, прописывает в ячейках Диапазона 1 название месяца либо текст «Null». Далее, Диапазон 1 подадим на вход функции КУБМНОЖ(), а ее, в свою очередь, сделаем аргументом для функции КУБПОРЭЛЕМЕНТ(). Из рис. 3 видно, что из всего множества элементов, составляющего Диапазон 1 (см. Набор 6), функция КУБМНОЖ() cформировала новый набор, уже без неопределенных элементов. Поэтому функция КУБПОРЭЛЕМЕНТ() оставила в строках таблицы на листе книги Microsoft Excel только те месяцы, которые были предварительно выбраны при помощи «флажков» на форме. Теперь с каждым новым выделением месяцев на форме при помощи флажков будут одновременно меняться и строки с названиями месяцев в таблице отчета, что и было нашей целью.
Набор 6
{
Null
[Фев],
Null,
Null,
[Май],
[Июн]
}
В качестве небольшого отступления заметим, что пустая ячейка на листе не является для функции КУБМНОЖ() корректным аргументом. Поэтому попытка сформировать множество элементов для функции КУБМНОЖ() посредством явного перечисления нужных месяцев, оставляя оставшиеся строки в диапазоне пустыми, приводит к неизбежной ошибке ее исполнения. На рис. 3 показана такая ситуация. В Диапазоне 2 первые три элемента являются названиями месяцев, а оставшиеся три — пустыми ячейками листа. Соответственно вторая функция КУБМНОЖ(), работающая с Диапазоном 2, вместо результата в виде набора из трех элементов возвращает ошибку «#Н/Д».
Важные правила обращения с наборами элементов при составлении многомерных отчетов
После того как мы разобрались с созданием динамических наборов на базе одного измерения в среде Microsoft Excel, нам осталось решить последнюю проблему — научиться размещать на одной оси отчета элементы сразу из нескольких измерений. Начать придется с небольшого теоретического вступления.
Как уже неоднократно говорилось, многомерный отчет, создаваемый при помощи инструкции Select, располагает измерения OLAP-куба на трех осях:
Axis1 — ось X таблицы;
Axis2 — ось Y таблицы;
Where — невидимая в отчете ось, которая соответствует разделу фильтров сводной таблицы.
В простейшем варианте на каждой из осей размещаются наборы (Sets), состоящие из элементов одного измерения. Например, в выражении № 1 представлен запрос, который выводит на ось X (горизонтальная ось при экономичной записи обозначается символом «0») все элементы измерения «Дата», а на ось Y (обозначается символом «1») — элементы измерения «Дирекция». На оси «Where» размещается элемент [План_Факт].[План_Факт].[Факт], что означает фильтрацию данных только по фактическим значениям.
Выражение 1
select {nonempty([Дата].[Дата].members)} on 0,
{[Дирекция].[Дирекция].members} on 1
from [PF]
where [План_Факт].[План_Факт].[Факт]
Итогом работы запроса станет отчет, представленный на рис. 4.
Рис. 4. Создание простого MDX-запроса
Из рисунка видно, что на вертикальной оси («1») выводится набор, состоящий всего из трех элементов и при этом полностью совпадающий с измерением [Дирекция]:
Набор 7
{
([All]),
([Дир_1]),
([Дир_2])
}
В общем случае набор может состоять из любой комбинации элементов. Главное, чтобы все они относились к одному измерению. Увеличим для примера размер набора, для этого повторно добавим в выражение № 1 элементы измерения [Дирекция], а также еще один вычисляемый элемент [Дир_2 - Дир_1], считающий отклонение результатов Дир_2 от результатов Дир_1 по текущему контексту.
Выражение 2
with member [Дирекция].[Дирекция].[Дир_2 - Дир_1] as ([Дирекция].[Дирекция].[Дир_2]-[Дирекция].[Дирекция].[Дир_1])
select {nonempty([Дата].[Дата].members)} on 0,
{[Дирекция].[Дирекция].members, [Дирекция].[Дирекция].members, [Дирекция].[Дирекция].[Дир_2 - Дир_1]} on 1
from [PF]
where [План_Факт].[План_Факт].[Факт].
В результате работы Выражения № 2 структура выходного отчета немного изменится (рис. 5).
Рис. 5. Отчет на базе производного набора элементов
из измерения «Дирекция»
Основное отличие нового отчета от исходного варианта заключается в увеличении количества строк таблицы. Во втором случае их уже семь, но заголовками строк остаются всё те же представители измерения [Дирекция].[Дирекция]:
Набор 8
{
([All]),
([Дир_1]),
([Дир_2]),
([All]),
([Дир_1]),
([Дир_2]),
([Дир_2 – Дир_1])
}.
Конечный набор базируется на измерении «Дирекция», но вовсе не тождествен ему. В данном замечании нет ничего неожиданного: язык МDX как раз и предназначен для гибкой настройки представлений данных из кубов. Для нас же здесь важна упорядоченность полученного выходного набора — он представляет собой последовательность, к каждому элементу которой можно обратиться по номеру.
В случае MDX подобная операция выполняется посредством оператора Item(). Перепишем еще раз наше MDX-выражение:
Выражение 3
with member [Дирекция].[Дирекция].[Дир_2 - Дир_1] as ([Дирекция].[Дирекция].[Дир_2]-[Дирекция].[Дирекция].[Дир_1])
set [Дирекция_Расширенная] as {[Дирекция].[Дирекция].members, [Дирекция].[Дирекция].members, [Дирекция].[Дирекция].[Дир_2 - Дир_1]}
select {nonempty([Дата].[Дата].members)} on 0,
{[Дирекция_Расширенная].Item(3), [Дирекция_Расширенная].Item(6)} on 1
from [PF]
where [План_Факт].[План_Факт].[Факт]
В последнем варианте на вертикальной оси показываются только 4-й и 7-й элементы (нумерация элементов в наборе ведется с нуля) набора, определенного на предыдущем этапе (рис. 6).
Рис. 6. Вывод элементов с определенными порядковыми номерами
Операция, при которой происходят упорядочивание и нумерация элементов множества, согласно некому правилу, называется ранжированием, а номер, который при этом присваивается элементу, — рангом.
Мы подошли к одному из принципиальных отличий языка MDX от SQL. Запросы к реляционным данным, написанные на SQL, не гарантируют порядок возврата записей. Базовой технологией хранения данных в СУБД является «куча» (heap), поэтому запрос, запущенный в разное время, может показывать одни и те же записи в разной последовательности. Чтобы получить в результирующем наборе гарантированный порядок следования записей, требуется воспользоваться инструкцией ORDER. Однако команда ORDER только сортирует кортежи, но не нумерует их. Для получения упорядоченного и нумерованного набора следует применить одну из специальных ранжирующих оконных функций: RANK(), DENSE_RANK() или ROW_NUMBER(). Ранг записи при этом становится ее отдельным атрибутом и хранится в дополнительной колонке таблицы. Аналитические данные, напротив, упорядочены по своей природе. Поэтому результатом работы MDX-выражения всегда будет отсортированный набор, в котором каждый элемент по умолчанию обладает рангом.
Данный принцип верен и для MDX-запросов, объединяющих данные из нескольких измерений. Создадим отчет, в котором доходы коммерческих дирекций дополнительно детализируются по типам услуг.
Выражение 4
with set [Дирекции * Услуги] as [Дирекция].[Дирекция].members*[Сервис].[Сервис].members
select {nonempty([Дата].[Дата].members)} on 0,
{ [Дирекции * Услуги]} on 1
from [PF]
where [План_Факт].[План_Факт].[Факт]
В результате работы запроса получим отчет, показанный на рис. 7.
Рис. 7. Детализация доходов по дирекциям и видам услуг
В этот раз на вертикальной оси отчета размещен набор, состоящий уже из кортежей. В нашем случае кортеж представляет собой упорядоченную пару элементов, взятых из измерений [Дирекция] и [Сервис].
Набор 9
{
([All],[All]),
([All],[Абонентская плата]),
([All],[ИнтернетДоступ]),
([All],[МГ/МН связь]),
([All],[Местная связь]),
([All],[Установочная плата]),
([Дир_1],[All]),
([Дир_1],[Абонентская плата]),
([Дир_1],[ИнтернетДоступ]),
([Дир_1],[МГ/МН связь]),
([Дир_1],[Местная связь]),
([Дир_1],[Установочная плата]),
([Дир_2],[All]),
([Дир_2],[Абонентская плата]),
([Дир_2],[ИнтернетДоступ]),
([Дир_2],[МГ/МН связь]),
([Дир_2],[Местная связь]),
([Дир_2],[Установочная плата])
}
Каждый элемент теперь представлен массивом из двух элементов. Но, как и в случае с одномерным набором, все элементы новой последовательности также имеют ранги, позволяющие обращаться к ним. Выделим для примера кортеж ([Дир_1],[Абонентская плата]), он расположен в результирующем наборе на 8-м месте. Для этого в очередной раз воспользуемся функцией Item().
Выражение 5
With set [Дирекции * Услуги] as [Дирекция].[Дирекция].members*[Сервис].[Сервис].members
select {nonempty([Дата].[Дата].members)} on 0,
{[Дирекции * Услуги].Item(7)} on 1
from [PF]
where [План_Факт].[План_Факт].[Факт]
Ожидаемым результатом работы Выражения 5 станет отчет, состоящий всего из одной строки (рис. 8).
Рис. 8. Вывод элемента многомерного набора
с определенным порядковым номером
Зададимся вопросом: что произойдет, если изменить порядок следования атрибутов в кортеже? Каким будет отчет из Выражения 5, в котором на первом месте будет стоять измерение [Сервис], а не [Дирекция]?
Выражение 6
With set [Услуги*Дирекции] as
[Сервис].[Сервис].members*[Дирекция].[Дирекция].members
select {nonempty([Дата].[Дата].members)} on 0,
{ [Услуги*Дирекции]} on 1
from [PF]
where [План_Факт].[План_Факт].[Факт]
Новый результирующий набор (рис. 9) несколько отличается от отчета на рис. 7.
Рис. 9. Изменение порядка следования измерений в наборе
Заметим, что выражения 4 и 6 эквиваленты в том плане, что определяют одно подмножество OLAP-куба. Из рисунков видно, что количество и состав записей в обоих отчетах совпадают, но при этом существенно различается порядок их следования. Последовательность, в которой записи выводятся в результирующем наборе, зависит от того, как перечислялись измерения в операторе перекрестного умножения. Иными словами, в зависимости от синтаксиса MDX-выражений записи в отчетах упорядочиваются по-разному.
Место кортежа в наборе определяет его ранг. Поэтому ранг кортежа также меняется в зависимости от вида записи MDX-выражения. В частности, кортеж ([Абонентская плата], [Дир_1]) в выражении 6 определяет тот же адрес на осях измерений [Дирекция] и [Сервис], что и кортеж ([Дир_1],[Абонентская плата]). Но его ранг равен 5 (строка с кортежем идет пятой по счету в отчете на рис. 7).
Для успешной работы с функцией КУБПОРЭЛЕМЕНТ() критически важно уметь на базе исходного набора составлять производные наборы, в которых используется иной порядок следования измерений в кортеже, но при этом сохраняется ранг самого кортежа. В нашем случае необходимо уметь составлять следующий набор.
Набор 10
{
([All],[All]),
([Абонентская плата],[All]),
([ИнтернетДоступ],[All]),
([МГ/МН связь],[All]),
([Местная связь],[All]),
([Установочная плата],[All]),
([All],[Дир_1]),
([Абонентская плата],[Дир_1]),
([ИнтернетДоступ],[Дир_1]),
([МГ/МН связь],[Дир_1]),
([Местная связь],[Дир_1]),
([Установочная плата],[Дир_1]),
([All],[Дир_2]),
([Абонентская плата],[Дир_2]),
([ИнтернетДоступ],[Дир_2]),
([МГ/МН связь],[Дир_2]),
([Местная связь],[Дир_2]),
([Установочная плата],[Дир_2])
}
В Наборе 10 кортеж ([Абонентская плата],[Дир_1]) стоит на 8-м месте, а значит имеет тот же ранг, что и кортеж ([Дир_1],[Абонентская плата]) в Наборе 9. Проблема заключается в том, что получить набор из Набора 9 методом «грубой силы» — путем изменения порядка измерений в операторе CROSSJOIN() — не удастся. Такую операцию придется выполнять посредством функции EXTRACT().
Функция EXTRACT() в определенном смысле является обратной для оператора CROSSJOIN(). Если оператор перекрестного умножения создает прямое произведение набороваргументов, то оператор EXTRACT(), напротив, из итогового набора извлекает элементы одного из базовых измерений. Извлечение элементов производится в том же порядке, в каком они следуют в исходном наборе, — оператор сохраняет ранги. Но если в ходе такой операции возникают дубликаты, то они не показываются в итоговом результате.
Рассмотрим, как работает функция EXTRACT() на нашем примере. Набор 9 из начала статьи представляет собой набор кортежей из двух элементов: ([Дирекция], [Сервис]), задаваемого операцией прямого произведения измерений [Дирекция], и [Сервис]: [Дирекция].[Дирекция].members*[Сервис].[Сервис].members.
Теоретически на базе набора ([Дирекция], [Сервис]) можно составить набор, состоящий только из элементов одного измерения ([Дирекция]).
Набор 11
{
([All]),
([All]),
([All]),
([All]),
([All]),
([All]),
([Дир_1]),
([Дир_1]),
([Дир_1]),
([Дир_1]),
([Дир_1]),
([Дир_1]),
([Дир_2]),
([Дир_2]),
([Дир_2]),
([Дир_2]),
([Дир_2]),
([Дир_2])
}
Если теперь из Набора 11 удалить все дубликаты, не нарушая при этом порядка следования элементов, то он выродится в Набор 12, совпадающий с измерением [Дирекция].
Набор 12
{
([All]),
([Дир_1]),
([Дир_2])
}
Именно эти действия и совершает функция EXTRACT() — рис. 10.
Рис. 10. Работа функции Extract()
Посредством функции EXTRACT() можно определять кортежи, состоящие из любой комбинации измерений исходного набора. В частности, на базе набора элементов {[Дирекция], [Сервис]} допустимо составить набор из кортежей {[Сервис], [Дирекция]}. При этом функция сохранит порядок следования элементов и удалит возникающие дубликаты из результирующего набора. Понятно, что в нашем случае повторяющихся элементов не будет, а оператор EXTRACT() просто переставит элементы в кортежах во всем наборе.
Из рис. 11 следует, что в отчете на оси «1» кортеж ([Абонентская плата],[Дир_1]) находится на 8-м месте и соответственно имеет такой же ранг, что и кортеж ([Дир_1],[Абонентская плата]) из отчета на рис. 7.
Рис. 11. Изменение порядка следования измерений посредством функции EXTRACT()
Заключение
Настоящая статья рассматривает два важных аспекта работы с многомерными данными. Этот материал предназначен для читателей с любым уровнем предварительной подготовки. Поэтому все теоретические вопросы обсуждались достаточно подробно, чтобы сделать их понятными для начинающих. О практической применимости описанных выше методов мы поговорим в следующий раз.