По полному как пишется

Слово неактуально в подавляющем большинстве случаев пишется слитно, поскольку является краткой формой прилагательного неактуальный в среднем роде оно неактуально. ориентироваться

Слово «неактуально» в подавляющем большинстве случаев пишется слитно, поскольку является краткой формой прилагательного «неактуальный» в среднем роде («оно неактуально»). Ориентироваться здесь на словесные конструкции типа «вдаль – в даль»; «вдали – в дали»; «навстречу – на встречу»; «пораньше – по ранее»; «по-своему – по своему»; «тоже – то же», и т.п., неуместно, так как исходное в данном случае слово «актуальный» не имя существительное, местоимение, наречие или частица, но прилагательное, причём не качественное, называющее свойства предметов, а относительное, описывающее отношения между ними. Отношение предполагает действие, поэтому в речи прилагательное «неактуально» зачастую используется как наречие и предикатив (см. далее, о значениях слова).

Когда «не актуально» разделяется

Писать «не актуально» раздельно нужно по общим для приставочных наречий правилам, но с учётом того, что краткие прилагательные всё же изменяются по родам и числам («он неактуален»; «она неактуальна», «оно неактуально», «они неактуальны»), а противоположное полному по значению имеет сравнительную степень («актуальнее»):

  • При кратном отрицании, то есть с усилением противительного значения повтором (повторами): «Ваше извинение запоздало, оно не искренне, не актуально»; «Эти данные не актуальны, не полны, не достоверны».
  • В сильном согласительном (положительном) смысле, в утвердительных предложениях с отрицанием отрицания антитезой, противопоставлением: «Ваше прибытие не актуально, но более чем своевременно», то есть якобы «сверхактуально». Для проверки применяем так называемую интерпозицию – вставляем в разорванное «не актуально» союз «только» или «просто»: «… не просто актуально, носвоевременно». Смысл предложения не изменился, значит, раздельное написание использовано верно.
  • В сравнительно-предположительных (вопросительно-сравнительных) предложениях с частицами «бы», «ли» и, возможно, согласующим глаголом «было», в качестве рассоединённого «не актуально» используется обратное с частицей «не»: «Не актуально <ли> <было> бы нам проехаться в горы, на лыжах покататься?»; «Алевтина Геннадьевна, проверьте, пожалуйста, не актуальнее ли прежних окажутся условия нового поставщика?» Частичные синонимы «желательно», «своевременно» (к исходной форме), «желательнее», «лучше» (к сравнительной степени).

Золотое правило

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

Поэтому если есть сомнения – просто пишите «неактуально» одним словом.

Пояснение к правописанию

Поскольку средний род тоже весьма относителен (лишён мужских и женских признаков), то краткие формы среднего рода относительных прилагательных обладают также синтаксическими признаками наречий. В частности, они легко соотносятся с глаголами (действиями), и очень трудно, или вовсе не соотносятся с предметами – именами существительными и личными местоимениями. «Делать неправильно», тут всё сразу понятно. Но что бы означало «дом неправильно» или «я неправильно»? Тут нужны полные формы: «дом неправильный» или «я неправильный». Поэтому со словами «<не>адекватно», «<не>актуально», «<не>годно», «<не>действенно», «<не>действительно», «<не>достаточно», «<не>интересно», «<не>корректно», «<не>верно», «<не>нужно», «<не>обдуманно», «<не>осмысленно», «<не>правильно», «<не>разумно, «<не>пригодно», «<не>уместно», «<не>умно» и подобными им в живой речи следует обращаться как с наречиями, помня всё же, что, в отличие от совершенно неизменяемых наречий такие слова могут менять число, способны изменить свой род и восходить в сравнительную и даже превосходную степени.

Значение

Прилагательное «неактуально» в русском языке наделено двумя значениями:

  1. Употребляется в качестве определительного наречия сравнения и уподобления, означающего непригодность, ненужность чего-то или кого-то в настоящее время, на текущий момент: «Массовое бумажное книгоиздание ныне неактуально ввиду развития электронных средств информации и повсеместно уступает малотиражной печати по требованию (print on demand)»; «Rolling Stones продолжают концертировать и записываться, но в сущности они давно уж неактуальны». Частичные синонимы, только в среднем роде «неинтересно», «ненужно», «недейственно», «недействительно». «невостребовано <невостребован, невостребована, невостребованы могут стать полными синонимами, если будут признаны нормативными в русском языке.
  2. Используется как предикатив, то есть слово или выражение, само по себе, независимо от построения предложения с ним, придающее высказыванию определённую эмоциональную окраску и/или выражающее авторскую оценку события, субъекта, явления. На письме в данном значении отделяется запятыми: «Жениться не подумываешь? – Неактуально, до тридцати ещё топать и топать, и постоянной работы нет». Синоним «ненужно».

Грамматика слова

«неактуально» – краткая форма прилагательного «неактуальный» в единственном числе женского рода. Несклоняемое слово. Состоит из приставки «не-», корня «-актуаль-», суффиксов «-н-» и «-о». Постановка ударения и разделение переносами не-ак-ту-а́ль-но.

***

© ПишемПравильно.ру

Автор: Садов Артур Александрович, лингвист-типолог

Перечень академических источников, использовавшихся при подготовке материалов.

Правописание этих слов надо знать:

Проверить еще слово:

Уважаемые писатели! И поэты!

В связи со сложившейся проблемой я попробую описать вам нужный мне процесс.

Об чём речь?

Я ищу человека, способного написать либретто НА ГОТОВУЮ МУЗЫКУ.

1.     Почему музыка первична

Поэты делятся на 3 группы:

  1. Всё жизнь пишут одним размером, например 4-стопным ямбом «Мой дядя самых честных правил». Не зависимо от объема произведения (от четверостишия до книг);
  2. Могут писать и в каких-то других размерах (чаще – хореем. 3-х стопный размер – едва виден)
  3. Истинные поэты – у них размер идет ЗА смыслом (грандиозный пример – дедушка Крылов)

Какое МУЗЫКАЛЬНОЕ РАЗНООБРАЗИЕ может возникнуть у поэтов 2, а особенно 1, группы? Только если ИСКУССТВЕННО мучить голос певцов и слух слушателей – лишь бы разнообразно. Пример:

Заметался пожар голубой,
Позабылись родимые дали.
В первый раз я запел про любовь,
В первый раз отрекаюсь скандалить.

Из это можно сделать душещипательный романс. А как этот текст будет звучать в МАРШЕ? А как из этого текста сделать ДИАЛОГ?

Вы спросите: «А кто мешает взять ДРУГОЙ текст, чтобы он подходил к маршу или к диалогу?» А я вам отвечу: «А где, если он ВЕСЬ ТАКОЙ?» (см. поэтов a) и b))

Поэтому я и пришел к мысли – для того, чтобы МУЗЫКА была РАЗНООБРАЗНОЙ, она должна быть ПЕРВИЧНА! Чтобы течению мелодии не мешал текущий стихотворный размер.

2.     Писатели

Как я понял по работам с некоторыми авторами – писатели следуют правилу :  «Сначала надо ввязаться в серьезный бой, а там уже видно будет». Т.е. глянув в наметки МОЕГО либретто сразу начинают гнать текст, да ещё и стихотворный. Когда спрашиваешь: «А как Экспозиция перейдет в завязку, а как та  перейдет в конфликт, который развяжется на финал?» Отвечают: «Как дойдем – так там и поймем!»

Слушайте. Так можно писать КНИГУ. Она не ограничена ни объёмом текста, ни временем действия (внутри текста), ни временем чтения (этого текста). Поэтому вполне возможно появление эпизодических лиц, побочных линий сюжета и прочего, что ВБРЕДЁТ автору в голову  В МОМЕНТ НАПИСАНИЯ.

Так ещё и оплата идёт от КОЛИЧЕСТВА страниц! Поэтому – чем больше, тем лучше. Страниц этак 350-400. Но это не годится для либретто.

3.     Сценаристы

По поводу сценария хорошо написано https://new-storyteller.livejournal.com/3405.html .   Там сценарий на 90-120 страниц разбит на 3 этапа с подэтапами. Чем принципиально отличается книга от сценария – меньше описаний, больше диалогов. Однако и здесь можно разогнаться на целый сериал…

4.     Драматурги

Всё действие – диалоги и монологи.  Требования единства: действия, места и времени – не всегда можно соблюсти. Театр от Шекспировских времён,когда пьеса шла целый день с перерывами на перекусы (это хорошо видно по полному тексту «Ромео и Джульетты»), сейчас не пройдет – мы живем намного быстрее. И неопределенный Чехов, с его «тара-бум-бия, сижу на тумбе я» требует от режиссера САМОСТОЯТЕЛЬНОГО придумывание действия, соответствующего отсутствующему тексту. Размер «Вишневого сада» – 80-90 страниц. Действие на сцене  – 2-2,5 часа – как гонит режиссер.

5.     Либреттисты

