Metrika

10 января 2014 г.

MySQL: Определение количества строк и размера таблиц запросом

SELECT table_name, table_rows, ROUND(((data_length + index_length + data_free) / 1024 / 1024), 2) "SpaceMB" 
FROM information_schema.TABLES 
WHERE table_schema = "DB_NAME"
ORDER BY data_length + index_length + data_free DESC
Поле table_rows не содержит точного количества строк, т.е. оно отличается от количества посчитанного с помощью COUNT(*), но его можно использовать для примерной оценки той или иной таблицы

SQL Server: Определение количества строк и размера таблиц запросом

SELECT t.name, p.rows,
 SUM(a.total_pages) * 8.0 / 1024 TotalSpaceMB, 
    SUM(a.used_pages) * 8.0 / 1024 UsedSpaceMB, 
    (SUM(a.total_pages) - SUM(a.used_pages)) * 8.0 / 1024 UnusedSpaceMB
FROM sys.tables t
INNER JOIN sys.indexes i ON t.object_id = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
WHERE i.object_id > 255 
GROUP BY t.name, p.rows

Запрос будет работать точно начиная с  SQL Server 2008. Я не помню, когда появились эти вьюхи sys.tables и пр. Поэтому на SQL Server 2005 не знаю будет работать или нет. На SQL Server 2000 точно не будет работать.

27 августа 2013 г.

JavaScript: Построение выпуклого многоугольника по конечному однородному множеству точек

Построение многоугольника будет производиться алгоритмом QuickHull. Код можно брать и использовать. Я его проверял на массивах до 30000 точек, не лагало. Если найдете баги, пишите в коменты.

// Построение выпуклого многоугольника по массиву однородных точек
function Point() {
    this.x = 0;
    this.y = 0;
}

Point.prototype.init_by_building = function (building) {
    this.x = building.lng;
    this.y = building.lat;

    return this;
}

Point.prototype.init = function (x, y) {
    this.x = x;
    this.y = y;

    return this;
}

// Отрезок
function Line(x1, y1, x2, y2) {
    this.start = new Point().init(x1, y1);
    this.end = new Point().init(x2, y2);

    // Расчет дистанции до точки
    this.distanceToPoint = function (point) {
        var Vy = this.end.x - this.start.x;
        var Vx = this.start.y - this.end.y;

        return Vx * (point.x - this.start.x) + Vy * (point.y - this.start.y);
    };

    this.findMostDistantPoint = function (points) {
        var max_dist = 0;
        var max_point = null;
        var new_points = [];

        for (var idx in points) {
            var pt = points[idx];
            var d = this.distanceToPoint(pt);

            if (d > 0) new_points.push(pt);
            else continue;

            if (d > max_dist) {
                max_dist = d;
                max_point = pt;
            }
        }

        return {'maxPoint':max_point, 'newPoints':new_points}
    };
}

function QuickHull(points) {
    this.points = points;

    this.all_lines = [];

    this._getBaseLine = function () {
        var max_x = -10000;
        var min_x = 10000;
        var max_point, min_point;

        for (var idx in points) {
            var pt = points[idx];
            if (pt.x > max_x) {
                max_point = pt;
                max_x = pt.x;
            }

            if (pt.x < min_x) {
                min_point = pt;
                min_x = pt.x;
            }
        }

        return new Line(min_point.x, min_point.y, max_point.x, max_point.y);
    };

    this._buildConvexHull = function(line, points) {
        this.all_lines.push(line);

        var convex_hull_base_lines = [];

        var t = line.findMostDistantPoint(points);

        // Если есть точка за пределами линии
        if (t.maxPoint != null) {
            var pt = t.maxPoint;
            convex_hull_base_lines = convex_hull_base_lines.concat(this._buildConvexHull(new Line(line.start.x, line.start.y, pt.x, pt.y), t.newPoints));
            convex_hull_base_lines = convex_hull_base_lines.concat(this._buildConvexHull(new Line(pt.x, pt.y, line.end.x, line.end.y), t.newPoints));

            return convex_hull_base_lines;
        }
        else { return [line]; }// Нет точки за пределами линии
    };


    this.Start = function () {
        var base_line = this._getBaseLine();

        return [].concat(this._buildConvexHull(base_line, this.points), this._buildConvexHull(new Line(base_line.end.x, base_line.end.y, base_line.start.x, base_line.start.y), this.points));
    };
}

Как использовать:
var qh = new QuickHull(points);
var cvx_lines = qh.Start();

Необходимо передать массив точек в конструктор QuickHull. Я использовал алгоритм для построения полигона на карте, поэтому у меня в объекте Point два конструктора: init_by_building, куда передается объект здания, который в свою очередь имеет свойства lat (широта) и lng (долгота); и init, куда передются координаты x и y точки.
После выполнения в массиве cvx_lines будут содержаться объекты типа Line, которые формируют многоугольник.

18 июля 2013 г.

Аналог ROW_NUMBER в MySQL

В статье T-SQL: Функция ROW_NUMBER я рассказывал, как выводить номер строки в запросе в Microsoft SQL Server. Тут я расскажу, как выводить номер строки в запросе в MySQL.

Для того, что бы это работало, в строке соединения с базой должно быть прописано: allow user variables=true;

Запрос, выводящий номера строк выглядит вот так:
SELECT @row:=@row+1 row_number, t.* 
FROM tbl t
INNER JOIN (SELECT @row := 0) c

26 июня 2013 г.

Base64 кодирование в JavaScript

Если нужно кодировать и декодировать строки в/из Base64 в браузере,  подойдет вот эта библиотека: http://jsbase64.codeplex.com/

Только она, из найденных мной, работает с русским языком в UTF-8.


11 марта 2013 г.

JMeter: Нагрузочное тестирование MySQL

Речь пойдет об использовании инструмента Apache JMeter™ для нагрузочного тестирования сервера MySQL.

JMeter - это опенсорсный продукт на Java. Его можно использовать для нагрузочного тестирования Web-сервера, FTP-сервера, сервера БД и пр. Обладает хорошим пользовательским интерфейсом и гибкими настройками.

В сети довольно много материалов по JMeter и понять как его использовать для задач нагрузочного тестирования различных систем не трудно. Здесь я опишу проблему, с которой столкнулся я, и ее решение.

