Сквозной поиск для сайта на Yii

Документы и лупа

При разработке любого более-менее крупного проекта на Yii у программиста может возникнуть необходимость внедрения поиска. И если для интернет-магазина будет достаточно искать только по каталогу, то для информационного сайта нужно обеспечить сквозной поиск по нескольким сущностям сразу. В конце этого урока мы рассмотрим готовые решения по поиску, а в начале для образовательных целей напишем свой велосипед.

Поиск по нескольким таблицам

На сайте из нескольких разделов более вероятно, что материалы описываются разными сущностями и хранятся в разных таблицах. Исключение составляют проекты с CCK, например Drupal с модулем Views или 1C Bitrix с его инфоблоками. Чаще всего в них все придуманные разработчиком в панели управления сущности хранятся в одной общей таблице. Но на Yii такой подход не распространён, поэтому возникает задача склейки выборок из нескольких таблиц. Эту проблему мы и рассмотрим.

В простейшем случае, для поиска по таблице используется SQL запрос с оператором LIKE. По одной таблице мы можем искать вхождение строки в заголовок или текст так:

SELECT title, text FROM tbl_post WHERE title LIKE :query OR text LIKE :query

Пока не будем вдаваться в поиск по словоформам, по вариантам перестановки искомых слов и прочей семантике.

Предположим, что нам нужно производить поиск по блогу, новостям и страницам. Нам на помощь придёт SQL оператор UNION. Именно он служит для склейки результатов нескольких запросов в одну ленту. Самое банальное решение по выборке из трёх таблиц может выглядеть так:

SELECT title, text FROM tbl_new WHERE title LIKE :query OR text LIKE :query UNION 
SELECT title, text FROM tbl_post WHERE title LIKE :query OR text LIKE :query UNION 
SELECT title, text FROM tbl_page WHERE title LIKE :query OR text LIKE :query

Или если вынесем общие элементы за скобки:

SELECT t.* FROM (
    SELECT title, text FROM tbl_new UNION 
    SELECT title, text FROM tbl_post UNION 
    SELECT title, text FROM tbl_page
) AS t WHERE t.title LIKE :query OR t.text LIKE :query;

От трёх поисков и склейки мы перешли к одному поиску по одной склеенной временной выборке. На основе этого запроса мы и будем строить варианты решения. Первый вариант – это непосредственное использование этого запроса в DAO.

Если, например, для записей блога используется Markdown синтаксис или какие-либо другие фильтры с преобразованием HTML кода в поле text_purified при сохранении записи, то мы можем использовать псевдонимы для подмены поля text:

SELECT title, text FROM tbl_new UNION 
SELECT title, text_purified AS text FROM tbl_post UNION 
SELECT title, text FROM tbl_page

В любом случае итоговые имена полей в каждом подзапросе должны быть одинаковыми. Теперь приступим к поиску по результирующей выборке.

Поиск с использованием DAO

Если записей на сайте не так уж и много, то можно одним запросом без указания LIMIT и OFFSET выбрать все результаты в ассоциированный массив $items и передать его в CArrayDataProvider, который позаботится о разбивке на страницы:

class SearchController extends CController
{
    const ITEMS_PER_PAGE = 10;
 
    public function actionIndex($query)
    {                      
        $items = Yii::app()->db->createCommand("
            SELECT t.* FROM (
                SELECT title, text FROM {{new}} UNION 
                SELECT title, text FROM {{post}} UNION 
                SELECT title, text FROM {{page}}
            ) AS t WHERE t.title LIKE :query OR t.text LIKE :query
        ")->queryAll(true, array(
            ':query'=>'%' . $query . '%',
        ));       
 
        $dataProvider = new CArrayDataProvider($items, array(
            'pagination'=>array(
                'pageSize'=>self::ITEMS_PER_PAGE,
            )
        ));
 
        $this->render('index', array(
            'dataProvider'=>$sqlDataProvider,
            'query'=>$query,
        ));
    }
}