Ну и, наконец, писатели текстов для МУЗЫКАЛЬНЫХ спектаклей. Ещё более сжато. Либретто Сильвы – 35 страниц, включая ремарки. Никаких прологов и философствований – сразу с ДЕЙСТВИЯ. Поменьше разговоров между номерами – больше содержания в пении. 

Надо сказать, что музыкальные номера делятся на 2 вида:

  1. Вставные. Это номера – настроения. В «Пиковой даме» вставной номер один – куплеты Томского, вписанные Чайковским по просьбе первого исполнителя.  Известнейшая оперетта «Свадьба в Малиновке» – ВСЯ состоит из вставных номеров, т.е.  взята пьеса (комедия) и в некоторые места вписаны музыкальные номера: арии, дуэты, хоры, танцы (ну, как положено). Если такой номер УБРАТЬ – то СМЫСЛ самогО произведения (пьесы) не потеряется.
  2. Сюжетные. Номера, в которых, собственно, передается содержание пьесы. Чем меньше номеров вставных и чем больше сюжетных – тем компактнее КОНЕЧНОЕ произведение.

6.     Как писать либретто НА ГОТОВУЮ музыку?

(Теоретическая основа  –

6 этапов: http://www.cinemotionlab.com/stati/shest_etapov_uspeshnogo_scenariya/

)

  • Слушаем номера.
  • Определяемся по стилю. У меня, например, вся музыка в РУССКОМ духе (не а-ля рюс). Из неё нельзя получить «Летучую мышь» или «Цыганского барона» – музыка просто не ляжет на текст.
  • Придумываем сюжет.
  • Пишем синопсис.
  • К КАЖДОМУ ДЕЙСТВИЮ СИНОПСИСА ПОДБИРАЕМ МУЗЫКАЛЬНЫЙ НОМЕР (совместно с композитором)
  • Пишем связки между номерами – ПРОЗОЙ (прозаики) или СТИХАМИ (поэты).
  • Пишем «рыбу» на музыкальные номера (прозой).
  • Переводим «рыбу» в поэтический (согласно ритму пьесы) текст (поэты).

ПРОФИТ! Оперетта готова.

Желающие принять участие в написании оперетты (мюзикла) «Там, на неведомых дорожках» могут послушать музыку  на Диске https://yadi.sk/d/1fxVUZkhBYZj_Q  и посмотреть примеры синопсиса в файлах Оперетта (n). У меня получается 2 действия в 4 актах. Музыку слушать на качественной аппаратуре (не телефон).

Статья находится на проверке у методистов Skysmart.
Если вы заметили ошибку, сообщите об этом в онлайн-чат (в правом нижнем углу экрана).

Когда не с прилагательными пишется слитно

Если без не слово не употребляется

Не с прилагательными пишется слитно, если такое слово нельзя употреблять без не.

Примеры: ненасытный, ненастье, негодовать, недоумевать, небылицы, ненавидеть.

Если есть сопоставление

Когда слова не противоречат друг другу, а по-разному описывают предмет или ситуацию, прилагательное и частица не пишутся слитно. Часто такой случай можно узнать по союзам но, и.

Примеры:

  • Холл небольшой, но уютный.

  • Холл небольшой и уютный.

  • Холл небольшой, уютный.

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

Если пояснительные слова — это наречия меры и степени

Когда прилагательное поясняют наречия меры и степени, не пишется слитно. Такими словами могут быть:

  • почти,

  • отчасти,

  • весьма,

  • довольно,

  • гораздо,

  • вполне,

  • полностью,

  • очень,

  • абсолютно,

  • слишком,

  • крайне,

  • совершенно,

  • в высшей степени и т. д.

Примеры:

  • Эта ситуация в высшей степени несправедлива.

  • Человек напротив был крайне нескладным и угловатым.

  • Тайник оказался слишком незаметным.

Таблица правописания НЕ с прилагательными слитно | skysmart.ru

Когда не с прилагательными пишется раздельно

Если есть противопоставление

Когда в предложении ясно, что одну его часть автор утверждает, а другую — отрицает, не с прилагательными пишется раздельно. Такие случаи можно узнать по союзу а. При этом оба слова должны противоречить друг другу, то есть быть антонимами. Это же правило касается и кратких прилагательных.

Примеры:

  • Это дерево не высокое, а низкое.

  • Дом на холме не близок от деревни, а далек.

  • Он встретил нас не гостеприимно, а грубо.

Иногда противопоставление автор не пишет, а лишь подразумевает. В этом случае не с прилагательным тоже пишется раздельно.

Пример:

  • Отношения между ними были не дружескими.

Если есть отрицательные местоимения и наречия

Когда в предложении есть отрицательные местоимения и наречия, не с прилагательными пишется раздельно.

Такими словами могут быть:

  • вовсе,

  • далеко,

  • отнюдь,

  • ничуть,

  • нисколько и т. д.

Примеры:

  • Ремонт оказался вовсе не дорогим.

  • Хоть отец и не приехал, мне было ничуть не грустно.

Если у краткого прилагательного нет полной формы

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

Примеры:

  • Я не готов к такому повороту событий.

  • Он явно был не рад видеть мою семью.

Если не используем с относительными прилагательными

Иногда предложение отрицает свойство предмета с помощью относительного прилагательного с не. В этом случае не следует писать раздельно.

Примеры: не каменный забор, не серебряные серьги, не шелковый шарф, не деревянный стол.

Если прилагательное передает цвет или вкус

Когда предложение отрицает цвет или вкус предмета с помощью качественного прилагательного, не пишется раздельно.

Примеры: не кислая вишня, не зеленый учебник, не синее пальто.

Если не используем с притяжательными прилагательными

Когда предложение отрицает, что предмет кому-либо принадлежит, не пишется раздельно. Такие прилагательные отвечают на вопросы: чей? чья? чьи? чье?

Примеры: не мамина сумка, не отцов дом.

Если предложение отрицает два одинаковых признака сразу

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

Пример:

  • Она не молода и не стара.

Таблица правописания НЕ с прилагательными раздельно | skysmart.ru

Особые случаи, когда не с прилагательными пишется раздельно или слитно

Не с пояснительным словом совсем

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

Если слово совсем мы используем в значении очень, совершенно, то не с таким прилагательным пишется слитно.

Пример:

  • Совсем некрасивый человек (очень некрасивый).

Если же под совсем мы имеем в виду отнюдь или никоим образом, то прилагательное с не пишется раздельно.

Пример:

  • Совсем не красивый человек (отнюдь не красивый).

Если прилагательное в сравнительной степени

Обычно прилагательные в сравнительной степени с не пишут раздельно.

Пример:

  • Все детство я был не выше своей сестры.

Но бывают и исключения. Когда прилагательное в сравнительной степени образовалось от другого прилагательного с приставкой не, такое слово пишется слитно.

Пример:

  • Лекция была еще непонятнее, чем обычно.

Здесь прилагательное непонятнее образовано от слова непонятная. Противопоставления нет, и слово можно заменить синонимом без не — запутанная, сложная. Значит, не — это приставка.

Проверьте себя

Как пишется не с прилагательными в этих случаях? Для самопроверки можно использовать таблицу правописания частицы не с прилагательными ниже.

  1. И он ушел, высокий, (не) складный, никем (не) понятый.

  2. Перед нами открылась (не) крохотная полянка, а целое поле.

  3. Это была (не) обычная новогодняя ночь: за окном светило совсем (не) зимнее солнце.

  4. Он оказался (не) готов к такой резкой смене событий.

  5. Ее (не) ряшливый вид кричал о тяжелом периоде в жизни.

Таблица правописания НЕ с прилагательными | skysmart.ru

Подготовка к ОГЭ по русскому онлайн — отличный способ разобрать реальные экзаменационные задания в комфортной обстановке с внимательным преподавателем.

Русский [ править ]

Наречие, определительное, сравнения и уподобления; неизменяемое.

Приставка: по-; корень: -друж-; суффиксы: -еск-и [Тихонов, 1996] .

Произношение [ править ]

    : [ pɐ‿ˈdruʐɨskʲɪ ]

Семантические свойства [ править ]

Значение [ править ]

  1. так, как характерно для друзей ◆  Отсутствует пример употребления (см. рекомендации ).

Синонимы [ править ]

Антонимы [ править ]

Гиперонимы [ править ]

Гипонимы [ править ]

Родственные слова [ править ]

  • существительные: друг
  • прилагательные: дружеский
  • наречия: дружески
  • предлоги: по
  • имена собственные: Дружок
  • фамилии: Дружинин, Дружников
  • пр.  существительные: друг, друган, друганок, друганочек, другиня, дружба, дружбан, дружбанок, дружбаночек, дружелюбие, дружелюбье, дружественность, дружина, дружинник, дружинница, дружишко, дружище, дружка, дружок, дружочек, недруг, недружелюбие, недружелюбье, недружественность, подруга, подружаечка, подружайка, подружаня, подруженька, подружка, содружество
  • прилагательные: дружелюбный, дружеский, дружественный, дружинный, дружный, малодружественный, недружелюбный, недружеский, недружественный, недружный
  • глаголы: додружить, додружиться, дружить, дружиться, задружить, задружиться, надружить, надружиться, подружить, подружиться, продружить, сдружить, сдружиться, удружить
  • наречия: друг в друга, друг друга, друг другу, друг за другом, друг к другу, друг на друга, друг от друга, друг под друга, друг против друга, друг с другом, друг у друга, дружелюбно, дружески, дружественно, дружно, недружелюбно, недружески, недружественно, недружно, по дружбе, по-дружески, сам-друг

