Fi1osof 15 ноября 2015 2 0
В продолжение предыдущего топика про сложные SQL-запросы, хотел бы поделиться свежим опытом.

Сегодня на нашем сайте Клуба я решил сделать одну полезную штуку для себя — на страницах топиков и тегов выводить информацию о сопутствующих услугах.

Справедливости ради сразу замечу, что данная «реклама» выводится только топиках, написанных мною, и не паразитирует на чужих топиках.

В целом все логично: есть какая-то статья, описывающая произвольную проблему, и есть соответствующая этой проблеме услуга. К примеру, проблема «тормозит сайт», услуга «техническая оптимизация сайта». Собственно, все что здесь нужно — это прописать желаемые теги в документах услуг, а при заходе на страницу мы выполняем поиск этих услуг по тегам текущего документа-топика (а теги у нас заполнены во всех топиках). И вот здесь есть ряд тонкостей, о которых я и расскажу ниже.

1. Дополнительное поле тегов в услугах.
Это обычное TV-поле «Список (множественный выбор)». В поле Возможные значения прописываем SQL-запрос.
@SELECT DISTINCT tag FROM `[[+PREFIX]]society_topic_tags` ORDER BY 1


Это позволит формировать всегда актуальный список тегов.

К слову, список тегов — почти 900 штук, но в админке тормоза с этим полем не замечены (хотя не знаю как на слабых тачках будет себя вести).

Указываем нужные теги и сохраняем. Далее остается только прописать поиск тих услуг по тегам. И вот здесь начинается интересное… Значение данной TV-шки записывается в БД одной строкой с разделителем ||, к примеру оптимизация||производительность||тормозит сайт||нагрузка на сервер. И все бы ничего, да только поиск по этой строке нам надо выполнить как правило тоже не одного тега, а нескольких, к примеру оптимизация, тормозит сайт, нагрузка на сервер. Ясное дело, что порядок тегов не всегда будет совпадать.
Еще раз уточню задачу: нам надо найти все документы, в которых будет найден хоть один из перечисленных тегов.

Итак, вот конечный код (сразу для вставки в getdata-процессор):
public function prepareQueryBeforeCount(xPDOQuery $c){
        $c = parent::prepareQueryBeforeCount($c);
        
        $alias = $c->getAlias(); 
        
        $where = array();
        
        // Поиск по тегу
        if($tags = trim($this->getProperty('tags'))){
            
            $tags_arr = explode(',', $tags);
            $tags_arr = array_map('trim', $tags_arr);
            $tags_arr = array_map(function($str){
                global $modx;
                $str = $modx->quote($str);
                $str = "FIND_IN_SET({$str}, replace(tags_tv.value, '||', ','))";
                return $str;
            }, $tags_arr);
            
            $find_in_set = implode(' OR ', $tags_arr);
            
            $c->innerJoin('modTemplateVarResource', "tags_tv", "tags_tv.tmplvarid = 18 AND tags_tv.contentid = {$alias}.id AND ({$find_in_set})");
        }
        
        if($where){
            $c->where($where);
        }
        
        return $c;
    }

Как видно, здесь я передаваемую строку разбиваю по запятой в массив и набиваю в итоге вот в такую хитрую строку:
FIND_IN_SET('оптимизация', replace(tags_tv.value, '||', ',')) OR FIND_IN_SET('тормозит сайт', replace(tags_tv.value, '||', ',')) OR FIND_IN_SET('нагрузка на сервер', replace(tags_tv.value, '||', ','))

Что здесь происходит?
1. replace(tags_tv.value, '||', ',') заменяет в TV-значении разделитель || на разделитель, (запятая). К сожалению, в этой ТВшке нельзя указать тип разделителя для записываемого значения.
2. FIND_IN_SET выполняет поиск заданного значения в строке с разделителем. В данной строке разделитель-запятая как бы разбивает строку на массив, в котором можно выполнять поиск с четким вхождением.

Ну а далее мы уже джоиним TV-поле с этим условием. Полный итоговый запрос выглядит примерно так:
SELECT modResource.* 
    FROM `modx_site_content` AS `modResource` 
    JOIN `modx_site_tmplvar_contentvalues` `tags_tv` 
        ON tags_tv.tmplvarid = 1 
            AND tags_tv.contentid = modResource.id 
            AND (
                FIND_IN_SET('оптимизация', replace(tags_tv.value, '||', ',')) 
                    OR FIND_IN_SET('тормозит сайт', replace(tags_tv.value, '||', ',')) 
                    OR FIND_IN_SET('нагрузка на сервер', replace(tags_tv.value, '||', ','))
            ) 
    WHERE  ( 
        `modResource`.`deleted` = 0 
            AND `modResource`.`hidemenu` = 0 
            AND `modResource`.`published` = 1 
    )


Опять-таки статистики ради: в базе 1500 документов и 1700 записей TV-полей, выборка выполняется практически мгновенно.
0 комментариев
Авторизуйтесь или зарегистрируйтесь (можно через соцсети ), чтобы оставлять комментарии.