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(*), но его можно использовать для примерной оценки той или иной таблицы
Metrika
10 января 2014 г.
MySQL: Определение количества строк и размера таблиц запросом
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 точек, не лагало. Если найдете баги, пишите в коменты.
Как использовать:
Необходимо передать массив точек в конструктор QuickHull. Я использовал алгоритм для построения полигона на карте, поэтому у меня в объекте Point два конструктора: init_by_building, куда передается объект здания, который в свою очередь имеет свойства lat (широта) и lng (долгота); и init, куда передются координаты x и y точки.
После выполнения в массиве cvx_lines будут содержаться объекты типа Line, которые формируют многоугольник.
// Построение выпуклого многоугольника по массиву однородных точек
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.
Только она, из найденных мной, работает с русским языком в 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.
Для того, что бы заработало, надо в 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
<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
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 [Регистрации]'
)
'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 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:
В моем случае подошла либа 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 написал вот так:
На маленьких массивах проблем с производительностью не наблюдалось, но при тестах на более менее вменяемых данных (около 15000 записей) этот код на ноуте выполнялся 2 мин!!! Epic Fail!!
Тот же код с использованием StringBuilder на тех же данных выполняется секунду!! Прирост производительност более чем в сто раз на не очень больших объемах данных:
1. Работа со строками. Строковые функции
2. String concatenation the fast way...maybe not
При написании прототипа нового проекта вообще не обратил на это внимания и для вывода массива объектов в 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, более подходящий аргумент - количество убийств за матч и т.д.
Создаю таблицу:
Затем в BI Studio создаю проект Analysis Services Project. В проекте создаю DataSource и DataView, который содержит таблицу _dm_test.
После этого в проекте тыкаю правой кнопкой на Mining Structures и выбираю New Mining Structure:
Последняя страница создания модели - ввод имен структуры и модели:
Окно редактирования 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:
После всех настроек кластеры стали выглядеть вот так:
Для примера я создам сегменты на основе таблицы с небольшим количеством параметров. На самом деле выбор правильных параметров для анализа само по себе является не простой задачей, и нужно быть готовым, что таблица с исходными данными в процессе построения сегментов может часто менять свою структуру и часто пересчитываться. Например с одной стороны можно решить, что параметр "количество убийств" хорошо характеризует успешность игрока, но на самом деле один игрок может набить 100 убийств за 3 матча, а другой за 10, более подходящий аргумент - количество убийств за матч и т.д.
Создаю таблицу:
create table _dm_test(Агрегирую в нее данные за последние 7 игровых дней за 30 календарных дней.
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 -- Диапазоны званий
)
Затем в 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:
Создаю в 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 у меня не заработал:
На вкладке 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 пользователя и названия кластеров.
Полезные ссылки
1. Очень помогла книга "Microsoft SQL Server 2008: Data Mining. Интеллектуальный анализ данных" Д. Макленнен, Ч. Танг, Б. Криват;
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: Поиск по джобам
Для поиска джоба можно применять следующий скрипт:
j.name - название джоба;
s.step_name - название шага джоба;
j.description - описание джоба.
select j.job_id, j.name, s.step_name, j.descriptions.command - команда, которую выполняет шаг джоба;
from msdb.dbo.sysjobs j
inner join msdb.dbo.sysjobsteps s on j.job_id = s.job_id
where s.command like '%some_text%'
j.name - название джоба;
s.step_name - название шага джоба;
j.description - описание джоба.
Подписаться на:
Сообщения
(
Atom
)



