Это не очень оптимальный вариант, так как если найдётся тысяча записей, то в массиве окажутся они все. Для избежания такой растраты памяти можно перейти к использованию CSqlDataProvider, передав ему непосредственно сам SQL-запрос и число элементов:

class SearchController extends CController
{
    const ITEMS_PER_PAGE = 10;
 
    public function actionIndex($query)
    {
        $from = "(
            SELECT CONCAT('new_', id) AS id, title, text FROM {{new}} UNION
            SELECT CONCAT('post_', id) AS id, title, text FROM {{post}} UNION
            SELECT CONCAT('page_', id) AS id, title, text FROM {{page}}
        )";        
        $where = 'WHERE t.title LIKE :query OR t.text LIKE :query';        
        $params = array(
            ':query'=>'%' . $query . '%',
        );
 
        $countSql = 'SELECT COUNT(*) FROM ' . $from . ' AS t ' . $where;
        $dataSql = 'SELECT t.* FROM ' . $from . ' AS t ' . $where;
 
        $count = Yii::app()->db->createCommand($countSql)->queryScalar($params);
        $sqlDataProvider = new CSqlDataProvider($dataSql, array(
            'params'=>$params,
            'keyField'=>'id',
            'totalItemCount'=>$count,
            'pagination'=>array(
                'pageSize'=>self::ITEMS_PER_PAGE,
            ),
        ));
 
        $this->render('index', array(
            'dataProvider'=>$sqlDataProvider,
            'query'=>$query,
        ));
    }
}

Здесь мы для удобства разделили запросы на части $from и $where.

Класс CArrayDataProvider сам произведёт установку параметров LIMIT и OFFSET для разбивки на страницы. Этому компоненту нужно указать ключевое поле, но значения id из разных таблиц будут повторяться. Поэтому мы схитрили и собрали это поле динамически (добавив префиксы):

SELECT CONCAT('new_', id) AS id, ...
SELECT CONCAT('post_', id) AS id, ...
SELECT CONCAT('page_', id) AS id, ...

Теперь этот CSqlDataProvider можно использовать привычным способом как и CActiveDataProvider в представлении views/search/index.php:

<h1>Поиск по запросу <?php echo CHtml::encode($query); ?></h1>
 
<?php $this->widget('zii.widgets.CListView', array(
    'dataProvider'=>$dataProvider,
    'itemView'=>'_item',
)); ?>

Но выводить элементы в представлении views/search/_item.php нужно с той лишь разницей, что у нас каждая запись представляет из себя ассоциативный массив, а не объект:

<h2><?php echo CHtml::encode($data['title']; ?></h2>
<p><?php echo mb_substr(strip_tags($data['text']), 200, 'UTF-8'); ?></p>

Здесь мы ограничились выводом в представлении первых 200 символов текста. При желании можно придумать подсветку найденного слова. Например так:

class SearchHighlighter
{
    public static function getFragment($text, $word){
        if ($word)
        {
            $pos = max(mb_stripos($text, $word, null, 'UTF-8') - 100, 0);
            $fragment = mb_substr($text, $pos, 200, 'UTF-8');
            $highlighted = str_ireplace($word, '<mark>' . $word . '</mark>', $fragment);
        } else {
            $highlighted = mb_substr($text, 0, 200, 'UTF-8');
        }
        return $highlighted;
    }
}
<h1>Поиск по запросу <?php echo CHtml::encode($query); ?></h1>
 
<?php $this->widget( 'zii.widgets.CListView', array(
    'dataProvider'=>$dataProvider,
    'itemView'=>'_view',
    'viewData'=> array('query'=>$query),
)); ?>
<h2><?php echo CHtml::encode($data['title']; ?></h2>
<p><?php echo SearchHighlighter::getFragment(strip_tags($data['text']), $query); ?></p>

Наш поиск уже должен работать. Осталось рассмотреть ещё пару нюансов.

Добавление ссылки на материал

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

В простейшем случае адреса можно генерировать используя конкатенацию прямо в запросе:

SELECT t.* FROM (
    SELECT title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
    SELECT title, text, CONCAT('/blog/post/', id) AS url FROM {{post}} UNION 
    SELECT title, text, CONCAT('/page/', alias) AS url FROM {{page}}
) ...

В каждой части запроса можно использовать связи таблиц. Например, если ссылки на посты блога должны включать в себя псевдоним категории, то можно построить JOIN для таблиц постов и категорий:

SELECT t.* FROM (
    SELECT CONCAT('new_', id) AS id, title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
    SELECT CONCAT('post_', p.id) AS id, p.title AS title, p.text_purified AS text, CONCAT('/blog/', c.alias, '/' , p.id) AS url FROM {{post}} AS p LEFT JOIN {{category}} AS c ON p.category_id = c.id UNION
    SELECT CONCAT('page_', id) AS id, title, text, CONCAT('/page/', alias) AS url FROM {{page}}
) ...

Теперь для вывода ссылки можно использовать значение псевдополя $data['url']:

<h2><?php echo CHtml::link(CHtml::encode($data['title']), $data['url']); ?></h2>

Этот подход подойдёт практически для всех случаев, за исключением многоуровневых категорий, вложенных страниц и прочих нестандартных реализаций ЧПУ. Этого мы коснёмся вдальнейшем. А сейчас попробуем упростить наши результирующие запросы.

Использование представлений в БД

При работе с СУБД на уроках информатики ученикам даётся задание создать несколько таблиц, а потом на основе содержащихся в них данных сконструировать какие-либо представления. Это на самом деле виртуальные таблицы, которые не содержат своей информации, а выводят записи из других таблиц. Фактически, это именованный и сохранённый в БД отдельный SQL запрос. Этим инструментом мы и можем воспользоваться.

Первым делом, создадим в базе данных наше представление:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT CONCAT('new_', id) AS id, title, text, CONCAT('/news/', id) AS url FROM tbl_new UNION 
SELECT CONCAT('post_', id) AS id, title, text, CONCAT('/blog/post/', id) AS url FROM tbl_post UNION 
SELECT CONCAT('page_', id) AS id, title, text, CONCAT('/page/', alias) AS url FROM tbl_page;

Теперь мы можем делать выборку из него как из обычной таблицы:

SELECT * FROM tbl_view_search WHERE title LIKE '%Yii%' OR text LIKE '%Yii%';

Представление можно создать данным запросом вручную, либо в миграции или (для экспериментов) непосредственно в контроллере перед запросом:

Yii::app()->db->createCommand("
    CREATE OR REPLACE VIEW {{view_search}} AS
    SELECT CONCAT('new_', id) AS id, title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
    SELECT CONCAT('post_', id) AS id, title, text, CONCAT('/blog/post/', id) AS url  FROM {{post}} UNION 
    SELECT CONCAT('page_', id) AS id, title, text, CONCAT('/page/', alias) AS url  FROM {{page}}
")->execute();

Теперь будем использовать это представление как таблицу для наших выборок:

$where = 't.title LIKE :query OR t.text LIKE :query';  
$countSql = 'SELECT COUNT(*) FROM {{view_search}} WHERE ' . $where;
$dataSql = 'SELECT * FROM {{view_search}} WHERE ' . $where;

Достоинство этого подхода (с выносом подзапроса в именованное представление) в том, что можно настраивать поиск для каждого сайта прямо в схеме базы, не влезая в программный код приложения.

Если Вы не любите использовать DAO или если ссылки в вашем проекте генерируются не очень банально (например, если проект многоязычный и нужно использовать указание языка в адресе), то можно воспользоваться достоинствами ActiveRecord.

Использование ActiveRecord

Замечательной особенностью представлений в БД является то, что они воспринимаются внешним миром как таблицы. Соответственно, для работы с этой виртуальной таблицей как с реальной мы можем использовать модель CActiveRecord.

Например, если у нас есть представление:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
SELECT title, text, CONCAT('/blog/post/', id) AS url  FROM {{post}} UNION 
SELECT title, text, CONCAT('/page/', alias) AS url  FROM {{page}}

мы можем создать для него модель:

/**
 * @property string $title
 * @property string $text
 * @property string $url
 */
class Search extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return '{{view_search}}';
    }
}

Теперь в коде контроллера мы можем работать с этой моделью как с любой другой:

class SearchController extends Controller
{
    public function actionIndex($query)
    {
        $criteria = new CDbCriteria();
        $criteria->addSearchCondition('title', $query);
        $criteria->addSearchCondition('text', $query, true, 'OR');
 
        $dataProvider = new CActiveDataProvider('Search', array(
            'criteria'=>$criteria,
        ));
 
        $this->render('search', array(
            'dataProvider'=>$dataProvider,
            'query'=>$query,
        ));
    }
}

и также привычно выводить результаты поиска:

<h2><?php echo CHtml::link(CHtml::encode($data->title), $data->url); ?></h2>

Здесь, как и прежде, адреса собираются конкатенацией в самом запросе.

Построение нестандартных ссылок

Иногда конкатенации и подключения частей через JOIN запрос может не хватать. Например, при генерации ссылок на вложенные страницы.

Хорошей практикой является добавление метода getUrl() в модель. Это позволяет просто использовать везде где нужно $model->url вместо громоздкой записи Yii::app()->createUrl(...) с различными для каждой сущности параметрами.

Добавим этот метод в наши модели:

class News extends CAtiveRecord
{
    ...
 
    private $_url;
 
    public function getUrl(){
        if ($this->_url === null)
            $this->_url = Yii::app()->createUrl('news/view', array('id'=>$this->id, 'alias'=>$this->alias));
        return $this->_url;
    }
}
 
class Post extends CAtiveRecord
{
    ...
 
    private $_url;
 
    public function getUrl(){
        if ($this->_url === null)
            $this->_url = Yii::app()->createUrl('blog/view', array('id'=>$this->id, 'alias'=>$this->alias));
        return $this->_url;
    }
}
 
class Page extends CAtiveRecord
{
    ...
 
    private $_url;
 
    public function getUrl()
    {
        if ($this->_url === null)
        {
            $this->_url = Yii::app()->createUrl('page/view', array('path'=>$this->getPath()));
        }
        return $this->_url;
    }
 
    public function getPath()
    {
        ...
    }    
}

Модель страницы отличается от моделей записи блога и новости тем, что содержит метод getPath, который склеивает вложенный псевдоним (например about/company/personal). А потом уже модель строит адрес на основе этого псевдонима.

Теперь для генерации ссылок для наших результатов поиска нужно использовать метод getUrl() соответствующей модели.

Изменим наше представление следующим образом:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT title, text, id AS material_id, 'News' AS material_class FROM tbl_new UNION 
SELECT title, text, id AS material_id, 'Post' AS material_class FROM tbl_post UNION 
SELECT title, text, id AS material_id, 'Page' AS material_class FROM tbl_page;

Представление будет возвращать идентификатор material_id и класс material_class, по которым можно будет найти оригинал модели. Если необходимо ипользовать Yii::import, то путь для него тоже можно возвращать из выборки:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT title, text, id AS material_id, 'news.models.News' AS material_import, 'News' AS material_class FROM tbl_new UNION 
SELECT title, text_purified AS text, id AS material_id, 'blog.models.Post' AS material_import, 'Post' AS material_class FROM tbl_post UNION 
SELECT title, text, id AS material_id, '' AS material_import, 'Page' AS material_class FROM tbl_page UNION
SELECT title, '' AS text, id AS material_id, 'photo.models.Photo' AS material_import, 'Photo' AS material_class FROM tbl_photo;

Здесь мы также добавили поиск по фотографиям. У них есть только заголовок и нет текста, поэтому в поле text мы возвращаем пустую строку. Аналогично можно легко добавить в поиск любую сущность.

Теперь необходимо доработать нашу модель поиска так, чтобы по свойству $model->material можно было бы получить доступ к оригинальной модели практически как через ленивую загрузку через отношение BELONGS_TO:

/**
 * @property string $title
 * @property string $text
 * @property string $material_import
 * @property string $material_class
 * @property integer $material_id
 */
class Search extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return '{{view_search}}';
    }
 
    private $_material;
 
    public function getMaterial()
    {
        if ($this->_material === null){
            if ($this->material_import){
                Yii::import($this->material_import);
            }   
            $this->_material = CActiveRecord::model($this->material_class)->findByPk($this->material_id);
        }
 
        return $this->_material;
    }
}