Мне было необходимо погонять различные SELECT-запросы к MySQL для определения наиболее проблемных и для их оптимизации. Для этого, сначала,надо было подключить JMeter к серверу MySQL, а затем выполнять SQL-запросы с различными параметрами в условиях.

Как подключать JMeter к MySQL и создавать запросы к базе описано здесь: http://jmeter.apache.org/usermanual/build-db-test-plan.html.  Проблема, которая обычно возникает во время подключения к базе, - это установка драйвера MySQL для Java. Его нужно скачать здесь: http://dev.mysql.com/downloads/connector/j/, распаковать архив и положить файл mysql-connector-java-5.1.23-bin.jar в папку lib в папке установки JMeter.

Мне было интересно делать запросы с реальными условиями, т.е. для каждого нового запроса к базе ставить свои параметры. Для этого я сохранил необходимые значения в CSV файл. Для того, что бы сгенерить выражение для получения данных для запроса из CSV файла, воспользовался Function Helper Dialog (в главном меню: Options->Function Helper Dialog):


Далее добавил полученное выражение как параметр в SQL-запрос:








7 февраля 2013 г.

IIS: проблемы с генерацией статического контента в ASP.NET

Если мне нужно сгенерировать на сайте ASP.NET статический контент (например JavaScript-файл), то я делаю это через Global.asax в обработчике события Application_BeginRequest:


    void Application_BeginRequest(object sender, EventArgs e)
    {
        string req = HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.ToLower();
     
        // Перенаправление обращений к JavaScript файлам на их динамическое формирование      
        if (req.CompareTo("~/scripts/item_types.js")==0)
        {
            Context.RewritePath("~/js_item_types.aspx");
            return;
        }
    }
Файл  js_item_types.aspx в ответ выдает необходимый контент, например после обращения к БД.

Все отлично работает, когда я запускаю проект на локальной машине под управлением встроенного в VS Web-сервера, но перестает работать, когда я развертываю проект на IIS сервере. При обращении к файлу /scripts/item_types.js возникает ошибка 404.

Проблема в том, что для IIS файлы *.js это статический контент и он проверяет их наличие или отсутствие еще до того, как передаст управление в ASP.NET

Что бы так не происходило, надо указать IIS, что этот файл обрабатывается в ASP.NET. Для этого в панели управления IIS выбираем сайт, далее тыкаем в иконку Handler Mappings, откроется список и в него надо добавить новый хендлер. Справа нажимаем Add Managed Handler в появившемся окне заполняем поля: Request path -> *item_types.js, Type -> ASP.global_asax, Name -> JS_item_types и давим OK.

29 декабря 2012 г.

Unity-клиент и IIS

Если вы разместите HTML страницу и Unity приложение, которое подгружается в эту страницу, в IIS, то скорее всего после загрузки HTML страницы, Unity-плеер скажет, что не может загрузить приложение: Failed to load data file.

Для того, что бы заработало, надо в IIS прописать MIME-type для расширения .unity3d.






Photon Server и Cross Domain Policy

Речь пойдет о соединении клиентом (в моем случае Unity-клиент встроенный в Web-страницу) по TCP сокету к Photon серверу, стоящему на машине под управлением Windows Server 2008.

Все привыкли, что кросс-доменная политика сервера описывается в файле crossdomain.xml, но в случае с сервером Photon, когда соединение происходит через сокет, будет использоваться файл C:\Photon\deploy\Policy\assets\socket-policy.xml


По-умолчанию, он будет позволять соединения с любых доменов на необходимые порты:


<cross-domain-policy>
      <allow-access-from domain="*" to-ports="5055,5056,5057,4530,4531,4532,9090" />
</cross-domain-policy>

Так же, когда вы запускаете PhotonControl и далее выполняете Install Service, Photon создаст в фаерволе необходимую запись, что бы соединения проходили.

По-умолчанию, Photon ждет соединения по TCP на порту 4530, а по UDP на порту 5055. Но кроссдоменная политика проверяется на TCP порту 843. Правило фаервола позволяет это соединение, но в моем случае, я не создал правило port-mapping на порт 843, поэтому соединение телнетом на порт 4530 проходили, но Unity-клиент не запускался.

17 ноября 2012 г.

IIS 7.5: Сложности в настройке компрессии GZip

Столкнулся с такой проблемой. При переносе WEB-сервера на другую машину (Windows Server 2008 R2) перестала работать компрессия динамических страниц, хотя в Web.config все было прописано как и раньше:


<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files">
             <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" />
             <dynamicTypes>
                 <add mimeType="text/*" enabled="true" />
             </dynamicTypes>
</httpCompression>

Оказалось, что при установке на сервере роли Web-сервера, я не поставил сервис Dynamic Content Compression:

Ответ нашел тут: http://stackoverflow.com/questions/3591155/iis-7-5-asp-net-4-gzip-compression

6 ноября 2012 г.

JavaScript: Форматирование числа - добавление пробелов

Весьма полезная функция - добавить пробелы, которые разделяют тысячи, миллионы и т.д.:

function addCommas(nStr)
{
  nStr += '';
  x = nStr.split('.');
  x1 = x[0];
  x2 = x.length > 1 ? '.' + x[1] : '';
  var rgx = /(\d+)(\d{3})/;
  while (rgx.test(x1)) {
    x1 = x1.replace(rgx, '$1' + ' ' + '$2');
  }
  return x1 + x2;
}

Взял тут:

27 марта 2012 г.

OLAP: Как обращаться к кубам через OLE DB

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

Я использовал подключение к кубам через OLE DB. Для этого понадобиться драйвер MSOLAP (Microsoft OLE DB Provider for Analysis Services 10.0), на клиенте у меня этого драйвера не было, поэтому все запросы шли через SQL Server. Посмотреть список установленых OLE DB провайдеров в SQL Server можно хранимой процедурой  master..xp_enum_oledb_providers

Варинат 1 (через OPENROWSET):


SELECT
          "[DimServices].[Name].[Name].[MEMBER_CAPTION]" service,
          "[Measures].[CountServiceAccounts]" regs
FROM OPENROWSET
(
          'MSOLAP',
          'Provide=MSOLAP;Integrated Security=SSPI;Persist Security Info=False;Data Source=127.0.0.1;Initial Catalog=cubeRegs;',
          'select {[Measures].[CountServiceAccounts]} on 0, { [DimServices].[Name].Children } on 1 from                            [Регистрации]'
)

