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 - описание джоба.