Контроллер мы оставим без изменений:

class SearchController extends Controller
{
    public function actionIndex($query)
    {
        $criteria = new CDbCriteria();
        $criteria->addSearchCondition('title', $query);
        $criteria->addSearchCondition('text', $query, true, 'OR');
 
        $dataProvider = new CActiveDataProvider('Search', array(
            'criteria'=>$criteria,
        ));
 
        $this->render('search', array(
            'dataProvider'=>$dataProvider,
            'query'=>$query,
        ));
    }
}

При выводе списка результатов мы теперь можем вызывать метод getUrl() соответствующей модели, к экземпляру которой мы можем обращаться через отношение getMaterial() нашей модели Search:

<h2><?php echo CHtml::link(CHtml::encode($data->title), $data->material->url); ?></h2>

Теперь мы можем легко добавить в поиск любого сайта на Yii новые сущности, заменив тело представления в базе данных и не изменив ни одной строки исходного кода контроллера или моделей. Это очень хороший результат.

Непосредственное получение экземпляров моделей

В последнем примере оригинальную запись можно получить через отношение $data->material. Но существует возможность возвращать оригинальные модели сразу в выборке, то есть оригинал будет содержаться вместо экземпляра класса Search прямо в переменной $data.

Этого можно добиться переопределив метод создания экземпляра модели CActiveRecord::instantiate:

/**
 * @property string $title
 * @property string $text
 * @property string $material_import
 * @property string $material_class
 * @property integer $material_id
 */
class Search extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return '{{view_search}}';
    }
 
    protected function instantiate($attributes)
    {
        if ($this->material_import){
            Yii::import($this->material_import);
        }
        return CActiveRecord::model($this->material_class)->findByPk($this->material_id);
    }
}

Данный метод будет вызываться при создании элементов в поисковых выборках find(), findByAttributes(), findAll() и findAllByAttributes(). Вместо экземпляра класса Search мы возвращаем экземпляр оригинального класса.

Определяя оригинальный класс элемента с помощью instanceof в _view.php мы можем подключать соответствующее типу материала представление:

<?php 
if ($data instanceof Page)) {
    $view = 'application.modules.page.views.default._view';
} elseif ($data instanceof News)) {
    $view = 'application.modules.new.views.default._view';
} elseif ($data instanceof BlogPost)) {
    $view = 'application.modules.blog.views.default._view';
} else {
    $view = '_default_view';
}
?>
 
<?php $this->renderPartial($view,  array(
    'data'=>$data, 
    'index'=>$index,
    'widget'=>$widget,
)); ?>

Теперь все найденные материалы будут выведены в одной ленте, причем каждый будет выведен в своём персональном оформлении. Для подключения новых сущностей достаточно лишь изменить SQL запрос в БД и привязать сооответствующие шаблоны.

Подобный трюк с использованием представления в базе данных и с переопределённым методом CActiveRecord::instantiate, привязанной к данному представлению модели, можно использовать не только для поиска, но и, например, для агрегации материалов различных типов в одну ленту RSS с общей сортировкой по дате.

Ну и как было обещано, пробежимся по некоторым альтэрнативам.

Другие решения по организации поиска на сайте

Здесь мы затронули простейший вариант поиска с использованием оператора LIKE. Кроме него в MySQL можно использовать и другие операторы, но это требует использования движка MyISAM и обязательное построение индексов.

Более сложные варианты предполагают использование сторонних систем. Их отличает то, что они обеспечивают полнотекстовый поиск, то есть способны не обращать внимания на порядок слов в запросе и словоформы. С примерами их использования в Yii можно ознакомиться, например, здесь, здесь и здесь. У каждого из этих решений свои особенности работы и свой порядок интеграции в проект.