Этимология [ править ]

Из по- + дружески (от дружеский), далее от друг, далее от праслав.  *drugъ , от кот. в числе прочего произошли: ст.-слав.  дрѹгъ (греч. φίλος), русск., белор., укр. друг , болг.  друг , сербохорв. дру̑г, словенск.  drȗg , чешск., словацк. druh , др.-польск. drug; восходит к праиндоевр. *dhreugh- «охранять». Родственно лит. draũgas «спутник, товарищ», латышск. draugs «друг», др.-прусск. draugiwaldūnen (вин. п.) «сонаследник», др.-исл. draugr (поэт.) «муж», готск. (driugan) «воевать» (греч. στρατεύειν), англос. dréogan «совершать, добиваться», готск. (gadraúhts) (στρατιώτης), др.-в.-нем. trucht «отряд воинов, свита», др.-в.-нем. truhtîn «военачальник, князь», лит. sudrugti «присоединиться» Использованы данные словаря М. Фасмера. См. Список литературы.

Фразеологизмы и устойчивые сочетания [ править ]

Перевод [ править ]

Это незаконченная статья. Вы можете помочь проекту, исправив и дополнив её .
В частности, следует уточнить сведения о:

«По-дружески» или «по дружески»?

«По-дружески» или «по дружески» − многие не задумываются над написанием слова, считая выбор между раздельным и дефисным написанием непринципиальным. Между тем, правильное написание слова определяет часть речи и характеризует уровень грамотности человека.

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

Как правильно пишется

Сочетание предлога «по» и прилагательного «дружеский» образует наречие, которое отвечает на вопросы

  • «как?»;
  • «каким образом?».

Согласно существующему правилу, образованные таким способом наречия приобретают приставку: «по», которая пишется со словом не слитно, а через дефис: «по-дружески».

Подтверждает дефисное написание наречия и другое правило: через дефис пишутся наречия с суффиксами:

  • «-(с)ки»;
  • «-ьи».

Примеры предложений

Рассмотрим несколько примеров:

  1. Несмотря на вчерашнюю ссору, он выглядел спокойным и разговаривал по-дружески. То ли он на самом деле так добр, то ли задумал что-то.
  2. Учитель предложил поговорить по душам, по-дружески. Но обратной реакции не было. Илья не хотел разговаривать совсем.
  3. Алексей по-дружески предложил свою помощь. И тем самым очень нас выручил.
  4. Новый сотрудник представился и по-дружески протянул руку Ивану Захаровичу. Тот вяло пожал ему руку.
  5. Ее взгляд больше не был колючим, наоборот, приветливым и по-дружески теплым.

Морфемный разбор слова «по-дружески»

Наречие имеет следующий состав:

  • приставка − «по-»;
  • корень − «-друж-»;
  • суффикс − «-еск-»;
  • суффикс − «-и».

Неизменяемое наречие имеет два суффикса. У слова нет окончаний. Основа слова: «по-дружески».

Приставка «по-» пишется не слитно, а через дефис.

Неправильное написание слова «по-дружески»

Некорректно писать слово слитно − «подружески» и раздельно, без дефиса − «по дружески». Кроме того, следует исключить возможные орфографические ошибки:

  • «падружески»;
  • «па дружески»;
  • «по дружиски»;
  • «подружиски».

Ошибочным является применение полной формы прилагательного: «по-дружескому». Такой вариант невозможен, так как у наречий, образованных от прилагательных, нет окончаний.

Заключение

Для того чтобы всегда писать правильно, необходимо запомнить: все наречия (вопросы: «как?», «каким образом?»), образованные от прилагательных с помощью приставки «по-», всегда пишутся через дефис.

Как пишется слово «по-дружески» и почему?

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

Таким образом, правильно это слово пишется так: по — дружески.

автор вопроса выбрал этот ответ лучшим

Искат­ель прикл­ючени­й [100K]

Добрый день, чтобы понять, какой вариант правильный:

Нужно обратится к правилам русского языка, в которых можно найти следующие:

Следовательно есть только один правильный ответ: по-дружески.

Друг — дружеский — по-дружески. Из приведённой словообразовательной цепочки наглядно видно, как образовалось наречие по-дружески от однокоренного прилагательного с помощью приставки по- и суффикса -и.

Он по-дружески похлопал меня по плечу.

Такие наречия с приставкой по- и оканчивающиеся на ому/-ему, -ски, -цки, -ьи согласно орфографическому правилу русского языка пишутся с дефисом.

по-приятельски, по-человечески, по-братски, по-матерински.

З В Ё Н К А [702K]

Наречие «по-дружески» пишется только через дефис. К этому нас склоняют причины, очень подробно описанные здесь.

Мы имеем дело с наречием, которое образовано при помощи так называемого префиксально-суффиксального способа словообразования. Для русских наречий он достаточно характерен, и схема «перед прилагательным ставим префикс по-, а в конце прилагательного добавляем вместо окончания -и» очень известна. Именно она применена и сейчас — «дружеский — по-дружески». Отсоединив «-ий», к основе с разных сторон добавляется «по-» и «-и».

Получившиеся таким способом наречия пишутся с дефисом после «по».

«По-дружески» является наречием, которое образовалось от прилагательного «дружеский» с помощью приставки «по» и суффикса «ск».

Согласно правилу такие наречия пишутся через дефис.

Пример предложения со словом «по-дружески»:

Мы разошлись по-дружески.

По-дружески так не поступают.

Еще наречия, образованные аналогичным способом:

по-человечески, по-людски, по-отечески.

В вопросе абсолютно верно приведено написание слова «по-дружески», то есть с использованием дефиса.

Подобное изображение слова на письме вызвано правилом русского языка, которое предполагает применение дефиса ко всем наречиям, которые

как в нашем примере, а также

В данных словах обязательно должны присутствовать

Тори Торич­ка [57.4K]

Наречие «по-дружески» следует писать через дефис.

Руководствуемся следующим правилом: если наречие начинается с ПО и имеет суффиксы -И, -ОМУ, -ЕМУ, то следует писать через дефис.

Пример употребления: поступить по-дружески.

По-дружески — это наречие, которое пишется через дефис. Существует правило написания таких наречий, если у них имеется приставка по, то она пишется через дефис, например, по-нашему, по-русски, по-немецки и т.д.

Наречие по-дружески обязательно надо писать с дефисом. Вообще все наречия, начинающиеся с «по» и оканчивающиеся на «ски» пишутся через дефис.

Подробнее читаем здесь.

А вот наречия, которые пишутся слитно, к примеру.

Данное слово нужно писать с использованием дефиса вот так: по-дружески. Дело в том, что слово «по-дружески» представляет собой , а как известно из правил русского языка, наречия с такой приставкой как «по» нужно писать через дефис.

Как пишется слово «по-дружески» и почему?

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

Таким образом, правильно это слово пишется так: по — дружески.

автор вопроса выбрал этот ответ лучшим

Искат­ель прикл­ючени­й [100K]

Добрый день, чтобы понять, какой вариант правильный:

Нужно обратится к правилам русского языка, в которых можно найти следующие:

Следовательно есть только один правильный ответ: по-дружески.

Друг — дружеский — по-дружески. Из приведённой словообразовательной цепочки наглядно видно, как образовалось наречие по-дружески от однокоренного прилагательного с помощью приставки по- и суффикса -и.

Он по-дружески похлопал меня по плечу.

Такие наречия с приставкой по- и оканчивающиеся на ому/-ему, -ски, -цки, -ьи согласно орфографическому правилу русского языка пишутся с дефисом.

по-приятельски, по-человечески, по-братски, по-матерински.

З В Ё Н К А [702K]

Наречие «по-дружески» пишется только через дефис. К этому нас склоняют причины, очень подробно описанные здесь.

Мы имеем дело с наречием, которое образовано при помощи так называемого префиксально-суффиксального способа словообразования. Для русских наречий он достаточно характерен, и схема «перед прилагательным ставим префикс по-, а в конце прилагательного добавляем вместо окончания -и» очень известна. Именно она применена и сейчас — «дружеский — по-дружески». Отсоединив «-ий», к основе с разных сторон добавляется «по-» и «-и».

Получившиеся таким способом наречия пишутся с дефисом после «по».

«По-дружески» является наречием, которое образовалось от прилагательного «дружеский» с помощью приставки «по» и суффикса «ск».

Согласно правилу такие наречия пишутся через дефис.

Пример предложения со словом «по-дружески»:

Мы разошлись по-дружески.

По-дружески так не поступают.

Еще наречия, образованные аналогичным способом:

по-человечески, по-людски, по-отечески.

В вопросе абсолютно верно приведено написание слова «по-дружески», то есть с использованием дефиса.

Подобное изображение слова на письме вызвано правилом русского языка, которое предполагает применение дефиса ко всем наречиям, которые

как в нашем примере, а также

В данных словах обязательно должны присутствовать

Тори Торич­ка [57.4K]

Наречие «по-дружески» следует писать через дефис.

Руководствуемся следующим правилом: если наречие начинается с ПО и имеет суффиксы -И, -ОМУ, -ЕМУ, то следует писать через дефис.

Пример употребления: поступить по-дружески.

По-дружески — это наречие, которое пишется через дефис. Существует правило написания таких наречий, если у них имеется приставка по, то она пишется через дефис, например, по-нашему, по-русски, по-немецки и т.д.

Наречие по-дружески обязательно надо писать с дефисом. Вообще все наречия, начинающиеся с «по» и оканчивающиеся на «ски» пишутся через дефис.

Подробнее читаем здесь.

А вот наречия, которые пишутся слитно, к примеру.

Данное слово нужно писать с использованием дефиса вот так: по-дружески. Дело в том, что слово «по-дружески» представляет собой , а как известно из правил русского языка, наречия с такой приставкой как «по» нужно писать через дефис.

Всем привет! Меня зовут Евгений Радионов, я бэкенд-разработчик, последние два года пишу на языке Go, до этого работал с Ruby. За это время столкнулся со множеством интересных и сложных задач, в одной из которых и познакомился с ElasticSearch. В этой статье мы разберем, как настроить продвинутый полнотекстовый поиск с использованием ElasticSearch и — в качестве бонуса — интегрировать его в приложение на Go.

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

Сначала хочу ознакомить вас со структурой статьи. Она разделена на три раздела, первый из которых расскажет о базовых принципах работы полнотекстового поиска, его возникновении и покажет, как можно построить неплохой полнотекстовый поиск на базе PostgreSQL.

Второй, основной и самый большой раздел посвящен ElasticSearch, принципам его работы, разным подходам к описанию настроек индекса и вариантам написания запросов.

Третий, бонусный раздел расскажет о том, как можно интегрировать ElasticSearch в приложение на Go так, чтобы это было удобно поддерживать и расширять.

Почему ElasticSearch

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

Однако такое решение вряд ли будет работать быстро, да и поддерживать его не очень удобно. Другой вариант — ElasticSearch: инструмент, зарекомендовавший себя как поисковый движок с большим количеством возможностей и настроек для полнотекстового поиска, фильтрации, с поддержкой работы с пространственной составляющей данных и множеством других полезных (и не очень) функций. Можно рассмотреть и прочие альтернативы поисковых движков, но вряд ли начать работать с ними будет так же просто, как с ElasticSearch.

Не стоит забывать о поддержке того или иного инструмента в языке программирования (посредством библиотек), возможности и простоте его развертывания и масштабирования внутри инфраструктуры. Не думаю, что кто-то хотел бы потратить много времени на интеграцию чего-либо, а затем узнать, что поддержка прекратится через три месяца. Забегая наперед, скажу, что выбор в пользу ElasticSearch оказался оправданным и позволил не только построить качественный поиск с релевантной выдачей, но и сделать его быстрым.

Немножко поисковой истории

Перед тем как перейти непосредственно к полнотекстовому поиску и ElasticSearch, давайте ненадолго вернемся в прошлое и посмотрим, с чего все начиналось.

Самый простой способ что-нибудь найти — это перебрать все доступные записи и сравнить значения интересующих нас полей с поисковым запросом в надежде увидеть полное совпадения поля и запроса. Например, если необходимо найти всех клиентов, имя которых John, а фамилия Smith, то на языке SQL это может выглядеть так:

SELECT * FROM customers
WHERE first_name = 'John' AND last_name = 'Smith'

Однако мы не всегда точно знаем, как зовут человека или как правильно пишется его имя/фамилия. В таком случае применим поиск с использованием символа подстановки (wildcard search). Так, например, чтобы найти все книги про Гарри Поттера, выполним такой запрос:

SELECT * FROM books
WHERE name LIKE 'Harry Potter%'

Подобные запросы хорошо справляются с задачей найти группу записей, объединенных общим критерием: у нас это книги, названия которых начинаются на (имеют префикс) Harry Potter, но при таком подходе проблема с орфографически неправильным написанием (имени, фамилии, названия произведения) остается.

Если продолжать рассматривать варианты в PostgreSQL, то на помощь может прийти поиск с использованием триграмм (trigram).

Поиск с использованием триграмм

Триграмма — это частный случай n-граммы (n-gram), где n = 3, а, в свою очередь, n-грамма — это непрерывная последовательность из n-элементов из заданного образца текста или речи. Изменяя значение n, получаем юниграммы (unigram, n = 1), биграммы (bigram, n = 2), триграммы (trigram, n = 3) и так далее.

В биологии и химии существует похожее понятие — k-мер (k-mer), однако вместо числовых приставок, взятых из английского языка, там используются приставки из греческого. Так получаются знакомые некоторым названия: мономер (monomer, k = 1), димер (dimer, k = 2), и знакомый миллионам полимер (от греч. πολύ «много» + μέρος «часть»), состоящий из множества частей.

Чтобы было проще разобраться с n-граммами, рассмотрим небольшой пример разбиения фразы the quick red fox jumps over the lazy brown dog на триграммы.

Разбить предложение (строку) на триграммы можно на уровне слов (word-level) или на уровне символов (character-level). В таблице ниже — результат таких операций, где «_» означает пробел.

Word-level Character-level («_» is space)
the quick red
quick red fox
red fox jumps
fox jumps over
jumps over the
over the lazy
the lazy brown
lazy brown dog
the
he_
e_q
_qu
qui
uic
ick
ck_
k_r
_re
red

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

Чтобы сделать это в PostgreSQL, нужно подключить расширение pg_trgm, создать таблицу и добавить индекс с триграммами:

CREATE EXTENSION IF NOT EXISTS "pg_trgm";
CREATE TABLE test_trgm (t text);
CREATE INDEX trgm_idx ON test_trgm USING GIST (t gist_trgm_ops);

Тогда для поиска используем следующий запрос:

SELECT t, similarity(t, 'word') AS sml
  FROM test_trgm
  WHERE t % 'word'
  ORDER BY sml DESC, t;

В результате получим значения текстового столбца t, уровень схожести (от 0 до 1) текста из этого столбца и слова word, при этом будут возвращены только те записи, схожесть которых выше порогового значения схожести t % ’word’ (устанавливается в настройках расширения pg_trgm.similarity_threshold).

Полнотекстовый поиск в PostgreSQL

Чтобы воспользоваться всеми возможностями PostgreSQL по полнотекстовому поиску, нужно применить такие типы, как tsvector и tsquery, которые конвертируют хранимые и входящие данные в формат, наиболее подходящий для полнотекстового поиска.

SELECT to_tsvector('The quick brown fox jumped over the lazy dog.');

                     to_tsvector
-------------------------------------------------------
 'brown':3 'dog':9 'fox':4 'jump':5 'lazi':8 'quick':2

Из примера выше мы видим, что в представлении tsvector наша входная строка немного преобразилась. Первое, что бросается в глаза, — это измененный порядок слов и наличие порядкового номера напротив каждого из них. Если рассмотреть подробнее результаты преобразования, можно заметить, что слова The и over куда-то потерялись, а jumped и lazy заменены на jump и lazi соответственно.

В первом случае мы отбросили ненужные (незначимые) для поиска слова, а во втором — преобразовали их в формы, более подходящие для полнотекстового поиска (убрали шум). Запомните это поведение формата tsvector, оно понадобится, когда будем рассматривать составляющие анализатора (analyzer) в ElasticSearch.

Для того чтобы выполнить запрос на полнотекстовый поиск в PostgreSQL, нужно воспользоваться одним из операторов, например @@, который возвращает true, если tsvector (документ) совпадает с tsquery (запросом). Следующие запросы вернут true:

SELECT to_tsvector('The quick brown fox jumped over the lazy dog') @@ to_tsquery('foxes');

SELECT to_tsvector('The quick brown fox jumped over the lazy dog') @@ to_tsquery('jumping');

SELECT to_tsvector('fat cats ate fat rats') @@ to_tsquery(fat & rat);

Так как полнотекстовый поиск в PostgreSQL — это тема для отдельной статьи, подробнее прочитать про него можно в документации, а мы будем переходить непосредственно к ElasticSearch.

Введение в ElasticSearch

ElasticSearch — это распределенный поисковый и аналитический движок с открытым исходным кодом, написанный на Java, который поддерживает большое количество типов данных, включая текстовые, числовые, геопространственные, структурированные и неструктурированные.

Это, по сути, документоориентированная база данных, оптимизированная под всевозможные операции поиска. Понимая это и имея базовое представление о концепциях документоориентированного подхода к хранению данных, становится немного проще разобраться в инструменте.