Если в MDX-запросе к кубу используется иерархия, то она будет преобразована в набор колонок, например если в иерархии времени есть год, месяц и день, то в результате получиться три разных колонки таблицы.

Проблема с OPENROWSET в том, что эту команду не может исполнять пользователь с ограниченными правами. Если не только админ должен выполнять запросы к кубам, то нужно использовать OPENQUERY.

Вариант 2 (OPENQUERY):


Сначала надо создать линкованный сервер:

EXEC master.dbo.sp_addlinkedserver @server = '127.0.0.1', @srvproduct='MSOLAP', @provider='MSOLAP', @datasrc='127.0.0.1', @catalog='cubeRegs'

Далее, нужно прописать пользователя, который будет обращаться к линкованному серверу:

EXEC sp_addlinkedsrvlogin '127.0.0.1', 'false', 'db_user', 'olap_user', 'olap_user_psw'

db_user - пользователь, который будет обращаться к линкованному серверу;
olap_user - пользователь, который имеет прова доступа к кубу
olap_user_psw - пароль  olap_user

Теперь можно обращаться к кубам:

SELECT
             "[DimServices].[Name].[Name].[MEMBER_CAPTION]" service,
             "[Measures].[CountServiceAccounts]" regs
FROM
OPENQUERY
(
          [127.0.0.1],
          'select {[Measures].[CountServiceAccounts]} on 0, { [DimServices].[Name].Children } on 1 from [Регистрации]'
)

Отмечу так же, что как в первом, так и во втором случае запрос вернет колонки типа ntext, с которым работать не очень удобно, поэтому надо прописать преобразование в нужные типы:

SELECT
           CAST("[DimServices].[Name].[Name].[MEMBER_CAPTION]" as varchar) service,
           CAST(CAST("[Measures].[CountServiceAccounts]" as varchar) as int) regs
FROM
OPENQUERY
(
           [127.0.0.1],
         'select {[Measures].[CountServiceAccounts]} on 0, { [DimServices].[Name].Children } on 1 from [Регистрации]'
)

Если нужно выполнить XMLA запрос

 

XMLA запросы нужны для управления кубами, например процессинг куба, удаление партиций, создание партиций. Ни OPENQUERY, ни OPENROWSET не будут выполнять запрос XMLA, а будут выдавать ошибку, т.к. они не могут принять запрос в формате XML.
Для того, что бы выполнить запрос XMLA нужно создать линкованный сервер (как для OPENQUERY), разрешить на нем RPC:

EXEC sp_serveroption @server='127.0.0.1', @optname='rpc', @optvalue='TRUE'
EXEC sp_serveroption @server='127.0.0.1', @optname='rpc out', @optvalue='TRUE'

И выполнить следующую команду:

EXEC ('.. Some XMLA Query ..') at [127.0.0.1]

1 марта 2012 г.

Python: преобразование строки в дату

Сейчас много времени провел в поисках пути преобразования в Python строки, где дата представлена в ISO формате: "2010-05-14T15:33:04.287+02:00" в объект datetime

Много гуглил, в итоге нашел ответ вот здесь: http://www.seehuhn.de/pages/pdate , толковая дока, много разнообразных примеров на тему преобразования из разных типов в дату.

В моем случае подошла либа dateutil:
import dateutil.parser

dt = dateutil.parser.parse("2010-05-14T15:33:04.287+02:00")

Пропал значок "Безопасное извлечение устройства" в Windows

Столкнулся с такой проблемой - в трее пропал значок "Безопасное извлечение устройства". Решение нашел такое - Пуск -> Выполнить и туда пишем rundll32 shell32.dll,Control_RunDLL hotplug.dll

21 декабря 2011 г.

C#: Конкатенация строк, StringBuilder vs +

В Сети много пишут о работе со строками в .Net. Что касательно конкатенации, я давно уже знал, что при небольших конкатенациях (несколько строк) лучше использовать +, а при конкатенации в циклах лучше использовать StringBuilder. Но ни когда не подозревал на сколько лучше тот или иной метод.
При написании прототипа нового проекта вообще не обратил на это внимания и для вывода массива объектов в JSON написал вот так:
string s_json = "[";

foreach (Building b in arr) {
     s_json += b.ToJSON() + ",";
}

На маленьких массивах проблем с производительностью не наблюдалось, но при тестах на более менее вменяемых данных (около 15000 записей) этот код на ноуте выполнялся 2 мин!!! Epic Fail!!

Тот же код с использованием StringBuilder на тех же данных выполняется секунду!! Прирост производительност более чем в сто раз на не очень больших объемах данных:

StringBuilder sb_json = new StringBuilder("[");

foreach (Building b in arr) {
            sb_json.AppendFormat("{0},", b.ToJSON());
}
Интересные ссылки:
1. Работа со строками. Строковые функции
2.  String concatenation the fast way...maybe not

28 ноября 2011 г.

Сегментирование данных в SSAS

Статья о сегментировании (кластеризации) данных с помощью SQL Server Analytsys Services на примере сегментирования  пользователей он-лайн шутера PointBlank.

Что такое сегментирование (кластеризация). 
Сегментирование - это группировка данных по некоторым признакам. Например есть пользователи, они играют часто или редко, платят мало или много. Т.е. есть два измерения, по которым бьются пользователи, в каждом измерении два значения, т.е. возможно сделать 4 сегмента. В принципе при маленьком количестве измерений специальных средств для построения сегментов можно и не использовать, определить сегменты можно с помощью OLAP-кубов или с помощью SQL-запросов к базе. Но, если измерений много, то разбить данные на сегменты становиться довольно сложно и необходимо применять специальные алгоритмы сегментирования.

Для чего нужно сегментирование.
Сегментирование нужно для более качественного и глубокого исследования данных, например разбиение пользователей на сегменты в игре, может показать какие люди играют в игру, чего они хотят от игры, что им нравиться, а что нет. Далее можно определять сегменты пользователей раз в какой-то промежуток времени и изучать переходы пользователей из одних сегментов в другие. Например, если были определены сегменты пользователей "Новички", "Платящие", "Неактивные", можно определять, кто переходит из сегмента "Новички" в "Платящие", такую информацию можно использовать например в маркетинговых кампаниях для определения наиболее эффективных каналов привлечения пользователей. Изучая переходы из сегмента "Платящие" в "Неактивные", можно определять недостатки в сервисе, можно привести какие-либо акции по возвращению "Неактивных" в "Платящие" и т.д.