Другие статьи

Нравятся ли вам компьютерные игры? Нравится ли то приятное чувство, когда растут проценты прохождения миссий и от уровня к уровню повышаются умения вашего персонажа или скорость и мощь вашего автомобиля? Как за неделю игры набираются десятки артефактов и растут запасы золота? Прямо сейчас вам предоставляется исключительная возможность сыграть в стратегию от первого лица, в которой «прокачать» нового, более чем реального героя.

При программировании на компонентном языке с поддержкой графического интерфейса часто приходится иметь дело с событиями. Любой визуальный объект в каждом из этих языков может обладать свойствами, методами и событиями. Но этими языками событийный подход не ограничивается. Попробуем по аналогии с реализацией в других языках разобраться с работой с ними в Yii.

Для вывода ленты записей на страницу в Yii имеется очень удобный готовый виджет CListView. Совместно с провайдером данных он позволяет выводить элементы с разбивкой на страницы и сортировкой. Но при разработке некоторых интернет-магазинов и всевозможных каталогов часто возникает необходимость в переключении числа элементов на странице. Попробуем добавить меню «Выводить по: 10 20 30» в нашу ленту записей.

Недавно мы познакомились с использованием HTMLPurifier. Этот компонент позволяет отфильтровать вредные элементы из HTML кода, обработать ссылки, закрыть незакрытые теги. Большой список возможностей позволяет использовать его для фильтрации полученного от пользователя контента. Вместо использования BBCode мы попробуем доработать HTMLPurifier для удобной работы тега <pre> в комментариях пользователей.

Комментарии

 

Евгений Швейн

Великолепная статья, а главное очень вовремя опубликованная, у меня как раз стоит задача в одном из проектов реализация простого поиска по разным сущностям.

Ответить

 

standalone

Искать средствами одного мускуля с помощью LIKE не целесообразно.

Ответить

 

Александр

Спасибо интересно было читать, очень жду статью о событиях. Потому как только после вашей статьи про поведения, я понял, что это такое и начал использовать их в своём коде. Надеюсь будет статья о событиях.

Ответить

 

Дмитрий Елисеев

Может вскоре напишу. Хотя их прямо почти не использую. Только косвенно.

Ответить

 

seydamet

Напишите если у вас будет время, было бы интересно прочитать. После прочтения "рецепта" на сайте самого Yii для меня вопрос остался открытым (не так как я себе это представлял), к сожалению. Можно ли в принципе использовать их не "раздувая" модели, а вешать их в контроллере.

Ответить

 

Сергей

Было бы неплохо отметить немного в статье почему именно UNION, по опыту могу сказать что он помогать будет до тех пор пока записей будет "не много". Я решал задачу иначе, написав поведение для сохранения/удаление модели, которое сохраняет/удаляет запись в отдельной таблице хранения ключевых слов, где происходит связка по названию модели и её первичному ключу

Ответить

 

Дмитрий Елисеев

Для больших объёмов уже LIKE или HAVING итак не подойдут, так что UNION сделает всё как наиболее простой способ без лишних телодвижений. А ваш подход с отдельной таблицей для поиска уже сродни ведению индексов в упомянутых Zend Lucene и Sphinx, то есть более серьёзный.

Ответить

 

Sanchezzzhak

Годно до 10к страниц вполне нормально.

Ответить

 

Ринат

Хороший сайт и хороший материал! Было бы не плохо если бы описали способ работы с zend_search_lucene или настройка работы с помощью sphinx

Ответить

 

Дмитрий Елисеев

Можно попробовать:

SELECT CONCAT(smalltext, ' ', fulltext) AS text ...
Ответить

 

Илья Левцов

Добрый день! Очень понравилась статья. Расскажите пожалуйста как можно сделать фильтрацию по gridview с csqldataprovider. Не могу решить проблему уже третий день. Заранее спасибо!

Ответить

 

