A segment is a condition used to filter visits. They can, for example,
select visits that have a specific browser or come from a specific
country, or both.
Individual segment dimensions (such as browserCode and countryCode)
are defined by plugins. Read about the {@hook API.getSegmentDimensionMetadata}
event to learn more.
Plugins that aggregate data stored in Piwik can support segments by
using this class when generating aggregation SQL queries.
### Examples
**Basic usage**
$idSites = array(1,2,3);
$segmentStr = "browserCode==ff;countryCode==CA";
$segment = new Segment($segmentStr, $idSites);
$query = $segment->getSelectQuery(
$select = "table.col1, table2.col2",
$from = array("table", "table2"),
$where = "table.col3 = ?",
$bind = array(5),
$orderBy = "table.col1 DESC",
$groupBy = "table2.col2"
);
Db::fetchAll($query['sql'], $query['bind']);
**Creating a _null_ segment**
$idSites = array(1,2,3);
$segment = new Segment('', $idSites);
$segment->getSelectQuery will return a query that selects all visits
private function insertNumericArchive($tableMonth, $idSite, $period, $date1, $date2, $segment, $doneValue) { $this->insertRow('archive_numeric', $tableMonth, $idSite, $period, $date1, $date2, 'nb_schweetz', 2); $this->insertRow('archive_numeric', $tableMonth, $idSite, $period, $date1, $date2, 'nb_fixes', 3); $this->insertRow('archive_numeric', $tableMonth, $idSite, $period, $date1, $date2, 'nb_wrecks', 4); $doneFlag = 'done'; if (!empty($segment)) { $segmentObj = new Segment($segment, array()); $doneFlag .= $segmentObj->getHash(); } $this->insertRow('archive_numeric', $tableMonth, $idSite, $period, $date1, $date2, $doneFlag, $doneValue); }
/** * @dataProvider getTestDataForSegmentSqlTest */ public function test_SegmentSql_IsCorrectlyDecoratedWithIdSegment($segment, $triggerValue, $expectedPrefix) { if (!empty($triggerValue)) { $_GET['trigger'] = $triggerValue; } $segment = new Segment($segment, array()); $query = $segment->getSelectQuery('*', 'log_visit'); $sql = $query['sql']; if (empty($expectedPrefix)) { $this->assertStringStartsNotWith("/* idSegments", $sql); } else { $this->assertStringStartsWith($expectedPrefix, $sql); } }
protected function checkSegmentValue($definition, $idSite) { // unsanitize so we don't record the HTML entitied segment $definition = Common::unsanitizeInputValue($definition); $definition = str_replace("#", '%23', $definition); // hash delimiter $definition = str_replace("'", '%27', $definition); // not encoded in JS $definition = str_replace("&", '%26', $definition); try { $segment = new Segment($definition, $idSite); $segment->getHash(); } catch (Exception $e) { throw new Exception("The specified segment is invalid: " . $e->getMessage()); } return $definition; }
public static function getDoneFlagArchiveContainsAllPlugins(Segment $segment) { return 'done' . $segment->getHash(); }
/** * join conversion on visit, then actions * make sure actions are joined before conversions * * @group Core */ public function testGetSelectQueryJoinConversionAndActionOnVisit() { $select = 'log_visit.*'; $from = 'log_visit'; $where = false; $bind = array(); $segment = 'visitConvertedGoalId==1;visitServerHour==12;customVariablePageName1==Test'; $segment = new Segment($segment, $idSites = array()); $query = $segment->getSelectQuery($select, $from, $where, $bind); $expected = array("sql" => "\n SELECT\n log_inner.*\n FROM\n (\n SELECT\n log_visit.*\n FROM\n " . Common::prefixTable('log_visit') . " AS log_visit\n LEFT JOIN " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action ON log_link_visit_action.idvisit = log_visit.idvisit\n LEFT JOIN " . Common::prefixTable('log_conversion') . " AS log_conversion ON log_conversion.idlink_va = log_link_visit_action.idlink_va AND log_conversion.idsite = log_link_visit_action.idsite\n WHERE\n log_conversion.idgoal = ? AND HOUR(log_visit.visit_last_action_time) = ? AND log_link_visit_action.custom_var_k1 = ?\n GROUP BY log_visit.idvisit\n ) AS log_inner", "bind" => array(1, 12, 'Test')); $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query)); }
public function test_getSelectQuery_whenPageUrlDoesNotExist_asBothStatements_OR_AND() { $pageUrlFoundInDb = 'example.com/found-in-db'; $actionIdFoundInDb = $this->insertPageUrlAsAction($pageUrlFoundInDb); $select = 'log_visit.*'; $from = 'log_visit'; $where = false; $bind = array(); /** * pageUrl==xyz -- Matches none * pageUrl!=abcdefg -- Matches all * pageUrl=@does-not-exist -- Matches none * pageUrl=='.urlencode($pageUrlFoundInDb) -- Matches one */ $segment = 'visitServerHour==12,pageUrl==xyz;pageUrl!=abcdefg,pageUrl=@does-not-exist,pageUrl==' . urlencode($pageUrlFoundInDb); $segment = new Segment($segment, $idSites = array()); $query = $segment->getSelectQuery($select, $from, $where, $bind); $expected = array("sql" => "\n SELECT\n log_inner.*\n FROM\n (\n SELECT\n log_visit.*\n FROM\n " . Common::prefixTable('log_visit') . " AS log_visit\n LEFT JOIN " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action ON log_link_visit_action.idvisit = log_visit.idvisit\n WHERE (HOUR(log_visit.visit_last_action_time) = ?\n OR (1 = 0))\n AND ((1 = 1)\n OR ( log_link_visit_action.idaction_url IN (SELECT idaction FROM log_action WHERE ( name LIKE CONCAT('%', ?, '%') AND type = 1 )) )\n OR log_link_visit_action.idaction_url = ? )\n GROUP BY log_visit.idvisit\n ORDER BY NULL\n ) AS log_inner", "bind" => array(12, "does-not-exist", $actionIdFoundInDb)); $this->assertEquals($this->removeExtraWhiteSpaces($expected), $this->removeExtraWhiteSpaces($query)); }
protected static function makeLockName($idsite, Period $period, Segment $segment) { $config = Config::getInstance(); $lockName = 'piwik.' . $config->database['dbname'] . '.' . $config->database['tables_prefix'] . '/' . $idsite . '/' . (!$segment->isEmpty() ? $segment->getHash() . '/' : '') . $period->getId() . '/' . $period->getDateStart()->toString('Y-m-d') . ',' . $period->getDateEnd()->toString('Y-m-d'); return $lockName . '/' . md5($lockName . SettingsPiwik::getSalt()); }
public function test_getSelectQuery_withTwoSegments_subqueryNotCached_whenResultsetTooLarge() { $this->enableSubqueryCache(); // do not cache when more than 3 idactions returned by subquery Config::getInstance()->General['segments_subquery_cache_limit'] = 2; list($pageUrlFoundInDb, $actionIdFoundInDb) = $this->insertActions(); $select = 'log_visit.*'; $from = 'log_visit'; $where = false; $bind = array(); /** * pageUrl=@found-in-db-bis -- Will be cached * pageUrl!@not-found -- Too big to cache */ $segment = 'pageUrl=@found-in-db-bis;pageUrl!@not-found'; $segment = new Segment($segment, $idSites = array()); $query = $segment->getSelectQuery($select, $from, $where, $bind); $expected = array("sql" => "\n SELECT\n log_inner.*\n FROM\n (\n SELECT\n log_visit.*\n FROM\n " . Common::prefixTable('log_visit') . " AS log_visit\n LEFT JOIN " . Common::prefixTable('log_link_visit_action') . " AS log_link_visit_action ON log_link_visit_action.idvisit = log_visit.idvisit\n WHERE\n ( log_link_visit_action.idaction_url IN (?) )" . "\n AND ( log_link_visit_action.idaction_url IN (SELECT idaction FROM log_action WHERE ( name NOT LIKE CONCAT('%', ?, '%') AND type = 1 )) ) " . "GROUP BY log_visit.idvisit\n ORDER BY NULL\n ) AS log_inner", "bind" => array(2, "not-found")); $cache = StaticContainer::get('Piwik\\Tracker\\TableLogAction\\Cache'); $this->assertTrue(!empty($cache->isEnabled)); $this->assertEquals($this->removeExtraWhiteSpaces($expected), $this->removeExtraWhiteSpaces($query)); }
private function loadLastVisitorDetailsFromDatabase($idSite, $period, $date, $segment = false, $countVisitorsToFetch = 100, $visitorId = false, $minTimestamp = false, $filterSortOrder = false) { $where = $whereBind = array(); list($whereClause, $idSites) = $this->getIdSitesWhereClause($idSite); $where[] = $whereClause; $whereBind = $idSites; if (strtolower($filterSortOrder) !== 'asc') { $filterSortOrder = 'DESC'; } $orderBy = "idsite, visit_last_action_time " . $filterSortOrder; $orderByParent = "sub.visit_last_action_time " . $filterSortOrder; if (!empty($visitorId)) { $where[] = "log_visit.idvisitor = ? "; $whereBind[] = @Common::hex2bin($visitorId); } if (!empty($minTimestamp)) { $where[] = "log_visit.visit_last_action_time > ? "; $whereBind[] = date("Y-m-d H:i:s", $minTimestamp); } // If no other filter, only look at the last 24 hours of stats if (empty($visitorId) && empty($countVisitorsToFetch) && empty($period) && empty($date)) { $period = 'day'; $date = 'yesterdaySameTime'; } // SQL Filter with provided period if (!empty($period) && !empty($date)) { $currentSite = new Site($idSite); $currentTimezone = $currentSite->getTimezone(); $dateString = $date; if ($period == 'range') { $processedPeriod = new Range('range', $date); if ($parsedDate = Range::parseDateRange($date)) { $dateString = $parsedDate[2]; } } else { $processedDate = Date::factory($date); if ($date == 'today' || $date == 'now' || $processedDate->toString() == Date::factory('now', $currentTimezone)->toString()) { $processedDate = $processedDate->subDay(1); } $processedPeriod = Period\Factory::build($period, $processedDate); } $dateStart = $processedPeriod->getDateStart()->setTimezone($currentTimezone); $where[] = "log_visit.visit_last_action_time >= ?"; $whereBind[] = $dateStart->toString('Y-m-d H:i:s'); if (!in_array($date, array('now', 'today', 'yesterdaySameTime')) && strpos($date, 'last') === false && strpos($date, 'previous') === false && Date::factory($dateString)->toString('Y-m-d') != Date::factory('now', $currentTimezone)->toString()) { $dateEnd = $processedPeriod->getDateEnd()->setTimezone($currentTimezone); $where[] = " log_visit.visit_last_action_time <= ?"; $dateEndString = $dateEnd->addDay(1)->toString('Y-m-d H:i:s'); $whereBind[] = $dateEndString; } } if (count($where) > 0) { $where = join("\n\t\t\t\tAND ", $where); } else { $where = false; } $segment = new Segment($segment, $idSite); // Subquery to use the indexes for ORDER BY $select = "log_visit.*"; $from = "log_visit"; $subQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy); $sqlLimit = $countVisitorsToFetch >= 1 ? " LIMIT 0, " . (int) $countVisitorsToFetch : ""; // Group by idvisit so that a visitor converting 2 goals only appears once $sql = "\n\t\t\tSELECT sub.* FROM (\n\t\t\t\t" . $subQuery['sql'] . "\n\t\t\t\t{$sqlLimit}\n\t\t\t) AS sub\n\t\t\tGROUP BY sub.idvisit\n\t\t\tORDER BY {$orderByParent}\n\t\t"; try { $data = Db::fetchAll($sql, $subQuery['bind']); } catch (Exception $e) { echo $e->getMessage(); exit; } $dataTable = new DataTable(); $dataTable->addRowsFromSimpleArray($data); // $dataTable->disableFilter('Truncate'); if (!empty($data[0])) { $columnsToNotAggregate = array_map(function () { return 'skip'; }, $data[0]); $dataTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, $columnsToNotAggregate); } return $dataTable; }
/** * @param $idSite * @param $period * @param $date * @param $segment * @param int $offset * @param int $limit * @param $visitorId * @param $minTimestamp * @param $filterSortOrder * @return array * @throws Exception */ public function makeLogVisitsQueryString($idSite, $period, $date, $segment, $offset, $limit, $visitorId, $minTimestamp, $filterSortOrder) { // If no other filter, only look at the last 24 hours of stats if (empty($visitorId) && empty($limit) && empty($offset) && empty($period) && empty($date)) { $period = 'day'; $date = 'yesterdaySameTime'; } list($whereClause, $bindIdSites) = $this->getIdSitesWhereClause($idSite); list($whereBind, $where) = $this->getWhereClauseAndBind($whereClause, $bindIdSites, $idSite, $period, $date, $visitorId, $minTimestamp); if (strtolower($filterSortOrder) !== 'asc') { $filterSortOrder = 'DESC'; } $segment = new Segment($segment, $idSite); // Subquery to use the indexes for ORDER BY $select = "log_visit.*"; $from = "log_visit"; $groupBy = false; $limit = $limit >= 1 ? (int) $limit : 0; $offset = $offset >= 1 ? (int) $offset : 0; $orderBy = ''; if (count($bindIdSites) <= 1) { $orderBy = 'idsite ' . $filterSortOrder . ', '; } $orderBy .= "visit_last_action_time " . $filterSortOrder; $orderByParent = "sub.visit_last_action_time " . $filterSortOrder; // this $innerLimit is a workaround (see https://github.com/piwik/piwik/issues/9200#issuecomment-183641293) $innerLimit = $limit; if (!$segment->isEmpty()) { $innerLimit = $limit * 10; } $innerQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy, $groupBy, $innerLimit, $offset); $bind = $innerQuery['bind']; // Group by idvisit so that a given visit appears only once, useful when for example: // 1) when a visitor converts 2 goals // 2) when an Action Segment is used, the inner query will return one row per action, but we want one row per visit $sql = "\n\t\t\tSELECT sub.* FROM (\n\t\t\t\t" . $innerQuery['sql'] . "\n\t\t\t) AS sub\n\t\t\tGROUP BY sub.idvisit\n\t\t\tORDER BY {$orderByParent}\n\t\t"; if ($limit) { $sql .= sprintf("LIMIT %d \n", $limit); } return array($sql, $bind); }
public function generateQuery($select, $from, $where, $groupBy, $orderBy) { $bind = $this->getBindDatetimeSite(); $query = $this->segment->getSelectQuery($select, $from, $where, $bind, $orderBy, $groupBy); return $query; }
public function popout() { header("Access-Control-Allow-Origin: *"); $params = UrlHelper::getArrayFromQueryString($_SERVER['QUERY_STRING']); $request = new Tracker\Request($params); // the IP is needed by isExcluded() and GoalManager->recordGoals() $ip = $request->getIp(); $visitorInfo['location_ip'] = $ip; /** * Triggered after visits are tested for exclusion so plugins can modify the IP address * persisted with a visit. * * This event is primarily used by the **PrivacyManager** plugin to anonymize IP addresses. * * @param string &$ip The visitor's IP address. */ Piwik::postEvent('Tracker.setVisitorIp', array(&$visitorInfo['location_ip'])); /*** * Visitor recognition */ $settings = new Tracker\Settings($request, $visitorInfo['location_ip']); $visitor = new Visitor($request, $settings->getConfigId(), $visitorInfo); $visitor->recognize(); $visitorInfo = $visitor->getVisitorInfo(); if (!isset($visitorInfo['location_browser_lang'])) { return "Who are you ?"; } $idSite = Common::getRequestVar('idsite', null, 'int'); $conversation = new ChatConversation($idSite, bin2hex($visitorInfo['idvisitor'])); /*** * Segment recognition */ foreach (ChatAutomaticMessage::getAll($idSite) as $autoMsg) { $segment = ChatSegment::get($autoMsg['segmentID']); $fetchSegment = new Segment($segment['definition'], array($idSite)); $query = $fetchSegment->getSelectQuery("idvisitor", "log_visit", "log_visit.idvisitor = ?", array($visitorInfo['idvisitor'])); $rows = Db::fetchAll($query['sql'], $query['bind']); if (count($rows) == 0) { continue; } if ($autoMsg['segmentID'] != $segment['idsegment']) { continue; } $getAlreadyReceivedMsg = $conversation->getAutomaticMessageReceivedById($autoMsg['id']); if (count($getAlreadyReceivedMsg) > 0) { // If the AutoMsg is a "one shot" if ($autoMsg['frequency'] == 0) { continue; } if ($autoMsg['frequency'] != 0) { // Now, we gonna try to define when the last AutoMsg received has been sent list($freqTime, $freqScale) = explode('|', $autoMsg['frequency']); if ($freqScale == "w") { $dayMultiplier = 7; } elseif ($freqScale == "m") { $dayMultiplier = 30; } else { $dayMultiplier = 1; } $secToWait = 3600 * 24 * $freqTime * $dayMultiplier; // Is it older than the time range needed to wait ? if ($getAlreadyReceivedMsg[0]['microtime'] + $secToWait > microtime(true)) { continue; } } } $conversation->sendMessage($autoMsg['message'], $autoMsg['transmitter'], $autoMsg['id']); } $view = new View('@Chat/popout.twig'); $view->idvisitor = bin2hex($visitorInfo['idvisitor']); $view->idsite = $idSite; $view->timeLimit = time() - 2 * 60 * 60; $view->isStaffOnline = ChatPiwikUser::isStaffOnline(); $view->siteUrl = ChatSite::getMainUrl($idSite); $view->lang = $visitorInfo['location_browser_lang']; return $view->render(); }
public function test_getSelectQuery_whenQueryingLogConversionWithSegmentThatUsesLogLinkVisitActionAndLogVisit_shouldUseSubselectGroupedByIdVisitAndBuster() { $select = 'log_conversion.idgoal AS `idgoal`, count(*) AS `1`, count(distinct log_conversion.idvisit) AS `3`, ROUND(SUM(log_conversion.revenue),2) AS `2`'; $from = 'log_conversion'; $where = 'log_conversion.server_time >= ? AND log_conversion.server_time <= ? AND log_conversion.idsite IN (?)'; $groupBy = 'log_conversion.idgoal'; $bind = array('2015-10-14 11:00:00', '2015-10-15 10:59:59', 1); $segment = 'visitorType==returning,visitorType==returningCustomer;pageUrl=@/'; $segment = new Segment($segment, $idSites = array()); $query = $segment->getSelectQuery($select, $from, $where, $bind, $orderBy = false, $groupBy); $logConversionTable = Common::prefixTable('log_conversion'); $logLinkVisitActionTable = Common::prefixTable('log_link_visit_action'); $logVisitTable = Common::prefixTable('log_visit'); $expectedBind = $bind; $expectedBind[] = 1; $expectedBind[] = 2; $expectedBind[] = '/'; $expected = array("sql" => "\n SELECT log_inner.idgoal AS `idgoal`, count(*) AS `1`, count(distinct log_inner.idvisit) AS `3`, ROUND(SUM(log_inner.revenue),2) AS `2`\n FROM (\n SELECT log_conversion.idgoal, log_conversion.idvisit, log_conversion.revenue\n FROM {$logConversionTable} AS log_conversion\n LEFT JOIN {$logLinkVisitActionTable} AS log_link_visit_action ON log_conversion.idvisit = log_link_visit_action.idvisit\n LEFT JOIN {$logVisitTable} AS log_visit ON log_visit.idvisit = log_link_visit_action.idvisit\n WHERE ( log_conversion.server_time >= ?\n AND log_conversion.server_time <= ?\n AND log_conversion.idsite IN (?) )\n AND ( (log_visit.visitor_returning = ?\n OR log_visit.visitor_returning = ?)\n AND ( log_link_visit_action.idaction_url IN (SELECT idaction FROM log_action WHERE ( name LIKE CONCAT('%', ?, '%') AND type = 1 )) ) )\n GROUP BY CONCAT(log_conversion.idvisit, '_' , log_conversion.idgoal, '_', log_conversion.buster)\n ORDER BY NULL )\n AS log_inner\n GROUP BY log_inner.idgoal", "bind" => $expectedBind); $this->assertEquals($this->removeExtraWhiteSpaces($expected), $this->removeExtraWhiteSpaces($query)); }
/** * @param $idSite * @param $period * @param $date * @param $segment * @param int $offset * @param int $limit * @param $visitorId * @param $minTimestamp * @param $filterSortOrder * @return array * @throws Exception */ public function makeLogVisitsQueryString($idSite, $period, $date, $segment, $offset, $limit, $visitorId, $minTimestamp, $filterSortOrder) { // If no other filter, only look at the last 24 hours of stats if (empty($visitorId) && empty($limit) && empty($offset) && empty($period) && empty($date)) { $period = 'day'; $date = 'yesterdaySameTime'; } list($whereBind, $where) = $this->getWhereClauseAndBind($idSite, $period, $date, $visitorId, $minTimestamp); if (strtolower($filterSortOrder) !== 'asc') { $filterSortOrder = 'DESC'; } $segment = new Segment($segment, $idSite); // Subquery to use the indexes for ORDER BY $select = "log_visit.*"; $from = "log_visit"; $groupBy = false; $limit = $limit >= 1 ? (int) $limit : 0; $offset = $offset >= 1 ? (int) $offset : 0; $orderBy = "idsite, visit_last_action_time " . $filterSortOrder; $orderByParent = "sub.visit_last_action_time " . $filterSortOrder; $subQuery = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy, $groupBy, $limit, $offset); $bind = $subQuery['bind']; // Group by idvisit so that a visitor converting 2 goals only appears once $sql = "\n\t\t\tSELECT sub.* FROM (\n\t\t\t\t" . $subQuery['sql'] . "\n\t\t\t) AS sub\n\t\t\tGROUP BY sub.idvisit\n\t\t\tORDER BY {$orderByParent}\n\t\t"; return array($sql, $bind); }
protected function adjacentVisitorId($idSite, $visitorId, $visitLastActionTime, $segment, $getNext) { if ($getNext) { $visitLastActionTimeCondition = "sub.visit_last_action_time <= ?"; $orderByDir = "DESC"; } else { $visitLastActionTimeCondition = "sub.visit_last_action_time >= ?"; $orderByDir = "ASC"; } $visitLastActionDate = Date::factory($visitLastActionTime); $dateOneDayAgo = $visitLastActionDate->subDay(1); $dateOneDayInFuture = $visitLastActionDate->addDay(1); $Generic = Factory::getGeneric(); $bin_idvisitor = $Generic->binaryColumn('log_visit.idvisitor'); $select = "{$bin_idvisitor}, MAX(log_visit.visit_last_action_time) as visit_last_action_time"; $from = "log_visit"; $where = "log_visit.idsite = ? AND log_visit.idvisitor <> ? AND visit_last_action_time >= ? and visit_last_action_time <= ?"; $whereBind = array($idSite, $visitorId, $dateOneDayAgo->toString('Y-m-d H:i:s'), $dateOneDayInFuture->toString('Y-m-d H:i:s')); $orderBy = "MAX(log_visit.visit_last_action_time) {$orderByDir}"; $groupBy = "log_visit.idvisitor"; $segment = new Segment($segment, $idSite); $queryInfo = $segment->getSelectQuery($select, $from, $where, $whereBind, $orderBy, $groupBy); $sql = "SELECT sub.idvisitor, sub.visit_last_action_time\n FROM ({$queryInfo['sql']}) as sub\n WHERE {$visitLastActionTimeCondition}\n LIMIT 1"; $bind = array_merge($queryInfo['bind'], array($visitLastActionTime)); return $this->db->fetchOne($sql, $bind); }
public function generateQuery($select, $from, $where, $groupBy, $orderBy) { $bind = $this->getGeneralQueryBindParams(); $query = $this->segment->getSelectQuery($select, $from, $where, $bind, $orderBy, $groupBy); return $query; }
public function generateQuery($select, $from, $where, $groupBy, $orderBy, $limit = 0, $offset = 0) { $bind = $this->getGeneralQueryBindParams(); $query = $this->segment->getSelectQuery($select, $from, $where, $bind, $orderBy, $groupBy, $limit, $offset); $select = 'SELECT'; if ($this->queryOriginHint && is_array($query) && 0 === strpos(trim($query['sql']), $select)) { $query['sql'] = trim($query['sql']); $query['sql'] = 'SELECT /* ' . $this->queryOriginHint . ' */' . substr($query['sql'], strlen($select)); } return $query; }