Этапы большого пути.
Для начала надо создать таблицу с данными, на основе которых будут строиться сегменты. В принципе, если данных не много, можно использовать вьюху. В таблице должен быть primary key, по которому можно идентифицировать пользователя и поля, которые содержат необходимые параметры пользователей.
Для примера я создам сегменты на основе таблицы с небольшим количеством параметров. На самом деле выбор правильных параметров для анализа само по себе является не простой задачей, и нужно быть готовым, что таблица с исходными данными в процессе построения сегментов может часто менять свою структуру и часто пересчитываться. Например с одной стороны можно решить, что параметр "количество убийств" хорошо характеризует успешность игрока, но на самом деле один игрок может набить 100 убийств за 3 матча, а другой за 10, более подходящий аргумент - количество убийств за матч и т.д.
Создаю таблицу:
create table _dm_test(
    user_id int not null primary key,
    perc_win_per_match int null,            -- Процент побед к матчам
    perc_kill_per_death int null,            -- Процент фрагов к смертям
    kill_per_match int null,                -- Фраги за матч
    perc_headshot_per_kill int null,        -- Процент хедшотов к фрагам
    connecttime_min int null,                -- Продолжительность сессий
    match_per_play_day int null,            -- Количество матчей за игровой день
    days_of_play int not null,                -- Количество игровых дней за месяц
    days_created int null,                    -- Возраст персонажа в днях
    rank_current int not null,                -- Текущее звание игрока
    rank_range varchar(100) null            -- Диапазоны званий
)
Агрегирую в нее данные за последние 7 игровых дней за 30 календарных дней.

Затем в BI Studio создаю проект Analysis Services Project. В проекте создаю DataSource и DataView, который содержит таблицу _dm_test.
После этого в проекте тыкаю правой кнопкой на Mining Structures и выбираю New Mining Structure:
  




Таблица _dm_test выбирается как Case для модели. Nested - используется в более сложных случаях анализа, например для учета покупок пользователей, более подробно можно почитать в книге
Далее проставляем для колонок, как они будут использоваться в модели:

Далее для колонок проставляем Content Type - это то как данные из колонки будут распределяться в модели по кластерам, например Continuous будут показаны в виде распределения вокруг среднего значения (имеет смысл, когда данные в колонке изменяются в широком диапазоне величин и довольно равномерно), Discrete - дискретизация будет взята из таблицы (имеет смысл, когда необходимо разбить данные самому, без участия алгоритма разбиения на сегменты), Discretized - данные будут разбиты на диапазоны алгоритмом построения сегментов. В данном примере Rank Range я предопределил в таблице, поэтому ставлю ему Discrete, остальным данным, кроме User Id, ставлю Discretized:

 Выбираем процент данных, которые будут использоваться для проверки построенной модели:

 Последняя страница создания модели - ввод имен структуры и модели:

 Окно редактирования Mining Structure будет иметь такой вид:

Окно редактирования модели:
 Здесь можно более подробно выставить параметры для обработки модели. Например на изображении сверху я для аргумента Connecttime Min выставляю количество диапазонов (DiscretizationBucketCount =10), на которые его будет разбивать модель (по умолчанию 5). Так же можно поменять Content Type.
Так же для полей можно выбрать то, как они будут участвовать в построении сегментов. Все поля со значением Input будут участвовать в алгоритме, Ignore - не будут участвовать вообще, Predict - используется в алгоритмах предсказания для того, что бы использовать аргумент и на входе алгоритма и на выходе, в построении кластеров его роль не ясна, PredictOnly - для алгоритма кластеризации данные будут просто распределены по кластерам, но в самом разбиении участвовать не будут. В моем случае, я не хочу чтобы поле Rank Current участвовало в разбиении, а поле Rank Range должно быть просто распределено по кластерам:

В этом же окне можно уточнить параметры алгоритма, для этого надо выделить заголовок модели и в свойствах перейти в AlgorithmParameters:
 У меня параметров модели не много, поэтому ограничу количество кластеров (сегментов) пятью - значение CLUSTER_COUNT = 5 (по умолчанию 10):

Далее процессим модель, тойже кнопкой, что и процессим кубы:

После того, как процессинг завершен можно посмотреть результат во вьюере. Тут видно как данные разбились по кластерам. Причем чем ближе кластеры друг к другу, тем они больше похожи, т.е. в дальнейшем можно их изучить подробнее, что бы посмотреть чем они отличаются и возможно уменьшить количество кластеров:

Так же тут видны связи между сегментами (черные линии). Слева бегунком можно отфильтровать более сильные или слабые связи. На подсветку кластеров влияют выпадающие списки Shading Variable и State. На изображении сверху видно, что 32% пользователей попали в Кластер 1 (проценты отображаются в контроле Density), а на изображении снизу, видно, что большинство тех, у кого процент побед за матч больше 70 попали в Кластер 3:

Далее переходим к профайлам кластеров, здесь удобно сравнивать кластеры и смотреть чем они друг на друга похожи или чем отличаются. В общем тут начинается работа для аналитика - регулируя количество и набор параметров, количество кластеров, количество диапазонов, на которые бьются данные, создать такое количество кластеров, которое можно описать, т.е. если два кластера на столько близки, что их нельзя описать по-разному, то количество кластеров нужно уменьшать:

После нескольких итераций я подобрал наиболее удовлетворяющие меня параметры, теперь для более удобной работы можно переименовать кластеры, для этого нужно тыкнуть на заголовке кластера правой кнопкой мыши и выбрать Rename Cluster:



После всех настроек кластеры стали выглядеть вот так:

Что дальше?
Просто разбить данные на кластеры не достаточно, их надо как то использовать. Как минимум надо понять, кто в какой кластер попал. Для этого нужно создать SSIS пакет, который пропустит данные из исходной таблицы через модель и выдаст их в результирующую таблицу.
Создаю в BI Studio проект Integration Services Project, перетаскиваю в Control Flow элемент Data Mining Query Task:

Двойным кликом перехожу в его Editor:
 На вкладке Mining model нажимаю кнопку New, что бы создать новое подключение к SSAS. В списке провайдеров надо выбирать Microsoft OLE DB Provider for Analysis Services, провайдер ... for Data Mining Services у меня не заработал:

В выпадающем списке Initial catalog выбираю модель:
В итоге модель появляется в списке Mining models:
Далее переходим во вкладку Query, где пишеться DMX-запрос к модели или строиться через мастер (кнопка Build New Query):
Тут нужно определить Mining Model, через которую пропускаются данные; Input Table, которая содержит данные, в моем случае я использую ту же таблицу; и внизу формы нужно выбрать поля и/или Prediction Functions, которые будут выведены в результирующую таблицу, мне нужно выбрать кластер и ID пользователя.
После этого во вкладке Query появиться DMX-запрос:
Далее переходим во вкладку Output и выбираем там результирующую таблицу:

Все. После этого запускаем пакет и в таблице _dm_test_out у нас будет связаны ID пользователя и названия кластеров.

Полезные ссылки
2. Так же советую книгу "Программируем коллективный разум" Тоби Сегаран - просто великолепная книга про глубокий анализ данных;
3. Статья о датамайнинге в он-лайн игре Айон:  http://habrahabr.ru/blogs/data_mining/134163/

10 октября 2011 г.

SSRS: Как объединить данные из двух датасетов в одну таблицу

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

В моем случае было два датасета (ds_regs и ds_sales), обращение шло к двум разным OLAP-кубам. В каждом было поле даты. В одном кубе это была дата регистрации пользователей, во втором - дата платежей.

Далее необходимо для таблицы выбрать в качестве источника данных один из датасетов, я выбрал ds_regs.  Поля из этого датасета будут добавляться в таблицу как обычно. Для добавления полей из второго датасета нужно добавить пустые ячейки и в Expression добавить функцию Lookup:
=Lookup(Fields!Day.Value, Fields!Day.Value, Fields!Sales.Value, "ds_sales")
Аргументы следующие: 1 - Ключевое поле из первого датасета (из ds_regs); 2 - ключевое поле из второго датасета (из ds_sales); 3 - поле из второго датасета, которое надо отобразить; 4 - название второго датасета.

Ключевые атрибуты можно варьировать, например, у меня из одно куба день выводился просто числом, а из второго куба с добавлением слова "Day" перед самим числом, поэтому функцию Lookup пришлось записать вот так:
=Lookup("Day " + Fields!Day.Value, Fields!Day.Value, Fields!Sales.Value, "ds_sales")

30 сентября 2011 г.

MDX: Запрос для вывода статистики с нарастающим итогом

Допустим есть куб "Регистрации", в котором лежат реги на неком сервисе по дням. Т.е. в кубе есть мера [Measures].[Accounts] и измерение [DimRegDate] с иерархией [DimRegDate].[Year-Month-Day]. Необходимо вывести за некий промежуток дат общее количество рег на этот день и количество новых рег вэтот день.
Для этого нужно создать два мембера - один с общим количеством рег за все время и второй с суммой рег за дни, которые идут после текущего дня. Потом из первого вычесть второй.
Запрос будет выглядеть вот так:

WITH MEMBER [Measures].[TotalAccounts_ALL] AS SUM([Measures].[Accounts], [DimRegDate].[Year-Month-Day].[All])
MEMBER [Measures].[TotalAccounts_After] AS SUM([DimRegDate].[Year-Month-Day].nextmember:null, [Measures].[Accounts])
MEMBER [Measures].[TotalAccounts] AS [Measures].[TotalAccounts_ALL] - [Measures].[TotalAccounts_After]

SELECT { [Measures].[TotalAccounts], [Measures].[Accounts]}  ON 0,
{ [DimRegDate].[Year-Month-Day].[CreatedYear].&[2011].&[9].&[19]:[DimRegDate].[Year-Month-Day].[CreatedYear].&[2011].&[9].&[25] } ON 1
FROM [Регистрации]

29 сентября 2011 г.

ASP.NET, логин через Facebook и ошибка e.root is undefined

Сегодня начал делать логин через Facebook на ASP.NET. Ну в общем тут и не важна серверная технология, т.к. все можно сделать на стороне клиента. Описание, как все делать, есть тут: http://developers.facebook.com/docs/guides/web/#login
Вроде бы все просто. Добавляем скрипт:
<script src="http://connect.facebook.net/en_US/all.js"></script>
Добавляем инициализацию:
       <script>
         FB.init({
            appId:'YOUR_APP_ID', cookie:true,
            status:true, xfbml:true
         });
      </script>
Добавляем специальный тег, который потом чудесным образом превратиться в красивую фейсбуковскую кнопку:
<fb:login-button>Login with Facebook</fb:login-button>
Запускаем и .. нифига не работает. Кнопка не появляется, а в яваскрипте возникает ошибка, какой то там root is undefined.
Погуглив, нашел решение, оказывается надо в код страницы еще добавить вот такой тег:
<div id="fb-root"></div>
Во всех примерах он есть, но вот в тексте он не упоминается и я его не заметил.
После его добавления все работает.
Собственно что делать дальше, читайте фейсбуковскую доку.


15 августа 2011 г.

T-SQL: Поиск по джобам

Для поиска джоба можно применять следующий скрипт:

select j.job_id, j.name, s.step_name, j.description
from msdb.dbo.sysjobs j
inner join msdb.dbo.sysjobsteps s on j.job_id = s.job_id
where s.command like '%some_text%'
s.command - команда, которую выполняет шаг джоба;
j.name - название джоба;
s.step_name - название шага джоба;
j.description - описание джоба.

nolock при обращении к талице на линкованом сервере

Если обратиться к таблице на линкованом SQL-сервере с директивой nolock:
SELECT * FROM [192.168.0.1].Users.dbo.users (nolock)
то будет выдаваться ошибка:
Msg 4122, Level 16, State 1, Line 1
Remote table-valued function calls are not allowed.
Решается вот так:
SELECT * FROM [192.168.0.1].Users.dbo.users WITH (nolock)
Ответ нашел тут.

11 августа 2011 г.

SSRS: Как рассылать отчеты по подписке НЕ по расписанию

Речь в статье пойдет о рассылке отчетов по подписке, но не по расписанию, а в те моменты, когда это необходимо. Например только в случае успешного процессинга OLAP-куба. Как раз такой случай я и буду рассматривать.

Все относиться к SQL Server 2008 R2 Enterprise Edition, т.к. от версии к версии база SSRS может меняться. Я делал подписку управляемую данными и не уверен, что все нижеследующее подойдет к обычной подписке, не проверял.