За годы успешного существования на рынке (первая версия вышла в 2010 году) ElasticSearch стал центральным элементом экосистемы Elastic: ELK Stack. ELK — это акроним трех продуктов компании Elastic: ElasticSearch (поисковый и аналитический движок), Logstash (конвейер обработки данных) и Kibana (интерфейс для визуализации данных). Сегодня можно выделить два самых популярных сценария использования ElasticSearch:

  • движок для полнотекстового поиска;
  • хранилище логов и метрик в ELK Stack.

В статье мы настроим полнотекстовый поиск в ElasticSearch и немного затронем Kibana для повышения удобства разработки, просмотра и отладки поисковых запросов и их результатов.

Начало работы с ElasticSearch

Перед началом работы еще немного теории. Идея создания ElasticSearch состоит в том, чтобы предоставить возможности библиотеки полнотекстового поиска Apache Lucene для Java пользователям других языков через простой и понятный всем интерфейс: JSON поверх HTTP. Так что все запросы представляют собой JSON, а передаются через HTTP и сегодня. Для исполнения запросов из примеров можно взять любой HTTP-клиент, будь то Postman или cURL. Но я рекомендую воспользоваться Dev Tools в Kibana, хотя бы потому, что там есть автокомплит запросов и подсветка синтаксиса.

Итак, чтобы начать использовать ElasticSearch, вам нужно развернуть его кластер. Проще всего это сделать с помощью docker-compose, заодно запустив Kibana:

version: "2"
services:
  elasticsearch:
    image: 'docker.elastic.co/elasticsearch/elasticsearch:7.4.0'
    container_name: 'elasticsearch'
    ports:
      - 9200:9200
    environment:
      discovery.type: single-node
  kibana:
    image: 'docker.elastic.co/kibana/kibana:7.4.0'
    container_name: 'kibana'
    ports:
      - 5601:5601
    environment:
      SERVER_NAME: kibana.my-organization.com
      ELASTICSEARCH_URL: http://elasticsearch:9200

ElasticSearch — гибкий инструмент, который работает по принципу «включено все, что тебе не нужно, пока ты это не выключишь». Например, если индекса не существует, то он будет автоматически создан при вставке первой записи.

Индекс — это коллекция документов, которые обычно имеют одинаковую структуру данных, хотя это не обязательно. Объединяя документы в коллекции (индексы), мы можем сгруппировать похожие данные в одном месте для последующего поиска по ним.

Хоть мы уже можем добавлять данные в ElasticSearch:

PUT /books_index/_doc/1
{
  "name": "Harry Potter and the Philosopher's Stone",
  "publishing_year": 1997,
  "author": {
    "name": "J. K. Rowling"
  }
}

Я рекомендую начать с определения формата хранимых данных (mapping) в индексе, если это возможно.

Давайте рассмотрим небольшой пример простого поиска на базе индекса для книг:

PUT /books_index // Create index first

PUT /books_index/_mappings
{
  "properties": {
    "name": {
      "type": "text"
    },
    "publishing_year": {
      "type": "integer",
      "fields": {
        "keyword": {
          "type": "keyword"
        }
      }
    },
    "author": {
      "properties": {
        "name": {
          "type": "text"
        }
      }
    }
  }
}

Объявляя mapping, мы описываем не только структуру хранимых данных в нашем индексе, но и правила поиска для конкретных полей, которые рассмотрим немного позже. А пока добавим еще один документ в индекс (не забудьте добавить первый документ):

PUT /books_index/_doc/2
{
  "name": "The Great Gatsby",
  "publishing_year": 1925,
  "author": {
    "name": "F. Scott Fitzgerald"
  }
}

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

Обратите внимание, что поля документа, которые будут сохранены, имеют собственный тип (text, integer), могут быть другими вложенными объектами со своими полями (author), а также иметь подполя (fields). Если со вложенными объектами все понятно, то вот с подполями, или, как они называются в документации, multi-fields, возникают вопросы.

Подполя нужны для того, чтобы проиндексировать одно и то же значение в разных форматах данных. Например, если хотим добавить возможность полнотекстового поиска для поля типа boolean (этот случай мы рассмотрим позже) или типа integer (хотя в данном случае это не обязательно), то можно объявить подполя типа text, и в момент вставки значение будет приведено к этому типу и сохранено в подполе.

Сразу отмечу, что поля типа keyword и text с виду похожи, но используются для разных целей. И важно понимать разницу: keyword — для поиска по полному совпадению, в то время как text — для полнотекстового поиска, то есть по частичному совпадению. Более подробно мы рассмотрим это позже, когда будем разбираться с анализаторами.

Теперь, когда у нас есть несколько документов в индексе, попробуем по ним поискать. Один из самых мощных запросов для полнотекстового поиска — это запрос query_string:

GET /books_index/_search
{
  "query": {
    "query_string": {
      "query": "harry"
    }
  }
}

GET /books_index/_search
{
  "query": {
    "query_string": {
      "query": "1925"
    }
  }
}

В первом случае будет найдена книга Harry Potter and the Philosopher’s Stone, а во втором — The Great Gatsby. Давайте более подробно разберем ответ, который получили от ElasticSearch:

"hits" : {
  "total" : {
    "value" : 1,
    "relation" : "eq"
  },
  "max_score" : 0.105360515,
  "hits" : [
    {
      "_index" : "books_index",
      "_type" : "_doc",
      "_id" : "1",
      "_score" : 0.105360515,
      "_source" : {
        "name" : "Harry Potter and the Philosopher's Stone",
        "publishing_year" : 1997,
        "author" : {
          "name" : "J. K. Rowling"
        }
      }
    }
  ]
}

Первым нас встречает total, он показывает, сколько документов, соответствующих нашему запросу, было найдено (value). Но есть нюанс: по умолчанию это число считается приблизительно. Чтобы получить точный результат, нужно в запросе указать «track_total_hits»: true или «track_total_hits»: 100, где 100 — количество записей, которые вы хотите точно подсчитать.

Но будьте осторожны: включение этой опции приведет к тому, что ElasticSearch будет «пробегаться» по всем документам в индексе, соответствующим запросу, что непременно скажется на скорости его выполнения. Второй параметр в total — relation: он может принимать либо значение eq (точное количество записей), либо gte (записей больше, чем написано в value).

Далее находится max_score — это максимальное значение _score среди найденных документов. Само же _score показывает, насколько хорошо документ подходит под критерии поиска (больше — лучше). По умолчанию оно колеблется между 0 и 1 для каждого документа, однако есть механизмы, которые позволяют это изменить (boost, tie_breaker).

Я бы советовал не привязываться к каким-то конкретным значениям показателя, так как он носит относительный характер и позволяет понять, насколько хорошо тот или иной документ из результатов выдачи соотносится с конкретным запросом. Еще один важный момент — без указания параметров сортировки поисковая выдача будет отсортирована по показателю _score в порядке убывания, что автоматически поднимет лучшие записи наверх.

Двигаемся дальше. Массив hits, который hits.hits, — это массив документов из выдачи. Наиболее важные параметры в нем — _score, _id и _source. Про _score мы уже говорили, _id — это уникальный идентификатор записи в индексе (указывается при запросе на вставку PUT/books_index/_doc/1, _id = 1). Он хранится как значение строчного типа, так что можно использовать не только числа, но и другие уникальные идентификаторы, например UUID. Поле _source — это те данные, которые были переданы ElasticSearch для вставки, то есть исходный документ.

Анализ и поиск в ElasticSearch

Любой поисковый запрос в ElasticSearch перед непосредственным исполнением попадает в анализатор (analyzer). Analyzer — это конвейер (pipeline), который состоит из нескольких частей: character filter, tokenizer и token filter.

image2 y6kVsVU

ElasticSearch предоставляет набор встроенных анализаторов для базовых потребностей, однако, скорее всего, вам придется написать собственный.

Разберем принципы работы анализатора на небольшом примере. Для этого нужно добавить в mapping собственный analyzer и назначить его для какого-нибудь поля:

PUT /books_index
{
  "settings": {
    "analysis": {
      "analyzer": {
        "customHTMLAnalyzer": {
          "type": "custom",
          "char_filter": [
            "html_strip"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "stop"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "customHTMLAnalyzer"
      },
      "publishing_year": {
        "type": "integer",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "author": {
        "properties": {
          "name": {
            "type": "text"
          }
        }
      }
    }
  }
}

Объявляем analyzer с именем customHTMLAnalyzer, определяем для него параметры character filters, tokenizer, filters (token filters) и указываем, что для поля имени книги будем использовать его.

Тогда и при вставке, и при поиске для поля документа и поискового запроса будет применено следующие:

  1. Убрать все HTML-теги из входящей строки (html_strip char filter).
  2. Разбить входную строку на токены, в данном случае на слова, при этом удаляя знаки пунктуации (standard tokenizer).
  3. Изменить регистр каждого токена на нижний (lowercase filter).
  4. Убрать токены, которые являются стоп-словами (spot filter) и не имеют важного значения для поиска. Например, для английского языка это a, an, and, for, if, in, the и так далее.

Tokenizer в ElasticSearch

Начнем подробный разбор анализатора с такой его составляющей, как tokenizer (токенайзер). Он принимает на вход массив символов (обычно текст или поисковую строку), разбивает их на отдельные токены (обычно слова) и передает их дальше в фильтры. Рассмотрим таблицу с наиболее популярными токенайзерами в ElasticSearch, полный список которых можно найти в документации.

Наиболее популярные токенайзеры
standard разбивает текст на слова, удаляет знаки пунктуации
The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone. →[The, 2, QUICK, Brown, Foxes, jumped, over, the, lazy, dog’s, bone]
classic токенайзер, основанный на правилах грамматики английского языка
ngram те самые n-граммы из части про PostgreSQL, разбивает текст на слова, затем каждое слово разбивает на n-граммы
quick → [qu, ui, ic, ck]
edge_ngram похож на ngram, тоже разбивает текст на слова, затем каждое слово разбивает на n-граммы, но делает это, сохраняя привязку к началу слова
quick → [q, qu, qui, quic, quick]
keyword это токенайзер, который ничего не делает, идеально подходит, когда нужно найти что-то по полному совпадению
quick → quick

Выбирать токенайзер нужно исходя из ваших входных данных и целей, для которых будет использоваться конкретное поле. Например, для полнотекстового поиска хорошо подходят standard (для любого языка) и classic (если планируется поиск только по английскому языку), для поиска по полному совпадению — keyword, для нечеткого (fuzzy) поиска — ngram и edge_ngram, последний — удачное решение для автодополнения (autocomplete или completion suggester, как это называется в ElasticSearch). Кроме этих токенайзеров, есть более специфические: UAX URL Email Tokenizer (uax_url_email) — такой же, как standard, только распознает email и URL-адреса как один токен, или же Path Tokenizer (path_hierarchy), который может построить иерархию пути (например, к файлу): /foo/bar/baz → [/foo, /foo/bar, /foo/bar/baz].

Filters в ElasticSearch

Переходим к фильтрам. Фильтр принимает на вход массив токенов из токенайзера и может их изменять (например, привести к нижнему регистру), удалять (стоп-слова) или добавлять новые (синонимы). Фильтры разделены на две группы: те, которые зависят от языка (отмечены *), и те, которые не зависят. Ключевая разница в том, что для языковых фильтров в их настройках нужно явно указывать язык или по умолчанию будет выбран английский (это поведение вряд ли изменится с приходом новых версий ElasticSearch, но лучше перестраховаться и всегда указывать явно). Соответственно, языковые фильтры для разных языков будут работать немного по-разному и учитывать особенности того или иного языка, чтобы предоставить наиболее релевантное поведение и, как следствие, наиболее релевантную поисковую выдачу.

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

Наиболее популярные фильтры
lowercase меняет регистр токена на нижний

«QuIck» → «quick»

trim удаляет пробельные символы в начале и в конце токена

» quick » → «quick»

stop* удаляет стоп-слова, для английского языка это a, an, and, for, if, in, the и так далее
stemmer* stemming — это процесс сокращения слова до его корневой формы. Хоть это и языковой фильтр, но чаще всего включает в себя удаление суффиксов и префиксов из слова.

«the foxes jumping quickly» → [ the, fox, jump, quickli ]

conditional позволяет применять фильтры в зависимости от условия

Character filters в ElasticSearch

Character filters используются для предварительной обработки текста до того, как он попадет в tokenizer. Встроенных фильтров для этой группы на удивление немного, но они универсальны.

Character filters
html_strip удаляет теги HTML, а также декодирует экранированные символы, например
<p>I'm so <b>happy</b>!</p>” → “nI'm so happy!n
mapping позволяет определить соответствие»ключ-значение» для последующей замены каждого найденного ключа на соответствующее ему значение
pattern_replace позволяет определить регулярное выражение (на диалекте языка Java) для нахождения символов (pattern), которые должны быть заменены согласно правилу в строке replacement

Analyze API в ElasticSearch

Для того чтобы было проще разобраться, что происходит с входными данными во время анализа, предусмотрен специальный механизм — Analyze API. Он анализирует конкретный текст с конкретным набором всех составляющих анализатора. Пользоваться им довольно просто и бывает полезно, чтобы понять, почему та или иная запись появляется (или не появляется) в поисковой выдаче и в каком именно виде будет сохранено значение конкретного поля в индексе. Приведу небольшой пример, а более подробно можно ознакомиться с этим механизмом в документации.

Запрос Ответ
GET /_analyze
{
  "tokenizer" : "whitespace",
  "filter" : [
    "lowercase",
    {
      "type": "stop",
      "stopwords": [
        "a",
        "is",
        "this"
      ]
    }
  ],
  "text" : "this is a test"
}
{
  "tokens" : [
    {
      "token" : "test",
      "start_offset" : 10,
      "end_offset" : 14,
      "type" : "word",
      "position" : 3
    }
  ]
}

Типы запросов в ElasticSearch

После того как мы разобрались с тем, как документы будут сохраняться и анализироваться в индексе, можно начинать выполнять поисковые запросы. Запросы — это то, что каждому нужно осваивать индивидуально. Описать универсальный подход к созданию запросов, чтобы они работали в большинстве случаев, невозможно. Я настоятельно рекомендую самостоятельно внимательно и детально изучить механизмы запросов в ElasticSearch, а также изучить то, как и из чего формируется _score документа в процессе запроса. Так что добро пожаловать на страницы документации, а мы проведем небольшой обзор наиболее распространенных типов запросов.

Начнем с понятий контекста запроса и фильтров (да-да, и тут фильтры). По умолчанию ElasticSearch сортирует результаты поиска по оценке релевантности (relevance score) — это то самое поле _score. Однако бывают ситуации, когда нужно произвести поиск внутри группы документов, которые соответствуют общим критериям, например, созданы не раньше определенной даты или которые имеют определенный статус. Здесь на помощь приходят фильтры запросов.

И поисковый запрос (query), и фильтр (filter) принимают на вход запросы в одном формате, но с той лишь разницей, что запросы, написанные внутри filter, не влияют на итоговое значение _score.

Рассмотрим небольшой пример:

GET /books_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "The Great Gatsby"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "publishing_year": {
              "gte": "1900"
            }
          }
        }
      ]
    }
  }
}

Вначале будут отобраны те книги, которые опубликованы в 1900 году и позднее (filter), а затем по ним будет произведен поисковый запрос (query).

Резюмируя, можно сказать, что в контексте запроса (query) мы отвечаем на вопрос: «Насколько хорошо тот или иной документ соответствует этому запросу?», а в контексте фильтра (filter): «Стоит ли рассматривать этот документ вообще?».

В примере запроса мы использовали три конструкции: bool, must и match. Рассмотрим их по порядку: bool относится к категории составных (compound) запросов. Составные оборачивают другие составные или простые запросы, чтобы объединить их результаты и оценки (_score), изменить их поведение или переключиться с запроса на контекст фильтрации. Bool-запрос сопоставляет документы, соответствующие логическим комбинациям других запросов. Наиболее близкий пример — это операция WHERE в SQL-запросе, куда тоже можно передать набор логических операций (x < 0 AND y > 5 OR z = 0). В ElasticSearch операциям AND и OR из SQL есть свои аналоги:

ElasticSearch SQL
"bool" : {
  "must" : [
    "term" : {"id" : 35},
    "term": {"age": 18}
  ]
}
SELECT * FROM users
WHERE id = 35 AND age = 18
"bool" : {
  "should" : [
    "term" : {"id" : 35},
    "term": {"age": 18}
  ]
}
SELECT * FROM users
WHERE id = 35 OR age = 18

В примере выше мы использовали новый тип запроса term — это аналог = в SQL, то есть наш документ попадет в результаты поиска, если значение из поля полностью совпадает с поисковым значением.

Перед тем как перейти к группе полнотекстовых запросов, давайте рассмотрим запросы на основе точных значений (term-level queries). С их помощью можно быстро найти записи, которые соответствуют точным критериям. Например, значение статуса продукта или его уникальный идентификатор (id), или же диапазон дат и прочее.

Важно отметить, что, в отличие от полнотекстовых запросов, запросы на основе точных значений не анализируют условия поиска (поисковый запрос). Вместо этого они ищут полное соответствие поискового запроса значению, хранящемуся в поле (проанализированному и преображенному при вставке). Поэтому фильтрация по полу типа text может не сработать, я рекомендую использовать подполя (multi-fields) с типом keyword для полей с типом text, если есть необходимость фильтрации и полнотекстового поиска по ним.