Дмитрий Елисеев

В крайнем случае можно поступить аналогичным образом: сохранить этот запрос в представление базы данных и обернуть ActiveRecord моделью. Тогда можно будет использовать CActiveDataProvider как обычно.

Ответить

 

Akulenok

Не понимаю как составить запрос. Хочу искать в блоге и в комментах
в блоге title, link, short
в комментах text
делаю так:

$from = '(
            SELECT id, title, link, CONCAT(short, full) AS text FROM post UNION
            SELECT id, text FROM comments
        )';               
$where = 'WHERE t.title LIKE :query OR t.text LIKE :query';
$params = array(
   ':query'=>'%' . $query . '%',
);

$countSql = 'SELECT COUNT(*) FROM ' . $from . ' AS t ' . $where;
$dataSql = 'SELECT t.* FROM ' . $from . ' AS t ' . $where;

CDbCommand не удалось исполнить SQL-запрос: SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns. The SQL statement executed was: SELECT COUNT(*) FROM (

Ответить

 

Akulenok

Все разобрался, спасибо за статью!!!

Ответить

 

white123

Есть ли исходный код?
Я начинающий, мне трудно понять как использовать код, напр. class SearchHighlighter (где создать файл и как использовать?).

Ответить

 

Дмитрий Елисеев

Создаёте в папке components, например.

Ответить

 

Андрей Лукьянов

На yii1.1.16 метод instantiate у меня не сработал... $this - пустая.

получилось так

    public function instantiate($attributes)
    {
        return self::model($attributes['material_class'])->findByPk($attributes['material_id']);
    }
Ответить

 

Almas

Bolshooooooooi rahmet, bratan!

Ответить

 

Sergalas

Получается представление предается стирать и записывать заново при каждом добавлении или обновлении Постов страниц и новостей?

Ответить

 

Дмитрий Елисеев

Представление - это всего лишь SQL-запрос. Данные будет всегда возвращать свежие. Перезаписывать нужно будет только для изменения самого запроса.

Ответить

 

Sergalas

то есть его достаточно вставить в экшен search и каждый раз он будет обновляться ?

Ответить

 

Sergalas

К чему вопрос когда я второй раз использую поисковой запрос вот такого плана

{
        Yii::$app->db->createCommand("CREATE  SQL SECURITY INVOKER VIEW fl_search(id,name,slug,created_at,description,country_title,year,nesting) AS
                SELECT id, name_film,slug_film, created_at, description_film,country_title,year, nesting FROM `film`
                UNION SELECT id, name_serial,slug_serial, created_at, description_serial,country_title,year, nesting FROM `serial`
                UNION SELECT id, name_mfilm,slug_mfilm, created_at, description_mfilm,country_title,year, nesting FROM `mfilm`
                ORDER BY created_at DESC ")->execute();
        $arr=array(" ",'-',':') ;
        $querys=str_replace($arr,'%',$query);
        $expression = new Expression('"%'.$querys.'%"');
        $search = Search::find()->where(['or',['like', 'name', $expression],['like', 'description', $query, ]]);
        $searchDataprovider = new ActiveDataProvider([
            'query' => $search,
            'pagination' => [
                'pageSize' => 15,
            ],
        ]);
        return $this->render('search', [
            'searchDataprovider' => $searchDataprovider,
            'vardump'   => $querys
        ]);
    }

выдает такую ошибку

SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'fl_search' already exists
The SQL being executed was: CREATE SQL SECURITY INVOKER VIEW search(id,name,slug,created_at,description,country_title,year,nesting) AS
SELECT id, name_film,slug_film, created_at, description_film,country_title,year, nesting FROM `film`
UNION SELECT id, name_serial,slug_serial, created_at, description_serial,country_title,year, nesting FROM `serial`
UNION SELECT id, name_mfilm,slug_mfilm, created_at, description_mfilm,country_title,year, nesting FROM `mfilm`
ORDER BY created_at DESC 
Ответить

 

Дмитрий Елисеев

Используйте CREATE OR REPLACE VIEW.

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>