Когда в SSRS создается подписка, последней страницей задается расписание рассылки, но бывает так, что трудно угадать время, когда отчет уже можно формировать, например в какой то день может быть слишком много данных и они не успеют загрузиться. В моем случае бывало, что кубы процессились с ошибками, в итоге отчет приходил некоректный.

Так вот, при создании расписания рассылки отчета, SSRS создает на сервере, где лежит его база, джоб. Называться он будет как-нибудь вот так: 2D45C6DB-559E-43CB-A019-F8F1484E17FF и он будет запускаться по расписанию, которое было указано в SSRS.

В джобе вызывается процедура ReportServer.dbo.AddEvent, которая кладет ID подписки в таблицу ReportServer.dbo.Event, из этой таблицы SSRS понимает, что пора отчет отсылать, после отсылки запись из таблицы ReportServer.dbo.Event удаляется. 

Исходя из вышесказанного, задача сводиться к тому, что надо добавить запись с ID подписки в таблицу ReportServer.dbo.Event в тот момент, когда пора отсылать отчет.

Шаг 1 - узнаем ID отчета (все селекты идут в БД ReportServer):
select ItemID from catalog where name = @name
Шаг 2 - узнаем ID подписки:

select SubscriptionID from Subscriptions where report_OID =@ItemID
 Шаг 3 - записываем данные в таблицу Event:
exec ReportServer.dbo.AddEvent @EventType='TimedSubscription', @EventData=@SubscriptionID
В моем случае, в джобе, после шага, который процессит куб, я добавил шаг, который выполняет:
exec ReportServer.dbo.AddEvent @EventType='TimedSubscription', @EventData='fa502959-73cf-59a1-b018-8ddcdae2e01'

Надо иметь в виду, что если отчет удалить, а потом выложить заново, все ID поменяются . Если отчет выложить,  перезаписав его, то все ID останутся прежними.

И еще есть важный момент - надо сделать так, что бы расписание самого отчета не срабатывало, для чего в интерфейсе SSRS на странице создания расписания надо поставить Однократно.

T-SQL: Как проверить выполнение джоба

Бывают случаи, когда необходимо сделать так, что в джобе некоторые шаги при ошибочном выполнении не приводят к завершению джоба с ошибкой, а переходят на другой шаг джоба. У меня таким образом работает куб, который процессит OLAP-кубы. Что бы не следить каждый день за выполнением джоба, мы настроили SMS-рассылку. Для этого потребовалось разработать механизм проверки работы джоба. Т.е. задача - найти шаги джоба, которые завершились ошибкой, но при этом джоб продолжил работу.
Для начала нужно получить ID джоба:
select job_id from msdb..sysjobs where name = @job_name
Лог работы джобов лежит в таблице msdb..sysjobhistory. Нужно вытащить записи, которые относятся к последнему выполнению джоба. У меня джоб выполняется каждый день, поэтому просто беру сегодняшнее число. Но в таблице msdb..sysjobhistory дата начала выполнения шага храниться не в datetime, а в двух полях типа int: run_date и run_time, т.е. дата 2011-08-11 13:54:33 будет лежать в виде run_date = 20110811 и run_time = 135433.
Наверное с такими датами можно было бы работать и в интовых значениях, но мне как то привычнее в datatime. Для преобразования такого формата в datetime я написал две функции:
 -- Конвертация времени из формата HHMMSS типа int в секунды
create function [dbo].[fn_convert_intdt_to_sec] ( @ts int )
    returns int
as
begin
    if (@ts < 60) return @ts
     
    declare @sec int = @ts - (@ts/100)*100
    declare @min int = (@ts - (@ts/10000)*10000)/100
    declare @hr int = @ts/10000
     
    return @hr*3600 + @min*60 + @sec
end

-- Конвертация времени из формата даты в джобе в нормальный DateTime
create function [dbo].[fn_convert_job_date_to_datetime]( @date int, @time int )
returns DateTime
as
begin
    return dateadd(second, dbo.fn_convert_intdt_to_sec(@time), convert(datetime, cast(@date as varchar), 112))
end
Далее выводим список шагов:
select instance_id, step_id, step_name, run_status, dbo.fn_convert_job_date_to_datetime(run_date, run_time) run_dt
from msdb..sysjobhistory
where job_id = @job_id
and step_id <> 0 and dbo.fn_convert_job_date_to_datetime(run_date, run_time) >= @dt
order by step_id
step_id = 0 - это строка для всего джоба, поэтомы ее исключаем.

Тут (http://msdn.microsoft.com/en-us/library/ms174997.aspx) лежит описание таблицы  msdb..sysjobhistory.Статусы у шага могут быть такие:
0 = Failed
1 = Succeeded
2 = Retry
3 = Canceled
 В проге, которая дергает данные, я проверяю, что у всех шагов статус = 1 , если нет, то шлю СМС с ошибкой.

20 июля 2011 г.

C#: System.Diagnostics.Process - не срабатывает событие Exited

Для того, что бы срабатывало событие Exited экземпляра класса System.Diagnostics.Process нужно установить свойство EnableRaisingEvents в true. Т.е.:

process.EnableRaisingEvents = true;
process.Exited += new EventHandler(process_Exited);

Распространяется свойство только на событие Exited, т.е. если его не установить в true, то другие события, например OutputDataReceived, будут срабатывать. Описание тут: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.enableraisingevents.aspx

C#: как запустить приложение из кода

Для того, что бы запустить приложение из кода, существует класс System.Diagnostics.Process.
Я запускал консольное приложение через WEB-страницу. То, что консольное приложение выводит в стандартный вывод надо было собирать в лог-файл. Весь код страницы приводить не буду, приведу только ту часть, которая запускает приложение и пишет лог:

private string path_log = "C:\\parser\\"; // Папка, где лежат логи парсера
private string path_ex = "C:\\parser\\log\\"; // Папка, где лежит экзешник парсера
private string file_name = "log.txt"; // Лог-файл
private System.IO.StreamWriter sw_log = null;
private System.Diagnostics.Process parser = null;

private void StartParser()
{
        parser = new Process();
        parser.StartInfo.FileName = path_ex + "parser.exe";
        parser.StartInfo.UseShellExecute = false;
        // Указываем, что будем хватать стандартный вывод
        parser.StartInfo.RedirectStandardOutput = true;
        // А так же кодировку, в которой данные выходят в стандартный вывод
        parser.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
        parser.StartInfo.CreateNoWindow = true;
        parser.StartInfo.WorkingDirectory = path_ex;
        // ВАЖНОЕ СВОЙСТВО!! Без него не будет срабатывать событие parser.Exited
        parser.EnableRaisingEvents = true;
        // Событие, которое срабатывает, когда в стандартный вывод поступает инфа
        parser.OutputDataReceived += new DataReceivedEventHandler(parser_OutputDataReceived);
        parser.Exited += new EventHandler(parser_Exited);
        // ПУСК!
        parser.Start();

        sw_log = new System.IO.StreamWriter(path_log + "\\" + file_name + ".txt");
        // Говорим, что пора ловить данные из стандартного вывода
        parser.BeginOutputReadLine();
        // Возвращаю ID процесса      
        log_content.InnerHtml = string.Format("Parser process ID: {0}", parser.Id);
}

// Пишем стандартный вывод процесса в лог-файл. 
// Я сделал, что бы файл после каждой строчки флашился, мне просто так надо было, не принципиально
private void parser_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
        if (!String.IsNullOrEmpty(e.Data))
        {
            sw_log.WriteLine(e.Data);
            sw_log.Flush();
        }
}