exists Возвращает документы, содержащие проиндексированное значение для поля — если для поля не выключена индексация (index: false) или оно не null или []
fuzzy Возвращает документы, похожие на поисковый запрос
ids Возвращает документы с соответствующими _id
prefix Возвращает документы, содержащие определенный префикс в указанном поле
range Возвращает документы, содержащие значения в указанном диапазоне
regexp Возвращает документы, соответствующие указанному регулярному выражению
term Возвращает документы, значение поля которых полностью совпадает с запросом.Аналог WHERE field = 15
terms Возвращает документы, содержащие одно или несколько точных совпадений в указанном поле.Аналог WHERE field IN (15, 26, 31)
terms_set То же самое, что и terms, но дает возможность указать минимальное число совпадающих значений
type Возвращает документы с указанным типом
wildcard Возвращает документы, соответствующие шаблону с символом подстановки (? и *).Аналог WHERE field LIKE ‘harry%’

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

match возвращает документы, соответствующие указанному тексту, числу, дате или логическому (boolean) значению. Этот тип запроса наиболее простой и наиболее базовый для проведения полнотекстового поиска. С его помощью также можно выполнять нечеткий (fuzyy) поиск. match анализирует входящий запрос, и в процессе анализа создается логический запрос из предоставленного текста. Параметр operator может быть установлен в and или or (по умолчанию or) для управления поведением. Это означает, что с оператором or будут найдены документы, в которых совпадает хотя бы один из токенов, при чем документы с большим числом совпадений будут иметь большее значение _score. А при использовании оператора and необходимо совпадение всех токенов из поискового запроса.

query_string — это швейцарский нож, с помощью которого через ключевые слова и символы можно построить запрос практически любой сложности. Однако применение этого типа запроса требует от разработчика экранирования специальных символов (а их немало), запрещенных пользователю для использования, а также обработки ошибок. Именно этот тип запроса напрямую применяют во вкладке Discover в Kibana.

Он поддерживает все возможности поискового синтаксиса. Например, status:active добавит условие, что поле status должно иметь значение active. Поддерживаются и логические операторы title:(quick OR brown). Также можно использовать символы подстановки qu?ck bro* и регулярных выражений name:/joh?n(ath[oa]n)//. Есть нечеткий поиск quikc~, поиск по диапазону (range), по нескольким полям сразу, группировка и многое другое.

Использование этого типа запроса дает обширные поисковые возможности, однако открывает пользователю доступ ко всем полям в индексе.

К счастью, есть более дружелюбный тип запроса с поддержкой поискового синтаксиса, но и с некоторыми ограничениям — это simple_query_string. Работает он точно так же, как и query_string, однако не возвращает ошибку, если в запросе неправильный синтаксис. Подробнее с отличиями между этими двумя запросами можно ознакомиться в документации.

Синонимы в ElasticSearch

Иногда нужно использовать значение константы или булево значение как значение поля в индексе. Например, мы хотим пометить нашу книгу как книгу лимитированного издания и дать пользователю возможность как-то выделить такие книги при полнотекстовом поиске. Тогда можем задать поле книги «limited_edition»: «limited_edition», но релевантность поисковой выдачи может быть не очень высока, так как при выполнении запроса пользователь получит желаемый результат, только если введет limited_edition. В остальных случаях возможны варианты.

Если же использовать более привычный вариант с полем типа boolean, то как-то описать значимость такого поля в полнотекстовом поиске не представляется возможным. Мы же хотим сделать так, чтобы при вводе таких слов и словосочетаний, как limited, limited edition, deluxe, deluxe edition, special, exclusive и других, пользователь мог увидеть книги из лимитированного издания и, может быть, приобрести их. Для этого понадобятся синонимы.

Синонимы в ElasticSearch — это не что иное, как синонимы в любом естественном языке — разные слова, которые имеют одинаковый смысл, с той лишь разницей, что в разговорной речи понимание синонимов происходит само собой, а в ElasticSearch их нужно явно определить. Для этого в поисковом движке предусмотрен специальный фильтр synonym token filter. Он поддерживает специальный синтаксис объявления синонимов, а также позволяет загрузить их непосредственно из файла. Для задания синонимов лучше использовать один подход: синонимы в файле или в настройках индекса (mapping).

"filter": {
  "synonym_filter": {
    "type": "synonym",
    "synonyms_path": "analysis/synonym.txt",
    "lenient": true,
    "synonyms": [
      "limited, limited edition, deluxe, deluxe edition => limited_edition",
      "free, cheap"
    ]
  }
}

Рассмотрим этот пример. Мы создаем фильтр, который называется synonym_filter и имеет тип synonym. Исключительно для примера были использованы оба способа объявления синонимов: для файла это поле synonyms_path и относительный путь к нему, для списка синонимов — массив synonyms. Свойство lenient отвечает за игнорирование ошибок при обработке синонимов.

Есть два основных способа отождествить два слова: простое отождествление, как в нашем примере free, cheap, и отождествление с заменой — при выполнении этого фильтра limited, limited edition, deluxe, deluxe edition будут заменены на константу limited_edition. Это два разных подхода, которые только на первый взгляд работают похожим образом. Нужно понимать, что при использовании нотации со стрелкой (=>) мы заменяем слова из левой части на слова из правой, что означает, что только документы, в которых есть слово из правой части, будут хоть как-то отображены в результатах поиска. Нотация с запятой позволяет отождествить слова. Можно представить это так: если слово совпало с одним из списка, то замените его на весь список. Таким образом документы, которые содержат в себе хотя бы одно из слов-синонимов, будут оценены выше, поэтому будут находиться выше в результатах поиска.

В примере есть одна неточность, точнее особенность. Так как синонимы — это тоже фильтр, то он работает с токенами. И если текст разбивается на токены по пробелам, то многословные синонимы (словосочетания) работать не будут, потому что в самом токене физически не может быть несколько слов, разделенных пробелом.

Чтобы решить эту проблему, можно как-то настроить токенайзер, однако лучше использовать встроенную функцию, предназначенную для таких случаев. Она называется synonym graph token filter.

Синтаксис у synonym graph token filter такой же, как у synonym filter, но работает он немного иначе. Во время работы этого фильтра будет создан graph token stream, то есть он будет обрабатывать не отдельные токены, а их набор. Принципы работы этого фильтра отлично описывает картинка из официальной документации. Если необходимо заменить фразу на ее акроним: domain name system => dns, то выглядит это следующим образом:

image3 hTGEkvb

Будет найдено словосочетание domain name system и заменено на dns, и последующие фильтры будут работать уже с измененным набором токенов. Возможны варианты, когда будут созданы два подзапроса: для исходного набора токенов и для измененного, например, когда используется match_phrase.

Подробнее узнать про синонимы и граф синонимов можно в документации.

Собираем все вместе

Чтобы получить более наглядную картину того, как все эти настройки уживаются вместе, рассмотрим пример:

{
  "index_patterns": [
    "book*"
  ],
  "settings": {
    "analysis": {
      "analyzer": {
        "books_analyzer": {
          "type": "custom",
          "char_filter": [
            "html_strip"
          ],
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "synonym_filter",
            "synonym_graph_filter",
            "english_possessive_stemmer",
            "english_stop"
          ]
        }
      },
      "filter": {
        "english_stop": {
          "type": "stop",
          "stopwords": "_english_"
        },
        "english_possessive_stemmer": {
          "type": "stemmer",
          "language": "possessive_english"
        },
        "synonym_filter": {
          "type": "synonym",
          "synonyms": [
            "limited, limited edition, deluxe, deluxe edition => limited_edition",
            "free, cheap"
          ]
        },
        "synonym_graph_filter": {
          "type": "synonym_graph",
          "synonyms": [
            "limited edition, deluxe edition => limited_edition"
          ]
        }
      }
    }
  },
  "mappings": {
    "dynamic": false,
    "date_detection": false,
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "books_analyzer"
      },
      "publishing_year": {
        "type": "integer",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "author": {
        "properties": {
          "name": {
            "type": "text",
            "analyzer": "books_analyzer"
          }
        }
      },
      "limited_edition": {
        "type": "text",
        "analyzer": "books_analyzer",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

Мы объявили шаблон индексов, который после добавления будет автоматически применяться ко всем индексам, имя которых начинается с book. Задали свой анализатор, который уберет все HTML-теги из входного текста, затем разобьет его на токены по пробелам, а также уберет символы пунктуации. После этого поля, для которых указано использование анализатора, будут следовать указанным правилам анализа.

Немного хитростей для удобства

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

Первая из них — это шаблоны индексов (index templates). Они позволяют описать настройки индекса и задать имена или паттерны имен индексов, при создании которых будет применен этот шаблон. Подробнее в документации.

Вторая — псевдоним (alias) индекса. Он работает по принципу указателя в языке программирования: указывает на конкретный индекс и может быть в любой момент изменен, чтобы указывать на другой. Таким образом, работая с индексом посредством псевдонима, можно писать код, не опасаясь, что изменение имени индекса приведет к изменению кода.

image1 srzqJTF

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

Опишем этот процесс:

  1. Обновить шаблон индексов.
  2. Создать новый индекс из шаблона.
  3. Добавить все данные из базы данных в новый индекс, во время этого процесса все изменения над данными должны записываться в два индекса (в alias и в новый индекс).
  4. Переключить alias на новый индекс.
  5. Удалить старый индекс.

И последние две настройки, которые позволят вам выделить другие поля в документе на фоне остальных при полнотекстовом поиске: boost и tie_breaker. Опцию boost (по умолчанию = 1) можно указывать как для полей, так и для запросов/подзапросов. Если она задана, то _score поля или запроса будет умножен на ее значение: _score = original _score * boost.

tie_breaker указывается для запроса, причем не каждый запрос его поддерживает, значение его может быть в пределах от 0.0 до 1.0 и работает он так: если его значение >0.0 (по умолчанию = 0.0), то финальное значение документа считается следующим образом:

  1. Выбрать _score наиболее подходящего поля.
  2. Умножить _score остальных полей, подходящих под критерии поиска на tie_breaker.
  3. Сложить и нормализировать полученные результаты.

С этой опцией можно выделить те документы, которые содержат один и тот же токен в разных полях.

Используя boost и tie_breaker, можно сделать акцент полнотекстового поиска на нужных вам полях, а также определить роль второстепенных полей.

Еще пару слов про ElasticSearch

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

Бонусный раздел: интеграция ElasticSearch в приложение на Go

Для работы с ElasticSearch в Go я использую библиотеку github.com/olivere/elastic/v7. Перед тем как отправить запросы в ElasticSearch, нужно к нему подключиться:

options := []elastic.ClientOptionFunc{
    elastic.SetURL("http://localhost:9200"),
}
cli, _ := elastic.NewClient(options...)

В этом и всех последующих примерах ошибки не обрабатываются для краткости, однако в реальных проектах так делать не стоит.

После этого можно создать шаблон индекса и сам индекс, а также добавить к нему псевдоним:

body, _ := ioutil.ReadFile("path/to/index/template.json")
cli.IndexPutTemplate("books_template").BodyString(string(body)).Do(ctx)
cli.CreateIndex("books_index_1").BodyString("").Do(ctx)
cli.Alias().Add("books_index_1", "books_index").Do(ctx)

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

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

exists, err := cli.IndexExists("books_index_1").Do(ctx)

И не создавать его, если в этом нет необходимости.

Для шаблонов такую операцию можно не проводить, они будут просто обновлены до нужного состояния.

На этом этапе уже можно как добавлять новые документы в индекс, так и искать по ним, однако давайте остановимся и попробуем написать удобный интерфейс для составления сложных запросов. Для этого обратимся к шаблону проектирования Builder.

type QueryBuilder struct {
    q *elastic.BoolQuery
}

func NewQueryBuilder() *QueryBuilder {
    return &QueryBuilder{
        q: elastic.NewBoolQuery(),
    }
}

func (b QueryBuilder) Query() elastic.Query {
    return b.q
}

Корневым запросом у нас будет boolean query, на базе которого можно построить разные вариации как поиска, так и фильтрации. Для начала объявим метод для полнотекстового поиска:

func (b *QueryBuilder) Search(query string) *QueryBuilder {
    b.q = b.q.Should(
        elastic.NewQueryStringQuery(query).Boost(2).DefaultOperator("AND").TieBreaker(0.4),
        elastic.NewQueryStringQuery(query).Boost(1).DefaultOperator("OR").TieBreaker(0.1),
    )

    return b
}

В нем мы используем запрос типа query_string (не забываем про экранирование спецсимволов или альтернативы этому запросу в виде simple_query_string или match), объединим (используя should) документы, которые лучше соответствуют поисковому запросу, с теми, которые соответствуют ему хуже. Для документов, в которых совпало больше токенов (default operator AND), мы умножим их _score на 2 (boost), а также увеличим влияние других полей (tie_breaker) на результат. А для документов, которые совпали не идеально с поисковым запросом (90% случаев), в поле boost явно зададим значение по умолчанию 1 и совсем немного увеличим влияние других полей.

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

Сделаю ремарку, что запись b.q = b.q.Should(…), то есть переприсваивание результатов выполнения, не является обязательной, так как в этой библиотеке для работы с ElasticSearch построение запросов модифицирует внутренний объект и возвращает его же в качестве результата. При написании b.q.Should(…) без переприсвоения мы неявно модифицируем объект q посредством библиотеки. Следовать тому или иному подходу — решать вам, я же предпочитаю явные объявления, поэтому и в дальнейших примерах буду использовать вариант с переопределением.

Чтобы добавить объект в индекс, можно воспользоваться следующей командой:

cli.Index().Index("books_index").Id(1).BodyJson(book).Do(ctx)

Где book — это наш объект, например структура, которая будет сериализована в JSON внутри метода BodyJson и добавлена в индекс, на который указывает псевдоним books_index.

Что, если мы захотим отфильтровать книги по какому-то признаку, например по автору или тому, является ли издание лимитированным.

func (b *QueryBuilder) Author(name string) *QueryBuilder {
    b.q = b.q.Filter(elastic.NewTermQuery("author.name", name))

    return b
}

Можно сделать практически то же самое, но чтобы результаты фильтрации влияли на _score.

func (b *QueryBuilder) TermQuery(termKey, termValue string) *QueryBuilder {
    b.q = b.q.Must(elastic.NewTermQuery(termKey, termValue))

    return b
}

Меняя Filter на Must, можно быстро и просто корректировать поведения поиска. А на базе такого запроса просто построить другой запрос, который позволит отфильтровать по полям, для которых было применено сочетание text + keyword (в нашем случае это limited_edition).

func (b *QueryBuilder) BoolKeywordQuery(termKey, termValue string) *QueryBuilder {
    return b.TermQuery(termKey+".keyword", termValue)
}

Как видите, обращение осуществляется так же, как и к вложенному объекту — через точку.

Запрос типа terms может выглядеть так:

func (b *QueryBuilder) TermsQuery(termKey string, vals []interface{}) *QueryBuilder {
    if len(vals) == 0 {
        return b
    }

    b.q = b.q.Must(elastic.NewTermsQuery(termKey, vals...))

    return b
}

А типа range для числовых значений — следующим образом:

func (b *QueryBuilder) RangeQuery(termKey string, min, max int) *QueryBuilder {
    if min == 0 && max == 0 {
        return b
    }

    var rangeQuery = elastic.NewRangeQuery(termKey)

    if min != 0 {
        rangeQuery = rangeQuery.Gte(min)
    }

    if max != 0 {
        rangeQuery = rangeQuery.Lte(max)
    }

    b.q = b.q.Must(rangeQuery)

    return b
}

Комбинируя разные запросы и добавляя новые слои абстракции над ними, получим удобный инструмент для написания сложных запросов простым способом.

Выполнить любой запрос можно так:

query := NewQueryBuilder().
    Search("harry potter limited edition").
    TermQuery("author.name", "J. K. Rowling").
    RangeQuery("publishing_year", 1990, 2001).
    Query()

cli.Search().
    Index("books_index").
    From(offset).
    Size(limit).
    Query(query).
    Do(ctx)

Здесь limit и offset — это параметры пагинации, так как по умолчанию результаты поискового запроса отсортированы в порядке убывания параметра _score, то явным образом сортировку можно не указывать, однако и это возможно.

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

func (b *QueryBuilder) DebugPrint() {
    fmt.Printf("=================n=  DEBUG START  =n=================n")

    source, _ := b.Query().Source()
    sourceJson, _ := json.Marshal(source)
    fmt.Printf("Query:n%sn", string(sourceJson))

    fmt.Printf("=================n=   DEBUG END   =n=================n")
}

Что осталось за кадром

Вот мы и подошли к концу статьи, для кого-то неожиданно быстро, а для кого-то неожиданно медленно. Напоследок хочу описать те важные моменты, что не попали в материал.

Во-первых, это внутреннее устройство принципов работы полнотекстового поиска в ElasticSearch, во-вторых — настройка, мониторинг и масштабирование его кластера.

Касательно самого ElasticSearch и его поисковых возможностей, то тут стоит обратить внимание на:

  • работу с сортировкой: для простых случаев она выполняется легко, но для более сложных — местами, нетривиально;
  • работа с агрегациями: агрегации — мощный инструмент в ElasticSearch, который позволяет не только заменить GROUP BY в SQL, но и превзойти его;
  • работа с вложенными полями: хоть мы и рассмотрели примеры по работе с вложенными объектами, однако не всегда вложенные поля — это объекты. Это могут быть и массивы. И работа с ними может оказаться неочевидной, особенно когда нужно осуществить сложную выборку и сортировку;
  • автодополнение (completion suggester) — тоже отдельная тема для разговора и изучения.

Послесловие

Вот и настало время прощаться. Спасибо вам за ваше время, проведенное за чтением статьи, надеюсь, вы потратили его с пользой. Хотелось бы еще раз сказать, что настройка качественного и быстрого поиска — это длительный процесс, так что запаситесь терпением, ищите, ошибайтесь, учитесь, развивайтесь, адаптируйтесь, эволюционируйте.

telegramПідписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

  • По осеннему хмурый как пишется
  • По перек как пишется через дефис или нет
  • По паллетно как пишется слово
  • По первому зову как пишется
  • По осеннему через дефис или нет как пишется