Общий вид статистики — различия между версиями
м |
м |
||
(не показаны 24 промежуточные версии этого же участника) | |||
Строка 1: | Строка 1: | ||
− | Маленький | + | Маленький настраиваемый OLAP’ик на PHP/MySQL, на самом деле также поддерживает любые источники данных, если напишете функцию, которая эти данные по запросу предоставит. Брать [{{SVN|vitalif/trunk/olap}} здесь]. Общий вид любой, по сути дела, статистики. Есть, условно говоря, «события» (в OLAP — «факты»), у каждого есть показатели. То есть, обычный OLAP-куб, который берётся из SQL-БД или генерируется функцией. По этим событиям интересно получать какие-то отчёты. |
Типы показателей: | Типы показателей: | ||
Строка 7: | Строка 7: | ||
Примеры показателей: | Примеры показателей: | ||
− | * Любые части даты/времени, типа: год, месяц, неделя, дата, время суток, час, минута | + | * Любые части даты/времени, типа: год, месяц, неделя, дата, время суток, час, минута и т. п. Каждый следующий такой показатель содержит все предыдущие. |
− | * Стандартные параметры из веб-статистики: IP, User-Agent, Referer, поисковой запрос | + | * Стандартные параметры из веб-статистики: IP, User-Agent, Referer, поисковой запрос и т. п. |
* Параметры смс-биллинга: стоимость смс, доход от смс, отправитель, надёжность отправителя. | * Параметры смс-биллинга: стоимость смс, доход от смс, отправитель, надёжность отправителя. | ||
* Показатели продаж: кто купил, что купил, за сколько купил. | * Показатели продаж: кто купил, что купил, за сколько купил. | ||
− | А | + | Вычисляется Агрегат(Функция(Показатель)). Агрегат и Функция необязательны, то есть может быть просто Функция(Показатель) или Агрегат(Показатель) или просто Показатель. Функции — любые преобразования показателей, те же даты и т. п. А агрегаты — это агрегатные функции: |
− | + | * Минимум, максимум | |
− | + | * Сумма, количество, среднее | |
− | # | + | * Количество уникальных значений |
− | #* По | + | * Дисперсия (среднеквадратичное отклонение) |
− | #* | + | |
− | + | == Построение отчёта == | |
− | + | ||
− | + | Во-первых, задаются условия выборки данных — потенциально, любые (просто WHERE в SQL-запросе). В простейшем виде — выбор одного значения или диапазона для каждого поля. | |
+ | |||
+ | Далее выбирается требуемый показатель: А(Ф(П)) или А(П). | ||
+ | |||
+ | И самое интересное — произвольный набор полей для группировки. Для каждого поля выбирается: | ||
+ | # Тип группировки. Доступные типы, по порядку убывания приоритета (то есть сначала всегда группируется по тем типам, которые выше в списке): | ||
+ | #* Несколько таблиц — для каждого значения данного поля будет построена отдельная таблица. | ||
+ | #* По вертикали — значения данного поля будут отложены по вертикальной оси (в таблице — слева тела таблицы). | ||
+ | #* По горизонтали — аналогично, но по горизонтальной оси. | ||
+ | #* В ячейке — список значений данного поля будет выведен внутри ячейки таблицы вместе с соответствующими значениями показателя. | ||
+ | #* График — по значениям поля строится JavaScript-график. Не комбинируется с другими типами. | ||
+ | # Само поле: любой П или Ф(П). | ||
+ | # Порядок сортировки — по убыванию или по возрастанию. | ||
+ | # Тип сортировки — либо по значениям самого поля, либо по А(Ф(П)) любого другого поля, группируемого не по всем выбранным полям, а только по полям с типом группировки «несколько таблиц» и полям с группировкой того же типа, что и данное поле, но предшествующим данному полю. | ||
+ | |||
+ | Важная фишка в том, что на каждый тип группировки можно выбрать несколько полей, и всё отобразится корректно. | ||
+ | |||
+ | {{note}} Последний пункт звучит страшно, но на самом деле, отражает довольно интуитивное поведение. Примеры: | ||
+ | * Для единственного поля по вертикали это будет А(Ф(П)) за соответствующую строку таблицы (например, общая сумма прибыли за строку). | ||
+ | * Для единственного поля по горизонтали — аналогично, за соответствующий столбец таблицы. | ||
+ | * Для поля в ячейках — за все ячейки, но только за их части, соответствующие данному значению. | ||
+ | * Если по вертикали не одно, а два поля, то для первого («внешнего») это будет А(Ф(П)) за все строки, соответствующие его значению, а для второго («внутреннего») — А(Ф(П)) за все строки, соответствующие значениям ''обоих'' полей. То есть «внутреннее» поле и будет сортироваться как бы «внутри» каждого значения внешнего. | ||
+ | |||
+ | Графики строятся с помощью JavaScript-библиотеки [http://www.jqplot.com/ jQplot]. На график «умещаются» максимум две группировки — одна по горизонтали, вторая — несколько линий на одном графике. Остальные группировки помещаются на отдельные графики. Тип группировки «график» не комбинируется с табличными способами — если хотя бы одна группировка — график, графиками становятся и остальные. | ||
+ | |||
+ | Поддерживается переключаемый логарифмический масштаб показателя (по вертикали). Графики сглаживаются. Числа и даты отображаются на линейной оси, строковые показатели — с равными интервалами между значениями. | ||
+ | |||
+ | [[File:olap-jqplot.png|frame|center|Старый скриншот (сглаживания и пропорциональной оси дат нет)]] | ||
+ | |||
+ | {{----}} | ||
+ | В коде также есть временно отключённая возможность строить «псевдографики», состоящие из горизонтальных полос шириной, пропорциональной значению. | ||
+ | |||
+ | == Конфигурация == | ||
+ | |||
+ | Конфигурация OLAP пишется в файле olap-config.php, кладётся рядом с olap.php, и включает в себя: | ||
+ | |||
+ | <source lang="php"> | ||
+ | OLAP::$sources = array( | ||
+ | 'строковой ID источника' => array( | ||
+ | 'name' => 'читаемое имя источника', | ||
+ | 'db' => array('имя БД', 'хост БД', 'пользователь БД', 'пароль пользователя'), | ||
+ | 'tables' => $SQL_таблицы, | ||
+ | 'where' => $SQL_условия_накладываемые_всегда, | ||
+ | 'use_pgm' => (true, если группировку и подсчёт нужно вычислять в PHP), | ||
+ | 'generator' => $функция_генератор_данных, | ||
+ | 'sql_filter' => $фильтр_SQL_строк, | ||
+ | 'sql_options' => $SQL_опции (используются только если use_pgm==true), | ||
+ | 'fielddescs' => array( | ||
+ | 'имя поля' => array( | ||
+ | 'sql' => 'SQL-код поля (необязателен, по умолчанию равен имени)', | ||
+ | 'name' => 'читаемое имя поля', | ||
+ | 'comment' => 'необязательный комментарий (показывается после полей ввода)', | ||
+ | 'options' => array(array('значение', 'читаемое имя')), // опции для выпадающего списка (необязательно) | ||
+ | // 'options' может быть callback'ом, вызываемым с одним параметром $olap (= объекту-построителю) | ||
+ | // и возвращающим опции в том же формате | ||
+ | 'autofilter' => (true, если можно загрузить опции для выпадающего списка из таблицы автоматически), | ||
+ | 'le_ge' => (true, если к полю применимы фильтры >= и <=), | ||
+ | 'is_time' => (true, если поле содержит дату/время), | ||
+ | 'format' => (для полей с датой/временем формат - константа TS_DB (YYYY-MM-DD HH:MM:SS) или TS_UNIX (время UNIX)), | ||
+ | 'mandatory' => (true, если данные поля нужно запрашивать всегда), | ||
+ | // используется только в use_pgm, на случай, если фильтру нужны эти значения | ||
+ | ), | ||
+ | ... | ||
+ | ), | ||
+ | ), | ||
+ | ... | ||
+ | ); | ||
+ | |||
+ | // Функция-генератор данных принимает параметры через хеш: | ||
+ | function generator($params = array( | ||
+ | 'where' => array(array('поле', 'оператор (le/ge/eq)', 'значение'), ...) | ||
+ | 'where_sql' => (условия where, транслированные в SQL), | ||
+ | 'fields' => array('поле', 'поле', ...), | ||
+ | 'fields_sql' => (поля, транслированные в SQL) | ||
+ | )) | ||
+ | { | ||
+ | // Должна вернуть объект, функция fetch() которого будет возвращать данные | ||
+ | // в виде ассоциативных массивов и NULL, когда данные кончатся. | ||
+ | return new Result(); | ||
+ | } | ||
+ | |||
+ | // Функция-фильтр применяется, если вам ещё нужно что-то сделать | ||
+ | // с данными в коде перед их скармливанием OLAP'у. Принимает один | ||
+ | // параметр - строчку данных (ассоциативный массив): | ||
+ | function filter($row) | ||
+ | { | ||
+ | // Должна вернуть массив строчек, возможно пустой. | ||
+ | // В конце обработки фильтру передаётся $row = NULL, на случай, | ||
+ | // если он зажал какие-то строки и должен отдать в конце | ||
+ | return array($row); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | За описанием параметров <tt>tables, where, sql_options</tt> обращайтесь к описанию подобного MediaWikовскому SelectBuilder’а: [[MySQL Select Builder]], или в его код — {{SVN|vitaphoto/php/DatabaseMysql.php|markup}} (функция <tt>DatabaseMysql::select_builder</tt>). | ||
+ | |||
+ | Да, кстати, под E_STRICT вся эта хрень выплюнет тучу Notice’ов, плюньте на них и скажите <tt>error_reporting(E_ALL & ~E_STRICT)</tt>. | ||
+ | |||
+ | == Пример генерации: Wayback Machine == | ||
+ | |||
+ | Любопытным примером генерации данных функцией является такая весёлая вещь — построение отчётов с учётом истории изменений, хранящихся в виде undo-логов, то есть, когда в базе хранится актуальное состояние объектов плюс история изменений (какое поле, с чего, на что и когда было изменено), а аналитику хочется делать развёрнуто по времени. Так, например, хранится история изменений багов в Bugzilla. | ||
+ | |||
+ | Без функции-генератора тут не обойтись, и она должна загружать актуальное состояние и нужные изменения, а потом просто последовательно применять их, восстанавливая состояния объектов на более старые даты. | ||
+ | |||
+ | [[Category:Техактивы]] |
Текущая версия на 13:12, 21 сентября 2017
Маленький настраиваемый OLAP’ик на PHP/MySQL, на самом деле также поддерживает любые источники данных, если напишете функцию, которая эти данные по запросу предоставит. Брать здесь. Общий вид любой, по сути дела, статистики. Есть, условно говоря, «события» (в OLAP — «факты»), у каждого есть показатели. То есть, обычный OLAP-куб, который берётся из SQL-БД или генерируется функцией. По этим событиям интересно получать какие-то отчёты.
Типы показателей:
- Численный тип
- Время (timestamp)
- Строковой тип
Примеры показателей:
- Любые части даты/времени, типа: год, месяц, неделя, дата, время суток, час, минута и т. п. Каждый следующий такой показатель содержит все предыдущие.
- Стандартные параметры из веб-статистики: IP, User-Agent, Referer, поисковой запрос и т. п.
- Параметры смс-биллинга: стоимость смс, доход от смс, отправитель, надёжность отправителя.
- Показатели продаж: кто купил, что купил, за сколько купил.
Вычисляется Агрегат(Функция(Показатель)). Агрегат и Функция необязательны, то есть может быть просто Функция(Показатель) или Агрегат(Показатель) или просто Показатель. Функции — любые преобразования показателей, те же даты и т. п. А агрегаты — это агрегатные функции:
- Минимум, максимум
- Сумма, количество, среднее
- Количество уникальных значений
- Дисперсия (среднеквадратичное отклонение)
Построение отчёта
Во-первых, задаются условия выборки данных — потенциально, любые (просто WHERE в SQL-запросе). В простейшем виде — выбор одного значения или диапазона для каждого поля.
Далее выбирается требуемый показатель: А(Ф(П)) или А(П).
И самое интересное — произвольный набор полей для группировки. Для каждого поля выбирается:
- Тип группировки. Доступные типы, по порядку убывания приоритета (то есть сначала всегда группируется по тем типам, которые выше в списке):
- Несколько таблиц — для каждого значения данного поля будет построена отдельная таблица.
- По вертикали — значения данного поля будут отложены по вертикальной оси (в таблице — слева тела таблицы).
- По горизонтали — аналогично, но по горизонтальной оси.
- В ячейке — список значений данного поля будет выведен внутри ячейки таблицы вместе с соответствующими значениями показателя.
- График — по значениям поля строится JavaScript-график. Не комбинируется с другими типами.
- Само поле: любой П или Ф(П).
- Порядок сортировки — по убыванию или по возрастанию.
- Тип сортировки — либо по значениям самого поля, либо по А(Ф(П)) любого другого поля, группируемого не по всем выбранным полям, а только по полям с типом группировки «несколько таблиц» и полям с группировкой того же типа, что и данное поле, но предшествующим данному полю.
Важная фишка в том, что на каждый тип группировки можно выбрать несколько полей, и всё отобразится корректно.
Последний пункт звучит страшно, но на самом деле, отражает довольно интуитивное поведение. Примеры:
- Для единственного поля по вертикали это будет А(Ф(П)) за соответствующую строку таблицы (например, общая сумма прибыли за строку).
- Для единственного поля по горизонтали — аналогично, за соответствующий столбец таблицы.
- Для поля в ячейках — за все ячейки, но только за их части, соответствующие данному значению.
- Если по вертикали не одно, а два поля, то для первого («внешнего») это будет А(Ф(П)) за все строки, соответствующие его значению, а для второго («внутреннего») — А(Ф(П)) за все строки, соответствующие значениям обоих полей. То есть «внутреннее» поле и будет сортироваться как бы «внутри» каждого значения внешнего.
Графики строятся с помощью JavaScript-библиотеки jQplot. На график «умещаются» максимум две группировки — одна по горизонтали, вторая — несколько линий на одном графике. Остальные группировки помещаются на отдельные графики. Тип группировки «график» не комбинируется с табличными способами — если хотя бы одна группировка — график, графиками становятся и остальные.
Поддерживается переключаемый логарифмический масштаб показателя (по вертикали). Графики сглаживаются. Числа и даты отображаются на линейной оси, строковые показатели — с равными интервалами между значениями.
В коде также есть временно отключённая возможность строить «псевдографики», состоящие из горизонтальных полос шириной, пропорциональной значению.
Конфигурация
Конфигурация OLAP пишется в файле olap-config.php, кладётся рядом с olap.php, и включает в себя:
OLAP::$sources = array( 'строковой ID источника' => array( 'name' => 'читаемое имя источника', 'db' => array('имя БД', 'хост БД', 'пользователь БД', 'пароль пользователя'), 'tables' => $SQL_таблицы, 'where' => $SQL_условия_накладываемые_всегда, 'use_pgm' => (true, если группировку и подсчёт нужно вычислять в PHP), 'generator' => $функция_генератор_данных, 'sql_filter' => $фильтр_SQL_строк, 'sql_options' => $SQL_опции (используются только если use_pgm==true), 'fielddescs' => array( 'имя поля' => array( 'sql' => 'SQL-код поля (необязателен, по умолчанию равен имени)', 'name' => 'читаемое имя поля', 'comment' => 'необязательный комментарий (показывается после полей ввода)', 'options' => array(array('значение', 'читаемое имя')), // опции для выпадающего списка (необязательно) // 'options' может быть callback'ом, вызываемым с одним параметром $olap (= объекту-построителю) // и возвращающим опции в том же формате 'autofilter' => (true, если можно загрузить опции для выпадающего списка из таблицы автоматически), 'le_ge' => (true, если к полю применимы фильтры >= и <=), 'is_time' => (true, если поле содержит дату/время), 'format' => (для полей с датой/временем формат - константа TS_DB (YYYY-MM-DD HH:MM:SS) или TS_UNIX (время UNIX)), 'mandatory' => (true, если данные поля нужно запрашивать всегда), // используется только в use_pgm, на случай, если фильтру нужны эти значения ), ... ), ), ... ); // Функция-генератор данных принимает параметры через хеш: function generator($params = array( 'where' => array(array('поле', 'оператор (le/ge/eq)', 'значение'), ...) 'where_sql' => (условия where, транслированные в SQL), 'fields' => array('поле', 'поле', ...), 'fields_sql' => (поля, транслированные в SQL) )) { // Должна вернуть объект, функция fetch() которого будет возвращать данные // в виде ассоциативных массивов и NULL, когда данные кончатся. return new Result(); } // Функция-фильтр применяется, если вам ещё нужно что-то сделать // с данными в коде перед их скармливанием OLAP'у. Принимает один // параметр - строчку данных (ассоциативный массив): function filter($row) { // Должна вернуть массив строчек, возможно пустой. // В конце обработки фильтру передаётся $row = NULL, на случай, // если он зажал какие-то строки и должен отдать в конце return array($row); }
За описанием параметров tables, where, sql_options обращайтесь к описанию подобного MediaWikовскому SelectBuilder’а: MySQL Select Builder, или в его код — http://svn.yourcmc.ru/viewvc.py/vitaphoto/php/DatabaseMysql.php?view=markup (функция DatabaseMysql::select_builder).
Да, кстати, под E_STRICT вся эта хрень выплюнет тучу Notice’ов, плюньте на них и скажите error_reporting(E_ALL & ~E_STRICT).
Пример генерации: Wayback Machine
Любопытным примером генерации данных функцией является такая весёлая вещь — построение отчётов с учётом истории изменений, хранящихся в виде undo-логов, то есть, когда в базе хранится актуальное состояние объектов плюс история изменений (какое поле, с чего, на что и когда было изменено), а аналитику хочется делать развёрнуто по времени. Так, например, хранится история изменений багов в Bugzilla.
Без функции-генератора тут не обойтись, и она должна загружать актуальное состояние и нужные изменения, а потом просто последовательно применять их, восстанавливая состояния объектов на более старые даты.