// Закрыаем все и освобождаем
private void parser_Exited(object sender, EventArgs e)
{
        parser.Close();
        parser.Dispose();

        sw_log.Close();
        sw_log.Dispose();
}

В общем все. Из возможно полезного в классе Process есть метод WaitForExit() - который будет ждать, когда запускаемый процесс завершит работу. Так же как стандартный вывод можно ловить инфу из вывода ошибки, для этого есть событие ErrorDataReceived и метод BeginErrorReadLine. Так же там много всякой отладочной инфы по процессу. Подробности тут: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.aspx.

7 июля 2011 г.

ASP.NET: Как файл PrecompiledApp.config влияет на Global.asax

Использую ASP.NET 2.0. При развертывании веб-приложения на сервере (IIS) удалил файл PrecompiledApp.config, т.к. посчитал его не важным. После чего долго удивлялся, почему в файле Global.asax.cs не выполняется метод Application_Start

Здесь обсуждается эта проблема: http://forums.asp.net/t/1694548.aspx/1/10

После того, как положил на сервер файл PrecompiledApp.config все заработало. Не знаю баг это или фича. Вообще, судя по описаниям, файл используется для отслеживания надо ли какие то файлы компилировать при очередном деплое сайта.

6 июля 2011 г.

Ошибка MySQL "data too long" при вставке

При вставке данных в таблицу в поле с типом  blob произошла ошибка "data too long". У меня стоит MySQL 5.5.13 на Windows Server 2008 R2 Standard, винда английская. Погуглив, нашел массу сообщений об этой ошибке и несколько вариантов решения. Мне помогло вот это: http://forums.mysql.com/read.php?103,51906,103853#msg-103853 Надо в my.ini из строки
sql-mode=STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
убрать STRICT_TRANS_TABLES и перезапустить мускул.

23 июня 2011 г.

C#: как получить символ по коду UTF-8

Мне нужно было из строки удалить символ, который имел код UTF-8 8232. Думал получить по коду переменную типа string и с помощью метода Replace заменить на пустую строку.
Что бы получить строку по коду, можно воспользоваться методом:
System.Text.Encoding.UTF8.GetString
Метод принимает массив byte[], соответственно код 8232 нельзя туда подсунуть, т.к. он больше максимального байта. Я подумал, может, если я разобью этот код на два байта, то все получиться, т.е. сделал так:
string str = System.Text.Encoding.UTF8.GetString(new byte[] { 0x20, 0x28 }, 0, 2);
Но в итоге, в переменной str оказывалось значение " (". Совсем не то, что нужно.
Погуглив нашел решение, надо взять не код в таблице символов, а строку, в которую кодируется этот символ в URL. В моем случае - это строка %E2%80%A8 и выполнить следующий метод:
string str = Uri.UnescapeDataString("%E2%80%A8");
Тогда в str окажется необходимый символ. И далее можно с ним работать строковыми методами.

8 июня 2011 г.

T-SQL: Функция ROW_NUMBER

Недавно открыл для себя очень удобную в некоторых случаях функцию ROW_NUMBER. Появилась она в SQL Server 2005.
Ее можно использовать в селектах для нумирации строк, можно с помощью нее селектить только строки с такого-то по такой-то номер, а так же можно выдавать номер строки в некоторй группе строк. Примеры можно посмотреть в описании.
Мне больше всего нравиться последний случай ее применения. Т.к. раньше такого рода задачи приходилось решать через курсоры, что вело к большим блокам кода и тормозам при исполнении.
Т.е. допустим есть таблица:
CREATE TABLE user_items
(
    id int not null primary key,
    user_id int not null,
    item_id int not null,
    added_at datetime not null
)
И хочется, что бы для каждого пользователя в таблице осталось не более 5 записей, которые были добавлены последними. С использованием ROW_NUMBER это можно сдедать так:

delete u from user_items u
inner join
(
    select ROW_NUMBER() OVER (partition by user_id order by added_at desc) rn, user_id, item_id, id
    from user_items
) a on u.id = a.id
where a.rn > 5
 Т.е. во вложенном запросе выбираются все записи и проставляются номера записей для каждого пользователя в обратном порядке добавления. Соответственно записи, у которых номер больше 5, надо удалить.

2 июня 2011 г.

Автоматическое партицирование OLAP-кубов в SSAS 2008


