Обучающие игры для любознательных аналитиков младшего и среднего возраста Модель вычислений Архитектура выполнения многомерного запроса Анализ производительности и методы оптимизации Перед нами фрагмент структуры модельной БД AdventureWorks «Боевые» БД из реального мира выглядят, очевидно, еще сложнее Задачи, для которых выстраивалась схема OLTP, - это учет и накопление данных Удобно ли проводить анализ на основе такой структуры? Под словом «анализ» здесь будем понимать исследование отображений n-мерного дискретного множества в область вещественных чисел F(Dn) -> Rm n-мерного в том плане, что оно распадается на произведение n независимых дискретных множеств: Dn = D1 x … x Dn Например, Продажи в деньгах = F(География, Товары) Продажи Товары География Под продажами может пониматься не только денежное, но и натуральное представление. Тогда будет две поверхности и отображение в R2. Практически никакие события реального мира не описываются аналитически Аналитическая зависимость – всего лишь удобная степень приближения Для их описания используется табличный метод Хлеб Молоко Пиво Вобла Памперсы Сиэтл Редмонд Лос-Анджелес Сан-Франциско Сан-Диего 15 35 55 75 80 65 105 135 155 190 105 170 215 240 245 65 105 135 155 190 15 35 55 75 80 Аналогично, когда измерений > 2, например Время 100 100 100 100 222 75 34 48 151 123 100 100 100 100 100 100 100 100 100 100 134 87 95 83 16 60 100 100 100 100 100 96 89 77 12 55 100 100 100 100 100 100 100 100 100 100 99 126 88 66 42 98 100 100 100 100 100 133 80 65 31 94 100 100 100 100 100 7 106 100 100 100 100 50 52 68 24 92 100 100 100 100 100 43 44 76 15 84 100 100 100 100 100 100 100 100 100 201 100 51 14 64 101 59 100 100 100 100 100 32 70 111 15 226 33 100 100 100 100 104 75 155 240 155 75 80 190 245 190 80 География Товары Внутри каждой ячейки сидят продажи, а также все остальные численные показатели (Rm) На каждом Di определена унарная операция «родитель», возвращающая множество родительских элементов для каждого элемента множества P(Сан-Франциско) = Калифорния, Р(17/10/2007) = 10/2007 Она задает над Di так называемую иерархию Таких операций может несколько, тогда говорят, что над измерением задано несколько иерархий Р1(17/10/2007) = 10/2007, Р2(17/10/2007) = 3-я неделя октября 2007 г. На основе этой операции определяются обратная операция «ребенок», операция «брат» и др. Реб(X) = {Y}, если Р({Y}) = X; Б(X) = Y, если P(X) = P(Y) Узлами иерархии называются те элементы Х, для которых Реб(Х) ≠ 0 Остальные элементы называются листьями дерева иерархии Как правило, отображение F(Dn) -> Rm задано для листьев Di Заданы правила расчета Rm в узлах на основе подчиненных элементов Измерения Di = таблицы-справочники (или совокупности таблиц) Иерархии = PK - FK отношения Отображение F(Dn) -> Rm = таблица фактов Rm Декартово F(Dn) -> Rm произведение Di Например Тем не менее далее в докладе по соображениям удобства визуализации в дальнейшем будем использовать сводную таблицу География Товары ... ... Редмонд Редмонд Редмонд Редмонд Редмонд Редмонд Редмонд Редмонд Редмонд Редмонд Редмонд ... Хлеб Хлеб Хлеб Молоко Молоко Молоко Пиво Пиво Пиво Вобла Вобла ... Время Выручка Объем ... ... ... ... Январь 24 ... Февраль 27 ... Март 21 ... Январь 33 ... Февраль 39 ... Март 42 ... Январь 100 ... Февраль 100 ... Март 100 ... Январь 100 ... Февраль 100 ... ... ... Key Store – хранение ключей атрибута в связке с уникальным идентификатором DataID Служебный идентфикатор однозначно идентифи всех местах возрастает AS, цирует атрибут члена во хранения, монотонно Property store – хранятся все св-ва атрибута: имена, трансляции, ... по его DataID Также физически упорядочено по DataID Хэш-таблицы – материализуются на диск для ускорения выборки атрибутов при выполнении запросов и процессинге Хэши создаются только для двух св-в атрибута: ключа и имени Relationship store – как следует из названия, хранит взаимоотношения атрибута с другими атрибутами Пример: черный спортивный шлем размера L, атрибуты: Продукт = спортивный шлем, Цвет = черный, размера L Запись в таблице вида 1001, 25, 5, где числа - DataID соотв.значений атрибутов Bitmap Indexes – 1, если данный DataID существует на данной странице Создание при процессинге; может занять время, если DataIDов много Не создавать – выставить AttributeHierarchyOptimizedState в Not Optimized Set Store – воспроизодит путь к каждому члену от верхнего уровня до текущего All -> Bikes -> Mountain Bikes -> Mountain Bike 500 представляется в виде 1,2,5,6, где числа - DataIDы соответствующих членов на разных уровнях иерархии Structure Store – для каждого члена уровня содержит DataIDы родителя и первого ребенка, а также общее число детей Записи физически упорядочены внутри уровня в соответствии с установками OrderBy атрибута, образующего данный уровень иерархии Материализуются только натуральные иерархии По партициям Каждая партиция содержит две категории данных: детальные и агрегаты В случае MOLAP структуры под их хранение идентичны Разбиты по сегментам, каждый сегмент из 256 страниц по 256 записей в каждом В каждой записи содержатся значения всех мер из группы + DataID гранулярных атрибутов каждого измерения Хранятся только только записи, присутствующие в реляционной таблице фактов, а не «тупое» произведение измерений Проверяется, можно ли удовлетворить запрос из кэша Storage Engine Самый быстрый, т.к. кэш всегда живет в памяти Можно ли удовлетворить его из агрегатов Напоминание: агрегаты рассчитываются на этапе процессинга и персистятся SE не требует точного совпадения, т.к. может агрегировать на лету более детальные агрегации Напр., он в курсе устройства измерений и знает, что Q1 = Янв + Фев + Март Если ничего из этого не удается, лезет в факты Должен пробежать партицию, чтобы достать из нее нужные записи Несмотря на применение bitmap-индексов, кластеризации и др. методик внутренней оптимизации, если партиция велика, это будет происходить медленно Имеет смысл разделить данные на партиции так, чтобы их обработка могла идти в параллели SE достаточно «сообразителен», чтобы по DataID членов в запросе отсечь ненужные партиции – см. позапредыдущий слайд Часто можно слышать вопрос: как из частных агрегатов внутри партиций AS получает общий агрегат в случае неаддитивной меры Необходимо представлять себе, как устроены агрегаты Analysis Services В качестве инструмента возьмем Aggregation Manager в составе примеров к SP2 Дается в виде исходников на C# Предположим для простоты, что в куб входят всего три измерения: Customer, Product, OrderDate Порядок существенен, в общем случае его можно видеть в редакторе куба, закладка Cube Structure, фрейм Dimensions Порядок атрибутов внутри каждого измерения в общем случае можно видеть, сгенерив XML-код измерения, в элементе <Attributes> Предположим, что в измерения входят следующие атрибуты Customer: CustomerID, City, Region, State/Province Product: ProductKey, Color, SubCategory, Category OrderDate: Date, Month, Quarter, Year Тогда агрегат по имени (0000, 0001, 0001) означает: Битовые вектора – измерения в том порядке, как они входят в куб Биты соответствуют атрибутам каждого измерения Бит установлен = атрибут участвует в агрегате, бит снят = атрибут входит туда как (All) • Т.е. атрибут вида (0000, 0001, 0001) – это примерно следующая таблица Агрегаты Category Year Sales Amount Order Quantity ... ... ... Bikes Bikes Bikes Accessories Accessories Accessories Components Components Components Clothing Clothing 2005 2006 2007 2005 2006 2007 2005 2006 2007 2005 2006 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... Остальные атрибуты входят в нее как (All) Или Default Member 460 380 540 44 21 49 92 77 98 235 212 ... Пусть в кач-ве неаддитивной меры выступает кол-во уникальных клиентов Т.е. используется DISTINCT COUNT по CustomerID Это будет означать декартово произведение предыдущей агрегатной таблицы на столбец CustomerID Отсюда, в Measure Group допускается только одна неаддитивная мера, иначе сильный overhead Партиционировать лучше по CustomerID, тогда можно суммировать результаты партиций Если партиционировать не по DISTINCT COUNT-атрибуту, потребуется делать UNION агрегатных таблиц с партиций Полуаддитивные меры типа AverageOfChildren, FirstChild, LastChild, FirstNonEmpty, ... Также требуют включения в агрегаты гранулярного атрибута Пример: сумма нарастающим итогом, итог за период – последний день периода Если существует агрегатная таблица, в которую входит поле «День», запросы будут отрабатывать быстрее All All Q1 Jan Feb Mar Q2 Apr May Jun Q3 Jul Aug Sep Q4 Oct Nov Dec WA Seattle Redmond CA Los Angeles San Francisco San Diego All WA Seattle Redmond CA Los Angeles San Francisco San Diego All Q1 Jan 12 43 10 - 83 Feb 32 - 25 32 75 Mar - 23 18 - 14 Apr 23 - 9 23 - May 19 54 19 19 24 Jun 23 59 - 23 65 Jul - 12 - - 12 Aug - 34 - - 8 Sep 11 65 11 17 65 Oct 21 3 21 - 88 Nov 23 - 23 23 31 Dec 12 6 12 12 - Q2 Q3 Q4 Синтаксис: Calculate [<subcube>]; В случае, если подкуб не задан, подразумевается текущий подкуб В начале выполнения скрипта текущий подкуб равен всему кубу Не оказывает влияния на вычисляемые члены Заполняет каждую ячейку подкуба агрегированными данными с листьев Без CALCULATE и определенных пользователем формул куб будет содержать только листовые фактовые данные Дизайнер расчетов вставляет CALCULATE по умолчанию в качестве первой команды скрипта All All WA Seattle Redmond Los Angeles CA San Francisco San Diego 1237 475 176 299 762 148 149 465 367 110 44 66 257 53 32 172 Jan 148 55 12 43 93 10 - 83 Feb 164 32 32 - 132 25 32 75 Mar 55 23 - 23 32 18 - 14 360 178 65 113 182 28 65 89 Apr 55 23 23 - 32 9 23 - May 135 73 19 54 62 19 19 24 Jun 170 82 23 59 88 - 23 65 235 122 11 111 113 11 17 85 Jul 24 12 - 12 12 - - 12 Aug 42 34 - 34 8 - - 8 Sep 169 76 11 65 93 11 17 65 275 65 56 9 210 56 35 119 Oct 133 24 21 3 109 21 - 88 Nov 100 23 23 - 77 23 23 31 Dec 42 18 12 6 24 12 12 - Q1 Q2 Q3 Q4 Cинтаксис: Scope(<subcube>); <assignment>; … End Scope; Направляет последующие вычисления в определенную область куба, т.е. в подкуб Если scope не задан, по умолчанию считается весь куб Scope + assignment заменяет собой понятие вычисляемых ячеек Может быть несколько вложенных Такое не представлялось возможным в вычисляемых ячейках SQL Server 2000 Родительский scope при этом наследуется Scope( Customers.Country.USA ); Scope( Customers.State.OR ); Scope( Customers.Gender.Male ); <Сейчас scope = USA, OR, Male> this = 1000; End Scope; End Scope; End Scope; Иерархия родительского scope может быть переписана Scope( Customers.Country.USA ); Scope( Customers.State.OR ); Scope( Customers.Gender.WA ); < Сейчас scope = USA, WA> this = 1000; End Scope; <Сейчас scope = USA, OR> this = 2000; End Scope; End Scope; All All WA Seattle Redmond Los Angeles CA San Francisco San Diego 1237 475 176 299 762 148 149 465 367 110 44 66 257 53 32 172 Jan 148 55 12 43 93 10 - 83 Feb 164 32 32 - 132 25 32 75 Mar 55 23 - 23 32 18 - 14 360 178 65 113 182 28 65 89 Apr 55 23 23 - 32 9 23 - May 135 73 19 54 62 19 19 24 Jun 170 82 23 59 88 - 23 65 Q1 Q2 Q3 235 Jul 24 Aug 42 Sep 169 Q4 275 SCOPE (CA) ... END SCOPE 122 11 111 113 11 17 85 12 - 12 12 - - 12 34 - 34 8 - - 8 76 11 65 93 11 17 65 65 56 9 210 56 35 119 Oct 133 24 21 3 109 21 - 88 Nov 100 23 23 - 77 23 23 31 Dec 42 18 12 6 24 12 12 - All All WA Seattle Redmond Los Angeles CA San Francisco San Diego 1237 475 176 299 762 148 149 465 367 110 44 66 257 53 32 172 Jan 148 55 12 43 93 10 - 83 Feb 164 32 32 - 132 25 32 75 Mar 55 23 - 23 32 18 - 14 360 178 65 113 1780 28 65 89 Apr 55 23 23 - 32 9 23 - May 135 73 19 54 62 19 19 24 Jun 170 82 23 59 88 - 23 65 235 122 11 111 113 11 17 85 Q1 Q2 Q3 SCOPE (CA) Q2 = [WA] * 10 END SCOPE Jul 24 12 - 12 12 - - 12 Aug 42 34 - 34 8 - - 8 Sep 169 76 11 65 93 11 17 65 275 65 56 9 210 56 35 119 Oct 133 24 21 3 109 21 - 88 Nov 100 23 23 - 77 23 23 31 Dec 42 18 12 6 24 12 12 - Q4 Централизованный репозиторий вычислений Много легче визуализировать зависимости Единое представление упрощает поддержку Вычисления над измерениями остаются в Dimension Store Унарные операторы Custom member formulas Процедурная модель выполнения Команды выполняются в порядке определения При необходимости автоматическм создается новый Calculation Pass Solve Order выбывает за ненадобностью – определяется порядком скриптов MDX-скрипт SCOPE + Assignment Устанавливает/обновляет значения или свойства ячеек К свойствам ячеек относятся: format, font, fore color, back color, … Calculated member Новый член в иерархии = новые ячейки в пространстве куба Вычисление значений ячеек аналогично assignment Named set Просто именованный набор ячеек Не влияет ни на пространство куба, ни на значения ячеек Прочие Custom rollups, unary operators KPIs Клиентское приложение может добавлять вычисления после MDX-скрипта Обычно вычисляемые члены или именованные множества Уровня сессии или запроса (предикат WITH) MDX-скрипт имеет глобальный scope Для каждого из 3-х масштабов поддерживаются свои кэши Модель вычислений Архитектура выполнения многомерного запроса Анализ производительности и методы оптимизации Парсинг Наполнение осей Вычисление значений ячеек Сериализация результирующего множества Рассмотрим запрос WITH MEMBER Measures.ContributionToParent AS „measures.Sales/(measures.Sales, Product.Currentmember.parent)‟ SELECT Product.[Product Family].members ON COLUMNS, Customer.Country.members ON ROWS FROM Sales WHERE Measures.ContributionToParent Клиентское приложение MDX-запрос Парсер Наполнение осей Formula Engine Analysis Services Drink Food Non-Consumable Canada Mexico USA WITH MEMBER Measures.ContributionToParent AS „measures.Sales/(measures.Sales, Product.Currentmember.parent)‟ SELECT Product.[Product Family].members ON COLUMNS, Customer.Country.members ON ROWS FROM Sales WHERE Measures.ContributionToParent Клиентское приложение MDX-запрос Парсер Наполнение осей Вычисление ячеек Операции над подкубами Calculation Engine s Кэши FE s Кэши SE Formula Engine SE Evaluation Engine Partition Data Query Storage Engine Analysis Services Drink Food Non-Consumable Canada (null) (null) (null) Mexico (null) (null) (null) USA 9.22% 71.95% 18.83% Клиентское приложение Сериализация результатов MDX-запрос Парсер Наполнение осей Вычисление ячеек Операции над подкубами Calculation Engine s Кэши FE s Кэши SE Formula Engine SE Evaluation Engine Partition Data Query Storage Engine Analysis Services Measures.ContributionToParent Canada Drink (null) Mexico (null) USA 9.22% Food (null) Non-Consumable (null) (null) (null) 71.95% 18.83% measures.[Unit Sales] = (Measures.Sales, Product.Currentmember.Parent) Drink Food Non-Consumable Canada (null) (null) (null) Mexico (null) (null) (null) USA $ 24,597.00 $ 191,940.00 $ 50,236.00 All Products / Canada (null) Mexico (null) USA $ 266,773.00 Одинаковый план: навигация, вычисления – повторяется для каждой ячейки Вычисление null’ов в то время, как мы заранее знаем, что там будет null В основе, в основном, две простые идеи Вычислять не-NULLовые ячейки Выполнять навигацию (.РrevМember, .Рarent, ...) один раз для блока вместо для каждой ячейки WITH MEMBER Measures.ContributionToParent AS „measures.Sales/(measures.Sales, Product.Currentmember.parent)‟ SELECT Product.[Product Family].members ON COLUMNS, Customer.Country.members ON ROWS FROM Sales WHERE Measures.ContributionToParent Например, известно, что Можно не вычислять дальше a/b, если a = null Родитель всех Product.[Product Family].* один - [Product].[All], это константа Нет нужды навигировать всякий раз к этой ячейке для каждого члена в Product.[Product Family].members Также известно, что SE хранит (и возвращает) только непустые данные Обычно пространство куба достаточно разрежено Значения существуют лишь для незначительной доли всевозможных комбинаций ключей измерений Drink Food Non-Consumable Canada (null) (null) (null) Mexico (null) (null) (null) USA 9.22% 71.95% 18.83% 3) …а все остальное есть NULL по определению Country Product Measure Value USA Drink Contribution to Parent 9.22% USA Food Contribution to Parent 71.95% USA Non-Consumable Contribution to Parent 18.83% Country Product Measure Value Country Product USA Drink Sales $24,597.00 USA USA Food Sales $191,940.00 USA NonConsumable Sales 2) Выполнить над ними вычисления (3 вместо 9) Measure All Products Sales Value $266,773.00 $50,236.00 1) Получить значения от Storage Engine. Они заведомо NOT NULL Основные арифметические операторы: *, /, +, Основные логические операторы: <, >, <=, >=, <>, = Основные унарные операторы: +, -, ~, веса Статические члены: напр., [Customers].[Seattle] Статические кортежи: напр., ([Customers].[Seattle], [Time].[2007]) Основные агрегатные функции (Sum, Min, Max, Aggregate) над: Статическими множествами: напр., Sum({USA, Canada}, Measures.Sales) Множествами из ф-ций .Members, PeriodsToDate, YTD/QTD/MTD, Crossjoin, Cousin, CurrentMember, оператора диапазона (“:”) над статическими множествами Полуаддитивные меры Measures.CurrentMember CurrentMember пока не прокатывает для других измерений IIF(cond, expr, NULL) IsEmpty Константными скалярами: напр., NULL, “строка”, 5, 3.14 Модель вычислений Архитектура выполнения многомерного запроса Анализ производительности и методы оптимизации Время выполнения запроса = время FE + время SE Следовательно, если вы недовольны временем выполнения, для начала нужно понять, кто съедает время: Formula Engine или Storage Engine, потому что подход к ним нужен разный Formula Еngine: однопоточный, сильно грузит CPU, сравнительно мало – диск Storage Еngine: все наоборот. Хотя CPU иногда тоже напрягает Первый вывод на тему «Кто виноват?» делается из Perfmon или Task Manager Второй вывод делается в профайлере, где показывается полное время запроса Время на SE есть общая длительность всех событий Query Subcube Для чистоты эксперимента используется XMLA-команда <ClearCache> Если вы не уверены, довольны вы временем выполнения или нет, значит, довольны Здоровый запрос к SE Видно как событие Query Subcube с большим Duration Чаще всего объясняется идущим следом событием Reading data from partition Что означает, что он полез в детальные факты по причине отсутствия необходимых агрегатов Инструменты для определения и создания необходимых агрегатов: Usage-Based Optimization Wizard, профайлер, Agg Manager Попал Промазал Если выстроить необходимые агрегаты не удается, продумайте политику эффективного партиционирования (см., напр., слайды про неаддитивные меры), чтобы облегчить ему процесс скана Туча мелких запросов к SE Видны как многочисленные события Query Subcube с незначительным Duration Чаще всего объясняется непрогретым кэшем SE Используйте CREATE CACHE, чтобы предвыбрать данные в кэш Напрягается FE Видно по 100%-й загрузке одного процессора По возможности переписать вычисления, чтобы использовать блоки вместо поячеечных расчетов (см. выше) Использовать методы оптимизации запросов (см. далее) В AS понятие пустой ячейки не тождественно NULL Пустой считается та, которая отсутствует в нижележащей таблице фактов, либо ее кортеж составляет невозможную в данном кубе комбинацию При процессинге NULL в мере интерпретируется как 0, если в св-вах меры не стоит NullProcessing = Preserve Напр., скидка NULL – это 0 или не считать такого клиента в DISTINCT COUNT? В вычислениях тоже: 1 + NULL = 1, а не NULL Большинство клиентов (Excel, ProClarity, ...) по умолчанию отображают непустые результаты, исключая члены без данных Для фактовых данных эта проверка происходит очень быстро Если с ячейкой связаны вычисления, может тормозиться в зависимости от сложности формулы, т.к. чтобы сказать пуста она или нет, ее сначала нужно рассчитать Когда таких ячеек много, тормоза могут стать ощутимыми Non_Empty_Behavior позволяет принять решение на основе проверки фактовых данных Create Member Measures.Profit As Measures.Sales – Measures.Cost, Format_String = "Currency", Non_Empty_Behavior = {Measures.Sales, Measures.Cost}; Filter(Customer.Members, Customer.CurrentMember.Properties(“Gender”) = “Male”) Exists(Customer.Members, Gender.[Male]) Закадровая функциональность Autoexists позволяет автоматически исключать пустые комбинации при CrossJoin атрибутов City.Seattle * State.Members = {(Seattle, WA)} (Seattle, OR), (Seattle, CA) и т.п. заведомо не существуют и будут исключены Ее также использует ф-ция Exists Exists (Customer.Customer.Members, Customer.Gender.Male) = только клиенты мужского пола Требует, чтобы иерархия по атрибуту была enabled Работает по атрибутам, относящихся к одному и тому же измерению Внимание при проектировании измерений Напр., а) включать Department атрибутом в измерение Employee или б) делать отдельным измерением? б) делает возможным анализ в новом срезе, но при этом лишаемся Autoexists для быстрого получения всех сотрудников департамента Create Member Measures.Sales As Iif(Currency.CurrentMember Is Currency.USD, Measures.SalesUSD, Measures.SalesUSD * Measures.XRate); Create Member Measures.Sales As Null; Scope(Measures.Sales, Currency.Members); This = Measures.SalesUSD * Measures.XRate; Scope(Currency.USA); This = Measures.SalesUSD; End Scope; End Scope; SCOPE предпочтительнее, т.к. в этом случае Query Execution Engine заранее знает область вычислений для каждого бизнесправила и может выбрать оптимизированный путь выполнения над этим диапазоном ячеек Условия оцениваются динамически для каждой ячейки в области Create Set RichCustomers As Filter(Customer.Members, CInt(Customer.CurrentMember.Properties(“Salary”)) > 100,000); Create Set RichCustomers As Filter(Customer.Members, Salary.MemberValue > 100,000); MemberValue – внутреннее св-во члена, введенное в SQL Server 2005 Если таких ситуаций попадается много, имеет смысл рассмотреть создание отдельной группы мер над таблицей измерения типа Salary или Population IIF ([Customer].[Company].Name = “Microsoft”, … IIF ([Customer].[Company] IS [Microsoft], … Сравнение IDов быстрее, чем сравнение строк (Time.CurrentMember.PrevMember,Measures.CurrentMember).Value Time.PrevMember MDX-выражения оцениваются в каком-либо контексте Контекст включает текущий член для каждой иерархии (по умолчанию All) Всегда, если не оговорено явно, задействуется CurrentMember Специально указывать его не нужно – негативно сказывается на производительности Value – св-во ячейки, подразумеваемое по умолчанию. Также нет нужды указывать его специально Не стоит валить на него все подряд Всегда лучше вычислять заранее во время процессинга, чем на ходу во время запроса По возможности перемещать вычисления из MDXскрипта в DSV Именованные запросы Вычисляемые колонки Кроме того, SSAS 2005 обладает встроенными возможностями поддержки достаточно замысловатых сценариев Неаддитивные меры, измерения «многие-ко-многим», ... Прежде, чем писать custom MDX в скрипте, убедитесь, что вы их задействовали по максимуму При планировании MDX-вычислений над обширными областями с многочисленными итерациями по возможности избегайте функций позднего связывания LinkMember, StrToSet, StrToMember, StrToValue, LookupCube Они оцениваются во время выполнения, следовательно, Query Execution Engine не в состоянии заранее отобрать эффективный Еxecution Рath Используйте Format_String вместо присвоения 0, Null, “N/A”, “-” и т.д. пустым ячейкам Примечание: клиентское приложение должно поддерживать это свойство Для выражений типа “expr1 * expr2” выражение, охватывающее бОльшую область куба, лучше ставить слева Sales * ExchangeRate вместо ExchangeRate * Sales Sales * 1.15 вместо 1.15 * Sales MDX-функция Aggregate предпочтительнее в вычислениях вместо непосредственного указания Sum, Min, Max, ... Microsoft SQL Server Analysis Services http://www.microsoft.com/sql/technologies/analysis/default.mspx Microsoft BI http://www.microsoft.com/bi/default.aspx Страница Ричарда Ткачука http://sqlserveranalysisservices.com Страница Моши Пасуманского http://sqljunkies.com/weblog/mosha Российская группа пользователей SQL Server http://sql.ineta.ru 23 октября. Москва Данила Корнев, Microsoft "ADO.NET Entity Framework« Иван Бодягин, RSDN "LINQ и Linq 2 SQL в качестве lightweight ORM" http://sql.ineta.ru/Events/EventInfo.aspx?ID=d2072a2d-cbc0-40f4-84d0a5ffd91bd0f6 31 октября. Ростов-на-Дону Сергей Моисеенко, Южный Федеральный Университет "Проект SQL-EX.RU и опыт использования Microsoft SQL Server " Алексей Шуленин, Microsoft "Новые возможности SQL в реализации Microsoft SQL Server 2008" http://sql.ineta.ru/Events/EventInfo.aspx?ID=75f5d7c8-a02f-4930-9b5c3ca83887d770 http://sql.ineta.ru/Events/Events.aspx © 2007 Microsoft Corporation. All rights reserved. Microsoft, Windows, Windows Vista and other product names are or may be registered trademarks and/or trademarks in the U.S. and/or other countries. The information herein is for informational purposes only and represents the current view of Microsoft Corporation as of the date of this presentation. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information provided after the date of this presentation. MICROSOFT MAKES NO WARRANTIES, EXPRESS, IMPLIED OR STATUTORY, AS TO THE INFORMATION IN THIS PRESENTATION.