function get_table_copy($table, $seperator=NULL, $null_value=NULL) { // uses pg_copy_to to return an array of an entire table // good for use of 'export table out' switch (func_num_args()) { case 1: return pg_copy_to($this->db, $table); break; case 2: return pg_copy_to($this->db, $table, $seperator); break; case 3: return ($seperator != NULL ?pg_copy_to($this->db, $table, $seperator, $null_value) : pg_copy_to($this->db, $table, NULL, $null_value)); break; } }
/** * Считает почасовую статистику. * Берет данные из stat_log до последнего часа и группирует их в промежутках по одному часу, uid смотрящего и uid кого смотрели. * Т.е. любое кол-во просмотров кем-либо (guest_id) в течении часа кого-либо (user_id) считается одним просмотром. * Сгруппированные данные сохраняются в stat_hourly, а полный дамп просмотров кладется в файл в директории $this->arc * @return string сообщение об ошибке или 0, если все прошло успешно */ function Step1() { // Отправляем данные из stat_log в stat_hourly. Ежечасно. // Архивный лог ($arc_name) не удаляем. Пусть будут дубли, не страшно. Потом при использовании, нужно будет их очистить и все. // Еще заводим лог ошибок. $DB = $this->_sDB; $this->log('stat_collector::Step1(). Экспорт данных из stat_log в stat_hourly.', self::LT_SUBHEADER); $time = $this->get_time(); $curH = date('Y-m-d H', $time) . ':00:00'; $curTH = strtotime($curH); // 1. Убиваем индекс "ix stat_log_t/_time" для облегчения последующей переправки данных в stat_log_t. // 2. Перенаправляем инсерт на stat_log_t. // 3. Переносим все данные в stat_log_t. // 4. TRUNCATE stat_log. // 5. Возвращаем инсерты на stat_log. // 6. Создаем индекс "ix stat_log_t/_time" снова, чтобы использовать его в селектах. // 7. Обрабатываем по одному часу stat_log_t. // 8. В конце делаем VACUUM FULL stat_log_t. // Таким образом вся обработка будет осуществляться на таблице stat_log_t. В начале каждого вызова данной функции, // stat_log_t будет содержать данные за неполный предыдущий час (_time >= $curH в предыдущем вызове), которые будут дополняться // новыми данными, накопившимися в stat_log . $this->log('Удаляем индекс "ix stat_log_t/_time".', self::LT_NOTICE); $sql = 'DROP INDEX IF EXISTS "ix stat_log_t/_time"'; if (!$this->_sDB->squery($sql)) { $this->log('Ошибка. ' . $this->_sDB->error, self::LT_WARNING); } $this->log('Перенаправляем инсерты на stat_log_t.', self::LT_NOTICE); if (!$this->_setLogTable('stat_log_t')) { return $this->log('Ошибка. ', self::LT_ERROR); } $this->log('Ок.', self::LT_NOTICE); $this->log('Ждем 3 секунды для завершения старых транзакций...', self::LT_NOTICE); sleep(3); $truncateErr = NULL; $this->log('Переносим все данные из stat_log в stat_log_t, TRUNCATE ONLY stat_log.', self::LT_NOTICE); $sql = 'INSERT INTO stat_log_t SELECT * FROM ONLY stat_log'; if (!$DB->squery($sql)) { $truncateErr = $this->log('Ошибка. ' . $DB->error, self::LT_ERROR); } else { $sql = 'TRUNCATE ONLY stat_log'; if (!$DB->squery($sql)) { $truncateErr = $this->log('Ошибка. ' . $DB->error, self::LT_ERROR); } } $this->log('Возвращаем инсерты на stat_log.', self::LT_NOTICE); if (!$this->_setLogTable('stat_log')) { return $this->log('Ошибка. ', self::LT_ERROR); } if ($truncateErr) { return $truncateErr; } $this->log('Восстанавливаем индекс "ix stat_log_t/_time".', self::LT_NOTICE); $sql = 'CREATE INDEX CONCURRENTLY "ix stat_log_t/_time" ON stat_log_t USING btree (_time)'; if (!$DB->squery($sql)) { $this->log('Ошибка. ' . $DB->error, self::LT_WARNING); } $lT = $DB->val("SELECT _time FROM stat_log_t ORDER BY _time LIMIT 1"); if ($DB->error) { return $this->log('Ошибка чтения stat_log_t. ' . $DB->error, self::LT_ERROR); } if (!$lT) { return $this->log('Данных нет.', self::LT_NOTICE); } $tH = strtotime(date('Y-m-d H', strtotime($lT)) . ':00:00'); if ($tH < $curTH) { $tH += 3600; } // Обрабатываем по одному часу. for ($tH; $tH <= $curTH; $tH += 3600) { $H = date('Y-m-d H', $tH) . ':00:00'; // в конец $arc_name добавлен суффикс H, т.к. в разное время из-за всяких сбоев может обрабатываться один и тот же час, // это приводило к затиранию предыдущего архива. $arc_name = $this->arc_dir . '/' . date('YmdH', $tH) . '-' . date('H') . '.log'; $this->log("Обработка данных: FROM stat_log_t WHERE _time < '{$H}'.", self::LT_NOTICE); // (а) Проверяем, есть ли данные в stat_log_t, которые можно экспортировать. $sql = "SELECT 1 FROM stat_log_t WHERE _time < ? LIMIT 1"; if (!($res = $DB->query($sql, $H))) { return $this->log('Ошибка чтения stat_log_t. ' . $DB->error, self::LT_ERROR); } if (!pg_num_rows($res)) { $this->log("Данных нет.", self::LT_NOTICE); continue; } // (б) Берем данные из stat_log_t за все "полные часы" (все, кроме текущего часа) и выбрасываем их во временную таблицу и в хранилище логов. // Данные выбрасываются в чистом виде, без преобразований и упаковываются (пока не упаковываются). // В случае ошибки прекращаем операцию. if (!$DB->start()) { return $this->log('Не удалось открыть транзакцию. ' . $DB->error, self::LT_ERROR); } $sql = "SELECT * INTO TEMPORARY TABLE ___tmp_arc FROM stat_log_t WHERE _time < ?"; if (!($res = $DB->query($sql, $H))) { $e = $DB->error; $DB->rollback(); return $this->log('Ошибка инсерта в ___tmp_arc. ' . $e, self::LT_ERROR); } $all_data = pg_copy_to($DB->connect(), '___tmp_arc'); if (!file_put_contents($arc_name, $all_data)) { $this->log("Лог {$arc_name} не записался.", self::LT_WARNING); } unset($all_data); // Пригодится для отката в случае конфликта ключей. if (!$DB->query('SAVEPOINT arc_created')) { $e = $DB->error; $DB->rollback(); return $this->log('Не удалось создать SAVEPOINT. ' . $e, self::LT_ERROR); } // (в) Берем данные из stat_log_t за тот же период, что и в (б), но группируем их специальным образом, так, чтобы не было ничего "лишнего". // (г) Если ошибка возникла в связи с конфликтом ключей (тут конкретная такая проверка, // то есть, теперь мы не гурьбой пытаемся запихнуть данные, а через временную таблицу пробуем загрузить только // "не дубликаты"), то удаляем файл. Иначе удаляем файл и прекращаем операцию. // Каждый час занимаемся только конкретными данными, только одним .tmp файлом. Если он не проходит в // stat_hourly по причине сбоя какого-нибудь, то прекращаем операцию. Так будет гарантия непрерывности данных, // которая необходима для наращивания счетчиков (итоговых) в stat_summary. $grp_data_sql = "(\n SELECT user_id,\n guest_id,\n CASE WHEN guest_id = 0 THEN guest_ip ELSE '' END as guest_ip,\n by_e,\n MAX(_time) as _time,\n MAX((referer_id=" . self::REFID_BLOGS . ")::int)::bool as from_b,\n MAX((referer_id=" . self::REFID_CATALOG . ")::int)::bool as from_c,\n MAX((referer_id=" . self::REFID_PAIDSEATINGS . ")::int)::bool as from_p,\n\t\t\t\tMAX((referer_id=" . self::REFID_PAYPLACE . ")::int)::bool as from_t,\n\t\t\t\tMAX((referer_id=" . self::REFID_FRL_OFFERS . ")::int)::bool as from_o,\n\t\t\t\tMAX((referer_id=" . self::REFID_SEARCH . ")::int)::bool as from_s\n FROM ___tmp_arc\n GROUP BY \n user_id,\n guest_id,\n CASE WHEN guest_id = 0 THEN guest_ip ELSE '' END,\n by_e,\n DATE_TRUNC('hour', _time)\n )"; $sql = "INSERT INTO stat_hourly (user_id, guest_id, guest_ip, by_e, _time, from_b, from_c, from_p, from_t, from_o, from_s) SELECT * FROM {$grp_data_sql} t"; if (!($res = $DB->squery($sql))) { $e = $DB->error; $DB->squery("ROLLBACK TO SAVEPOINT arc_created"); // откатываемся к "после создания вр. таблицы" $this->log('Ошибка при инсерте в stat_hourly. Возможно конфликт ключей, откатываемся, пытаемся обойти. ' . $e, self::LT_WARNING); $sql = "INSERT INTO stat_hourly (user_id, guest_id, guest_ip, by_e, _time, from_b, from_c, from_p, from_t, from_o, from_s)\n SELECT t.*\n FROM {$grp_data_sql} t\n LEFT JOIN\n stat_hourly h\n ON h.user_id = t.user_id\n AND h.guest_id = t.guest_id\n AND h.guest_ip = t.guest_ip\n AND h._time = t._time\n \n-- !!! Нужно AND DATE_TRUNC('hour', h._time) = DATE_TRUNC('hour', t._time)\n-- !!! и индекс переделать (user_id, guest_id, guest_ip, DATE_TRUNC('hour', _time))\n \n WHERE h.user_id IS NULL"; if (!($res = $DB->squery($sql))) { $e = $DB->error; $DB->rollback(); // откатываемся по полной. return $this->log('Ошибка не в конфликте ключей. ' . $e, self::LT_ERROR); } } $this->log('Получено ' . pg_affected_rows($res) . ' строк.', self::LT_NOTICE); pg_free_result($res); // Фиксируем все это дело. if (!$DB->commit()) { $e = $DB->error; $DB->rollback(); return $this->log('Не удалось зафиксировать транзакцию. ' . $e, self::LT_ERROR); } $DB->squery('DROP TABLE IF EXISTS ___tmp_arc'); // (д) Удаляем эспортированные данные из stat_log_t. Они уже не нужны, т.к. они уже есть в stat_hourly. // Если данные не удалились, то в следующий раз сработает проверка на дубликаты в пункте (г), так что все должно // быть окей. $DB->start(); $sql = "DELETE FROM stat_log_t WHERE _time < ?"; if (!($res = $DB->query($sql, $H)) || !$DB->commit()) { $e = $DB->error; $DB->rollback(); $this->log('Ошибка при удалении из stat_log_t. ' . $e, self::LT_WARNING); } else { pg_free_result($res); } $this->log('Ок.', self::LT_NOTICE); } $this->log('Пылесосим stat_log_t.', self::LT_NOTICE); if (!$DB->squery('VACUUM FULL stat_log_t')) { $this->log('VACUUM FULL stat_log_t не сработал. ' . $DB->error, self::LT_WARNING); } return 0; }
<?php include 'config.inc'; $db = pg_connect($conn_str); $rows = pg_copy_to($db, $table_name); pg_query($db, "DELETE FROM {$table_name}"); pg_copy_from($db, $table_name, $rows); echo "OK";
$i = 0; $field_headings = array(); while ($row = pg_fetch_object($result)) { $field_headings[] = $row->column_name; switch ($row->column_name) { case 'id': $pos_id = $i; break; case 'reporter_id': $pos_reporter_id = $i; break; } $i++; } array_splice($field_headings, $pos_id, 1); if ($data = pg_copy_to($db->getHandle(), 'incidents', "\t")) { header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="My PAT Facebook reports.tsv"'); header('Pragma: no-cache'); if (isset($_GET['header'])) { print implode("\t", $field_headings) . "\n"; } foreach ($data as $line) { $fields = explode("\t", $line); if ($user_id == $fields[$pos_reporter_id]) { array_splice($fields, $pos_id, 1); print implode("\t", $fields); } } } exit;