В статье Партицирование OLAP-кубов в SSAS 2008 я рассказывал зачем и как партицировать кубы в SSAS. Бывают случаи, когда партицировать надо часто и руками это делать скучно. Поэтому хочется этот процесс автоматизировать.
В моем случае мне нужно партицировать в трех кубах в общей сложности 10 групп мер. Если этого не делать каждую неделю, то время процессинга за две недели возрастает с 3,5 часов до 5 с лишним.
В статье Взаимодействие с OLAP кубами (SSAS 2008) на C# я рассказывал, как работать с кубами из кода. Сначала я начал решать задачу автоматического партицирования через разработку проги, но что то сильно углубился в программирование и мне эта идея перестала нравиться, поэтому я выбрал другой путь - создание пакета SQL Server Integration Services (SSIS). Здесь я и опишу этот способ.
Идея следующая - группы мер партицируются по годам плюс есть текущая партиция, которая процесситься каждый день. Т.е., например, для продаж есть партиции за 2010 год, 2011 и current. Раз в неделю для каждой группы мер необходимо изменять партицию за текущий год (2011) и партицию current, так что бы текущая партиция содержала данные только за несколько дней. Потом процессить партицию за текущий год, что бы не было пробелов в данных. После нового года надо будет руками создавать новую годовую партицию и менять процедуру изменения партиций.
Начальное партицирование куба надо провести руками, автоматически все с нуля не создается.
Для примера возьмем, что партиции группы мер продаж формируются следующими SQL-запросами:
2010: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2010-01-01' AND sale_date < '2011-01-01'
2011: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2011-01-01' AND sale_date < '2011-05-31'
current: SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date >= '2010-05-31'
Эти запросы и надо будет менять в партициях за 2011 год и в текущей партиции.
Для начала необходимо сгенерить скрипты XMLA для изменения партиций. Я буду делать все на примере только одной группы мер. Пакет SSIS будет сделан так, что количество не будет иметь значения.
Cоздаем папку: C:\XMLAScripts. Далее, для генерации скрипта в Management Studio открываем список партиций нужной группы мер, на текущей партиции тыкаем правой кнопкой, а далее как на картинке:

В последнем меню выбираем File... и сохраняем в созданной папке под названием sales_current.xmla. Далее тоже самое повторяем для партиции текущего года и сохраняем в другой файл sales_2011.xmla.
Далее открываем файлы по-очереди и видим что-то похожее на это:
<Alter ObjectExpansion="ExpandFull" xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
    <Object>
        <DatabaseID>cubeDbSales</DatabaseID>
        <CubeID>cubeSales</CubeID>
        <MeasureGroupID>Vw Sales 1</MeasureGroupID>
        <PartitionID>Vw Sales 1</PartitionID>
    </Object>
    <ObjectDefinition>
        <Partition ...>
            <ID>Vw Sales 1</ID>
            <Name>Vw Sales</Name>
            <Source xsi:type="QueryBinding">
                <DataSourceID>dbOLAP</DataSourceID>
                <QueryDefinition>SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date &gt;= '2010-05-31'
</QueryDefinition>
            </Source>
            <StorageMode>Molap</StorageMode>
            <ProcessingMode>Regular</ProcessingMode>
            <ProactiveCaching>
                <SilenceInterval>-PT1S</SilenceInterval>
                <Latency>-PT1S</Latency>
                <SilenceOverrideInterval>-PT1S</SilenceOverrideInterval>
                <ForceRebuildInterval>-PT1S</ForceRebuildInterval>
                <Source xsi:type="ProactiveCachingInheritedBinding" />
            </ProactiveCaching>
        </Partition>
    </ObjectDefinition>
</Alter>

В каждом файле заменяем дату, которая разделяет партицию текущего года и текущую партицию, в данном примере это 2011-05-31, на какую-нибудь последовательность символов. Я поменял на "###dt###", т.е. запрос в скрипте будет выглядеть вот так:
 <QueryDefinition>SELECT id, sale_date, user_id, money_sum, product_id FROM sales WHERE sale_date &gt;= '###dt###'</QueryDefinition>
Далее эта последовательность символов будет меняться на нужную дату.

В BIDS создаем Integration Services Project. Из Toolbox'а перетаскиваем Foreach Loop Container, а в него Script Task и Analysis Services Execute DDL Task. В итоге должно получиться вот так:
Далее создаем переменные:
Розовые уголки в иконках - это у меня плагин стоит, который я описываю в статье Как поменять область видимости переменной в BIDS (SSIS).
Для переменных dt и dt_str устанавливаем свойство EvaluateAsExpression = True. Для dt свойство Expression: GETDATE(). Для dt_str свойство Expression:
(DT_WSTR, 4)(YEAR(@[User::dt]))+"-"+
(LEN((DT_WSTR, 2)(MONTH(@[User::dt])))==1 ? "0"+(DT_WSTR, 2)(MONTH(@[User::dt])) : (DT_WSTR, 2)(MONTH(@[User::dt])))+"-"+
(LEN((DT_WSTR, 2)(DAY(@[User::dt])))==1 ? "0"+(DT_WSTR, 2)(DAY(@[User::dt])) : (DT_WSTR, 2)(DAY(@[User::dt])))
Такое выражение формирует из даты строку формата yyyy-MM-dd, другого пути я не нашел.
В совойствах Foreach контенера устанавливаем:
  1. В разделе Collection:
    1. Enumerator = Foreach File Enumerator
    2. Folder = C:\XMLAScripts
    3. Files = *.xmla 
  2.  В разделе Variable Mappings: Variable=User:file_name, Index = 0
 В свойствах блока Script в разделе Script:
  1. ReadOnlyVariables = User::dt_str,User::file_name
  2. ReadWriteVariables = User::alter_code
В коде скрипта пишем (у меня SQL Server 2008, поэтому пишу на C#):
        public void Main()
        {
            string file_name = Dts.Variables["file_name"].Value.ToString();
            StreamReader sr = new StreamReader(file_name);

            string content = sr.ReadToEnd();

            sr.Close();
            sr.Dispose();

            Dts.Variables["alter_code"].Value = content.Replace("###dt###",  Dts.Variables["dt_str"].Value.ToString());

            Dts.TaskResult = (int)ScriptResults.Success;
        }
 В свойствах блока Alter Cube в разделе DDL:
  1. Connection -> создаем новое соединение с кубом
  2. SourceType =Variable
  3. Source = User::alter_code
На этом создание SSIS пакета закончено. Foreach контейнер выбирает из папки C:\XMLAScripts все файлы с расширением xmla, в блоке Script текущий файл читается, в нем производиться замена строки "###dt###" на сегодняшнюю дату в формате yyyy-MM-dd, а блок Alter Cube передает полученную таким образом команду OLAP-серверу. Для того, что бы добавить другие группы мер для партицирования, надо в папку C:\XMLAScripts добавить XMLA-файлы с альтерами для этих групп, в которых дата в SQL-запросе будет заменена на "###dt###".
Далее нужно создать джоб, в котором первым шагом сделать бэкап куба на всякий случай, вторым шагом запустить этот SSIS пакет, а далее отпроцессить все партиции за текущий год.