{"id":349029,"date":"2023-06-19T15:03:28","date_gmt":"2023-06-19T15:03:28","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=349029"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=349029","title":{"rendered":"<span>\u041f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432 PostgreSQL \u0441 SQLAlchemy<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/378\/700\/137\/37870013796c77932078c8bcb614cea2.jpg\" width=\"870\" height=\"580\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/378\/700\/137\/37870013796c77932078c8bcb614cea2.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f\u00a0 \u043f\u0438\u0441\u0430\u043b \u043e \u0442\u043e\u043c, <a href=\"https:\/\/habr.com\/ru\/companies\/beeline_cloud\/articles\/734164\/\">\u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c end-to-end \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 telegram-\u0431\u043e\u0442\u0430.<\/a> \u0410 \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432 Postgres \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c SQLAlchemy \u0438 \u043a\u0430\u043a \u0435\u0433\u043e \u043f\u0440\u043e\u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c.\u00a0<\/p>\n<p>\u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041c\u0438\u0445\u0430\u0438\u043b \u0412\u044b\u0431\u043e\u0440\u043d\u044b\u0439, \u044f python-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, backend-developer \u0432 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u043c \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0435 <a href=\"https:\/\/cloud.beeline.ru\/?utm_source=owned_media&amp;utm_medium=habr&amp;utm_campaign=beeline_cloud&amp;utm_term=polnotekstoviy_poisk_v_PostgreSQL_s_SQLAlchemy\">beeline cloud<\/a>. \u0412\u044b \u0443\u0437\u043d\u0430\u0435\u0442\u0435, \u043a\u0430\u043a \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0444\u0430\u0439\u043b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f alembic. \u0412 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439, \u0433\u0434\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0441\u0435\u0440\u0432\u0438\u0441-\u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u0432 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442.\u00a0<\/p>\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0422\u0435\u043c\u0430 \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 (Full Text Search, FTS) \u0443\u0436\u0435 \u043d\u0435 \u0440\u0430\u0437 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u043b\u0430\u0441\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430. \u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043d\u0435 \u043e\u0431\u043e\u0439\u0442\u0438\u0441\u044c \u0431\u0435\u0437 Sphinx \u0438\u043b\u0438 Elasticsearch, \u043d\u043e \u0435\u0441\u043b\u0438 \u043f\u043e\u0438\u0441\u043a \u0432 \u0432\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u0435 \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u2013 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c FTS \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0432 \u0411\u0414. \u041d\u0435 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0432 \u043d\u0435\u043c \u043a\u043e\u043d\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439.<\/p>\n<p>Postgres \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 FTS.<\/p>\n<ul>\n<li>\n<p>\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u044b LIKE \/ ILIKE<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0438\u0441\u043a \u0441\u0442\u0440\u043e\u043a\u0438 ts_query \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0443 ts_vector<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 pg_trgm: \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c (Trigram \/ Trigraph)<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u043e LIKE \/ ILIKE \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0438 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0430 ts_query \/ ts_vector \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0442\u043e\u043d\u043a\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u0435\u0439 \u0438 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044f\u0437\u044b\u043a\u043e\u0432. \u0421\u0430\u043c\u044b\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c.<\/p>\n<p>\u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430 \u2014 \u044d\u0442\u043e \u0433\u0440\u0443\u043f\u043f\u0430 \u0442\u0440\u0435\u0445 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432, \u0432\u0437\u044f\u0442\u044b\u0445 \u0438\u0437 \u0441\u0442\u0440\u043e\u043a\u0438. \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u044c \u0441\u0445\u043e\u0436\u0435\u0441\u0442\u044c \u0434\u0432\u0443\u0445 \u0441\u0442\u0440\u043e\u043a, \u043f\u043e\u0434\u0441\u0447\u0438\u0442\u0430\u0432 \u0447\u0438\u0441\u043b\u043e \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0441\u0442\u044c \u0432 \u043e\u0431\u0435\u0438\u0445. \u042d\u0442\u0430 \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0438\u0434\u0435\u044f \u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0447\u0435\u043d\u044c \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0441\u0445\u043e\u0436\u0435\u0441\u0442\u0438 \u0441\u043b\u043e\u0432 \u043d\u0430 \u043c\u043d\u043e\u0433\u0438\u0445 \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u044f\u0437\u044b\u043a\u0430\u0445.<\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c pg_trgm \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Approximate_string_matching\">Fuzzy Text Search<\/a> (\u043d\u0435\u0447\u0435\u0442\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u0441 <em>\u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0430\u043c\u0438<\/em>) \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 \u043d\u0430 \u0420\u0443\u0441\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435.<\/p>\n<h2>\u041f\u043e\u0438\u0441\u043a pg_trgm \u0432 \u0441\u0432\u044f\u0437\u043a\u0435 \u0441 SQLAlchemy<\/h2>\n<h3>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043a \u043f\u043e\u0438\u0441\u043a\u0443<\/h3>\n<ul>\n<li>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0442\u0430\u043a\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">class Base(MappedAsDataclass, DeclarativeBase): id: Mapped[UUID] = mapped_column(primary_key=True, init=False, default_factory=uuid.uuid4)  class Article(Base, kw_only=True): __tablename__ = 'articles'  title: Mapped[str | None] body: Mapped[str | None]<\/code><\/pre>\n<ul>\n<li>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(text('CREATE EXTENSION IF NOT EXISTS pg_trgm'))<\/code><\/pre>\n<ul>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c.  <\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.add( Article( title='Full Text Search',  body='Full Text Search in PostgreSQL with SQLAlchemy. ' ) )<\/code><\/pre>\n<ul>\n<li>\n<p>\u0414\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0432 \u0437\u0430\u043f\u0438\u0441\u044f\u0445.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(select(func.show_trgm(Article.title))).all()  # \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u044b \u0434\u043b\u044f \u043f\u043e\u043b\u044f title ['  f', '  s', '  t', ' fu', ' se', ' te', 'arc', 'ch ', 'ear',   'ext', 'ful', 'll ', 'rch', 'sea', 'tex', 'ull', 'xt ']<\/code><\/pre>\n<ul>\n<li>\n<p>\u0422\u0430\u043a \u0436\u0435 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c similarity_ratio \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u042d\u0442\u043e\u0442 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 <em>\u0441\u0445\u043e\u0434\u0441\u0442\u0432\u043e<\/em> \u0434\u0432\u0443\u0445 \u0441\u0442\u0440\u043e\u043a \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(select(func.similarity(Article.title, 'search string')).all()<\/code><\/pre>\n<ul>\n<li>\n<p>\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u0443\u044e \u0440\u0430\u0437\u0434\u0430\u0447\u0443 \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0442 \u0432\u0441\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u0441 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442\u043e\u043c similarity \u0431\u043e\u043b\u044c\u0448\u0435 0.3. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0431\u043e\u043b\u0435\u0435 \u043c\u044f\u0433\u043a\u0438\u0439 \u043b\u0438\u043c\u0438\u0442 \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0435\u0441\u0441\u0438\u0438.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(select(func.set_limit('0.01')))<\/code><\/pre>\n<h3>\u041f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441<\/h3>\n<p>\u0414\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 SQLAlchemy \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 <a href=\"https:\/\/docs.sqlalchemy.org\/en\/20\/dialects\/postgresql.html#full-text-search\">match<\/a> . \u0414\u043b\u044f Postgres \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 @@, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u043f\u043e\u0438\u0441\u043a ts_query \u043f\u043e ts_vector.  <\/p>\n<pre><code class=\"sql\">select(Article.body.match('search string')) # \u043f\u043e\u0438\u0441\u043a ts_query \u043f\u043e ts_vector<\/code><\/pre>\n<p>\u041d\u043e \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u0432 \u0410\u043b\u0445\u0438\u043c\u0438\u0438 \u043d\u0435\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0443\u043b\u0435\u0432\u044b\u0439 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 pg_trgm: <em>\u0437\u043d\u0430\u043a \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u0430<\/em>.<\/p>\n<pre><code class=\"python\">Article.body.bool_op('%')('search string')<\/code><\/pre>\n<p>\u0426\u0435\u043b\u0438\u043a\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c.<\/p>\n<pre><code class=\"python\">term = 'search string' select( Article.body,  # \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u0432\u044b\u0434\u0430\u0447\u0443 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 func.similarity(Article.body, term),  ) .where( # \u0432\u0441\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043b\u0438\u043c\u0438\u0442\u0430 Article.body.bool_op('%')(term), ) .order_by( # \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442\u0443 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f func.similarity(Article.body, term).desc(),  )<\/code><\/pre>\n<h3>\u041f\u043e\u0438\u0441\u043a \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u043c<\/h3>\n<p>\u0412\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u044c \u0437\u0430\u043c\u0435\u0442\u0438\u043b, \u0447\u0442\u043e \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043f\u043e\u0438\u0441\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043a\u043e\u043b\u043e\u043d\u043a\u0435 body. \u0414\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u043e\u043b\u044f\u043c \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c concat.<\/p>\n<pre><code class=\"python\">columns = func.coalesce(Article.title, '').concat(func.coalesce(Article.body, '')) columns = columns.self_group() # \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u043a\u0430\u0442\u0438\u043d\u0430\u0446\u0438\u044e \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432 \u0432 \"\u0441\u043a\u043e\u0431\u043a\u0438\"<\/code><\/pre>\n<p>? \u041f\u043e\u043c\u0438\u043c\u043e concat, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u043a\u0430\u0436\u0434\u043e\u0435 \u043f\u043e\u043b\u0435 \u0432 coalesce, \u0447\u0442\u043e\u0431\u044b \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f None \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u043f\u043e\u043b\u0435\u0439, \u043c\u044b \u043d\u0435 \u0442\u0435\u0440\u044f\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043f\u0440\u043e concat \u0438 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 || \u0432 Postgres \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/www.postgresqltutorial.com\/postgresql-string-functions\/postgresql-concat-function\/\">\u0442\u0443\u0442<\/a>.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u043e\u043b\u044f\u043c.<\/p>\n<pre><code class=\"python\">term = 'search string' select( Article.body, Article.title, func.similarity(columns, term),  # \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043a\u043e\u043d\u043a\u0430\u0442\u0435\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u0442\u043e\u043b\u0431\u0446\u044b ) .where( columns.bool_op('%')(term),      # \u0437\u0434\u0435\u0441\u044c \u0442\u043e\u0436\u0435 ) .order_by( func.similarity(columns, term).desc(),  # \u0438 \u0437\u0434\u0435\u0441\u044c )<\/code><\/pre>\n<h3>\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438 pg_trgm<\/h3>\n<p>\u041d\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442 \u0437\u0430\u0431\u044b\u0432\u0430\u0442\u044c \u0438 \u043e \u043c\u0438\u043d\u0443\u0441\u0430\u0445 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u043c\u0435\u043d\u044f \u044d\u0442\u043e \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0439 \u0432\u044b\u0434\u0430\u0447\u0438. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0444\u0440\u0430\u0437\u044b &#8216;Full Text Search&#8217; \u043f\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0441\u0442\u0430\u0442\u044c\u044f\u043c:<\/p>\n<pre><code class=\"python\">Article(title='Full Text Search. ', body='Full Text Search in PostgreSQL with SQLAlchemy. '), Article(title='Full Text Search. ', body=None)<\/code><\/pre>\n<p>\u041c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u044d\u0442\u043e\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<pre><code class=\"python\">('Full Text Search. ', None, 0.4166666567325592), ('Full Text Search. ', 'Full Text Search in PostgreSQL with SQLAlchemy. ', 0.22727273404598236),<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0444\u0440\u0430\u0437\u0430 &#8216;Full Text Search&#8217; \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442\u0441\u044f \u0434\u0432\u0430\u0436\u0434\u044b, \u043d\u043e \u043e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430 \u043c\u0435\u043d\u044c\u0448\u0438\u0439 similarity_ratio. \u042d\u0442\u043e \u0432\u044b\u0437\u0432\u0430\u043d\u043e \u0442\u0435\u043c, \u0447\u0442\u043e \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043e\u043b\u044f text \u0438 body \u0438\u043c\u0435\u044e\u0442 \u0431\u043e\u043b\u044c\u0448\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442 \u0431\u043e\u043b\u044c\u0448\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435, \u0447\u0442\u043e \u043f\u043e\u043d\u0438\u0436\u0430\u0435\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c <em>&#171;\u0443\u043c\u043d\u044b\u0439&#187;<\/em> \u043f\u043e\u0438\u0441\u043a, \u0433\u0434\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0439 \u0432\u0435\u0441 \u0434\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 (\u043a\u043e\u0433\u0434\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u0446\u0435\u043d\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c \u0432 \u0442\u0435\u043b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438), \u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u0441\u043b\u043e\u0432 \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430, \u0442\u043e \u0441\u0442\u043e\u0438\u0442 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e ts_query \/ ts_vector. \u0422\u0430\u043a \u0436\u0435 \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0438\u043d\u0434\u0435\u043a\u0441 <a href=\"https:\/\/github.com\/postgrespro\/rum\">RUM<\/a> \u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u044c <a href=\"https:\/\/github.com\/postgrespro\/hunspell_dicts\">hunspell<\/a> \u043e\u0442 Postgres PRO \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430.<\/p>\n<h2>\u0418\u043d\u0434\u0435\u043a\u0441<\/h2>\n<h3>\u041f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/h3>\n<p>Postgres \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043c \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c. \u0412 SQLAlchemy \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c.<\/p>\n<pre><code class=\"sql\">Index( 'article_title_body_trgm_idx',  # \u0438\u043d\u0434\u0435\u043a\u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e \u0432\u044b\u0437\u043e\u0432\u0443 \u0442\u043e\u0447\u043d\u043e \u0442\u0430\u043a\u043e\u0439 \u0436\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, # \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 func.coalesce(Article.title, '').concat(func.coalesce(Article.body, '')).label('columns'),  # \u0442\u0430\u043a \u0436\u0435 \u0434\u043b\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u0430 GIN \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 gin_trgm_ops,  # \u0442\u0430\u043a Postgres \u043f\u043e\u0439\u043c\u0435\u0442,\u0447\u0442\u043e \u0438\u043d\u0434\u0435\u043a\u0441 \u043d\u0443\u0436\u043d\u043e \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c postgresql_using='gin', postgresql_ops={ 'columns': 'gin_trgm_ops', }, )<\/code><\/pre>\n<h3>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043d\u0430 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043e\u0431\u044a\u0435\u043c\u0430\u0445, \u043c\u043e\u0436\u043d\u043e <em>\u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c<\/em> Postgres \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c. \u041d\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442 \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435, \u0442\u0430\u043a \u043a\u0430\u043a Postgres \u0438\u043c\u0435\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b \u0434\u043b\u044f \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u0438\u043d\u043e\u0433\u0434\u0430 <em>\u0434\u0435\u0448\u0435\u0432\u043b\u0435<\/em> \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c SeqScan, \u0434\u0430\u0436\u0435 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430.<\/p>\n<pre><code class=\"python\">session.execute(text('SET enable_seqscan = OFF')) # \u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0432\u044b\u0432\u0435\u0441\u0442\u0438 Query Plan. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u043c explain \u0438\u0437 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 Wiki \u0410\u043b\u0445\u0438\u043c\u0438\u0438: <a href=\"https:\/\/github.com\/sqlalchemy\/sqlalchemy\/wiki\/Query-Plan-SQL-construct\">Query Plan SQL construct<\/a>.<\/p>\n<pre><code class=\"sql\">statement = select(...) session.execute(explain(statement)).scalars() Sort  (cost=42.40..42.42 rows=8 width=68)   Sort Key: (similarity(((COALESCE(title, ''::character varying))::text || (COALESCE(body, ''::character varying))::text), '21345'::text)) DESC   ->  Bitmap Heap Scan on article  (cost=32.07..42.28 rows=8 width=68)         Recheck Cond: (((COALESCE(title, ''::character varying))::text || (COALESCE(body, ''::character varying))::text) % '21345'::text)         ->  Bitmap Index Scan on article_title_body_trgm_idx  (cost=0.00..32.06 rows=8 width=0)               Index Cond: (((COALESCE(title, ''::character varying))::text || (COALESCE(body, ''::character varying))::text) % '21345'::text) <\/code><\/pre>\n<p>\u041a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c \u0438\u0437 \u043f\u043b\u0430\u043d\u0430, \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e\u0439\u0434\u0435\u0442 \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443: Bitmap Index Scan. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0431\u0443\u0434\u0435\u0442 \u0434\u0440\u0443\u0433\u0430\u044f \u043a\u043e\u043d\u043a\u0430\u0442\u0435\u043d\u0430\u0446\u0438\u044f \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432, Postgres \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u0447\u0435\u0440\u0435\u0437 Seq Scan.<\/p>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0441\u0430\u043c\u0438.<\/p>\n<pre><code class=\"python\"># \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0432\u0441\u0435\u0445 \u0442\u0430\u0431\u043b\u0438\u0446 Base.metadata.create_all(bind=engine)  # \u0438\u043b\u0438 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e index = Index('article_title_body_trgm_idx', ...) index.create(engine)<\/code><\/pre>\n<p>\u041d\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u0441\u0445\u0435\u043c\u044b \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c Alembic. \u0412 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043b\u0443\u0447\u0448\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0432 \u0440\u0435\u0432\u0438\u0437\u0438\u044e.<\/p>\n<pre><code class=\"python\">index = Index('article_title_body_trgm_idx', ...) ...  # \u0444\u0430\u0439\u043b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 def upgrade() -> None: op.create_table('article', ...) op.create_index( index.name, index.table.name, index.expressions, postgresql_using='gin', postgresql_ops={'columns': 'gin_trgm_ops'}, )    def downgrade() -> None: op.drop_index( 'article_title_body_trgm_idx', table_name='article', postgresql_using='gin', postgresql_ops={'columns': 'gin_trgm_ops'}, ) op.drop_table('article') ...<\/code><\/pre>\n<p>? \u041f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0444\u0430\u0439\u043b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 alembic revision &#8212;autogenerate \u0438\u043d\u0434\u0435\u043a\u0441 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<h2>\u041f\u043e\u0438\u0441\u043a \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c<\/h2>\n<p>\u041d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438, \u043a\u0430\u043a \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 pg_trgm \u0434\u043b\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432 <em>\u043e\u0434\u043d\u043e\u0439<\/em> \u0442\u0430\u0431\u043b\u0438\u0446\u044b. \u041d\u043e, \u0437\u0430\u0447\u0430\u0441\u0442\u0443\u044e, \u043b\u043e\u0433\u0438\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c \u0441\u0440\u0430\u0437\u0443. \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0438\u043c, \u043d\u0430\u0448\u0430 \u0441\u0445\u0435\u043c\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a.<\/p>\n<pre><code class=\"python\">class Author(Base, kw_only=True): __tablename__ = 'authors'  username: Mapped[str] first_name: Mapped[str | None] last_name: Mapped[str | None]  # relations: articles: Mapped[list[Article]] = relationship( back_populates='author', default_factory=list, )  class Article(Base, kw_only=True): __tablename__ = 'articles'  title: Mapped[str | None] body: Mapped[str | None]  # relations: author_id: Mapped[uuid.UUID] = mapped_column(ForeignKey(Author.id), init=False, repr=False) author: Mapped[Author] = relationship( back_populates='articles', init=False, )<\/code><\/pre>\n<p>\u0415\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0447\u0442\u043e \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0438\u0441\u043a\u0430\u0442\u044c \u0441\u0440\u0430\u0437\u0443 \u0438 \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u0430\u0432\u0442\u043e\u0440\u0430 \u0438 \u043f\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044e \u0441\u0442\u0430\u0442\u044c\u0438. \u041f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0432 \u0432\u044b\u0434\u0430\u0447\u0443 \u0432\u0441\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0430\u0432\u0442\u043e\u0440\u0430, \u0435\u0441\u043b\u0438 \u0441\u043e\u0432\u043f\u0430\u043b\u043e \u0435\u0433\u043e \u0438\u043c\u044f \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0430\u0432\u0442\u043e\u0440\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0436\u0435 \u0438\u043c\u044f.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c, \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c <a href=\"https:\/\/www.postgresql.org\/docs\/current\/rules-materializedviews.html\">Matherizlized View<\/a>. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0435 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0442\u044c \u0435\u0435 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445. \u0418 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f Alembic. \u041d\u043e \u043e\u0431 \u044d\u0442\u043e\u043c \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0447\u0430\u0441\u0442\u0438.<\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u043d\u0435 \u0441\u0430\u043c\u0443\u044e \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u0443\u044e \u0441\u0432\u044f\u0437\u043a\u0443 \u043c\u0435\u0436\u0434\u0443 PostgreSQL \u0438 SQLAlchemy, \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0440\u0430\u0431\u043e\u0447\u0438\u0439 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u0431\u0435\u0437 \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0441\u0442\u0438. \u041f\u043e \u0441\u0443\u0442\u0438, \u0432\u0441\u0435 \u0447\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c:<\/p>\n<ul>\n<li>\n<p>\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 pg_trgm<\/p>\n<\/li>\n<li>\n<p>\u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0441 \u043a\u043e\u043d\u043a\u0430\u0442\u0435\u043d\u0430\u0446\u0438\u0435\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c similarity_ratio \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0435\u0441\u0441\u0438\u0438<\/p>\n<\/li>\n<li>\n<p>\u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e \u044d\u0442\u0438\u043c \u0436\u0435 \u043f\u043e\u043b\u044f\u043c \u0441 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u043c where(columns.bool_op(&#8216;%&#8217;)(term))<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f FTS \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0447\u0435\u043d\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u0430 \u0434\u043b\u044f MVP-\u0440\u0435\u0448\u0435\u043d\u0438\u0439:<\/p>\n<ul>\n<li>\n<p>\u041f\u043e\u0438\u0441\u043a \u0431\u044b\u0441\u0442\u0440\u044b\u0439, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0438\u043d\u0434\u0435\u043a\u0441.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0438\u0441\u043a \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u0442 \u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0438, \u0442\u0430\u043a \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u044b.<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0438\u0441\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043b\u044e\u0431\u044b\u0445 \u044f\u0437\u044b\u043a\u0430\u0445, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u0435\u0439.<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0430\u043a \u0436\u0435, \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430, \u044f \u0441\u043e\u0431\u0440\u0430\u043b \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 <a href=\"https:\/\/github.com\/MishaVyb\/full-text-search\/tree\/main\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439<\/a>, \u0433\u0434\u0435 \u043e\u043f\u0438\u0441\u0430\u043b \u043a\u043b\u0430\u0441\u0441 FuzzySearchService. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0432 \u0434\u0435\u0442\u0430\u043b\u044f\u0445 \u0438\u043b\u0438 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e \u0432 \u0432 \u0441\u0432\u043e\u0435\u0439 \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e. \u0411\u0443\u0434\u0443 \u0440\u0430\u0434 \u0443\u0441\u043b\u044b\u0448\u0430\u0442\u044c \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445 \u0432\u0430\u0448 \u043e\u043f\u044b\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 FTS \u0432 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435 \u0438 \u0441 \u043a\u0430\u043a\u0438\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438 \u0432\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c.<\/p>\n<blockquote>\n<p><a href=\"https:\/\/cloud.beeline.ru\/?utm_source=owned_media&amp;utm_medium=habr&amp;utm_campaign=beeline_cloud&amp;utm_content=widget\"><strong>beeline cloud\u00a0<\/strong><\/a>\u2014 secure cloud provider.                                                         \u0420\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0432\u044b \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u043b\u0443\u0447\u0448\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b  <\/p>\n<\/blockquote>\n<\/div>\n<\/div>\n<\/div>\n<p> <!----> <!----><\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/companies\/beeline_cloud\/articles\/742214\/\"> https:\/\/habr.com\/ru\/companies\/beeline_cloud\/articles\/742214\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f\u00a0 \u043f\u0438\u0441\u0430\u043b \u043e \u0442\u043e\u043c, <a href=\"https:\/\/habr.com\/ru\/companies\/beeline_cloud\/articles\/734164\/\">\u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c end-to-end \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 telegram-\u0431\u043e\u0442\u0430.<\/a> \u0410 \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432 Postgres \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c SQLAlchemy \u0438 \u043a\u0430\u043a \u0435\u0433\u043e \u043f\u0440\u043e\u0438\u043d\u0434\u0435\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u0442\u044c.\u00a0<\/p>\n<p>\u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041c\u0438\u0445\u0430\u0438\u043b \u0412\u044b\u0431\u043e\u0440\u043d\u044b\u0439, \u044f python-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, backend-developer \u0432 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u043c \u043f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440\u0435 <a href=\"https:\/\/cloud.beeline.ru\/?utm_source=owned_media&amp;utm_medium=habr&amp;utm_campaign=beeline_cloud&amp;utm_term=polnotekstoviy_poisk_v_PostgreSQL_s_SQLAlchemy\">beeline cloud<\/a>. \u0412\u044b \u0443\u0437\u043d\u0430\u0435\u0442\u0435, \u043a\u0430\u043a \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0444\u0430\u0439\u043b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f alembic. \u0412 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439, \u0433\u0434\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0441\u0435\u0440\u0432\u0438\u0441-\u043a\u043b\u0430\u0441\u0441 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u0432 \u0441\u0432\u043e\u0439 \u043f\u0440\u043e\u0435\u043a\u0442.\u00a0<\/p>\n<h2>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0422\u0435\u043c\u0430 \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 (Full Text Search, FTS) \u0443\u0436\u0435 \u043d\u0435 \u0440\u0430\u0437 \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u043b\u0430\u0441\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0430. \u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043d\u0435 \u043e\u0431\u043e\u0439\u0442\u0438\u0441\u044c \u0431\u0435\u0437 Sphinx \u0438\u043b\u0438 Elasticsearch, \u043d\u043e \u0435\u0441\u043b\u0438 \u043f\u043e\u0438\u0441\u043a \u0432 \u0432\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0431\u043e\u043b\u044c\u0448\u0435 \u0444\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u2013 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c FTS \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u0432 \u0411\u0414. \u041d\u0435 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441 \u0438 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0432 \u043d\u0435\u043c \u043a\u043e\u043d\u0441\u0438\u0441\u0442\u0435\u043d\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439.<\/p>\n<p>Postgres \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 FTS.<\/p>\n<ul>\n<li>\n<p>\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u044b LIKE \/ ILIKE<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0438\u0441\u043a \u0441\u0442\u0440\u043e\u043a\u0438 ts_query \u043f\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0443 ts_vector<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 pg_trgm: \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c (Trigram \/ Trigraph)<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u043e LIKE \/ ILIKE \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0438\u043d\u0434\u0435\u043a\u0441\u044b \u0438 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0430 ts_query \/ ts_vector \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0442\u043e\u043d\u043a\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u0435\u0439 \u0438 \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044f\u0437\u044b\u043a\u043e\u0432. \u0421\u0430\u043c\u044b\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c.<\/p>\n<p>\u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430 \u2014 \u044d\u0442\u043e \u0433\u0440\u0443\u043f\u043f\u0430 \u0442\u0440\u0435\u0445 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432, \u0432\u0437\u044f\u0442\u044b\u0445 \u0438\u0437 \u0441\u0442\u0440\u043e\u043a\u0438. \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u044c \u0441\u0445\u043e\u0436\u0435\u0441\u0442\u044c \u0434\u0432\u0443\u0445 \u0441\u0442\u0440\u043e\u043a, \u043f\u043e\u0434\u0441\u0447\u0438\u0442\u0430\u0432 \u0447\u0438\u0441\u043b\u043e \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0435\u0441\u0442\u044c \u0432 \u043e\u0431\u0435\u0438\u0445. \u042d\u0442\u0430 \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0438\u0434\u0435\u044f \u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0447\u0435\u043d\u044c \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0439 \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0441\u0445\u043e\u0436\u0435\u0441\u0442\u0438 \u0441\u043b\u043e\u0432 \u043d\u0430 \u043c\u043d\u043e\u0433\u0438\u0445 \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u044f\u0437\u044b\u043a\u0430\u0445.<\/p>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c pg_trgm \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Approximate_string_matching\">Fuzzy Text Search<\/a> (\u043d\u0435\u0447\u0435\u0442\u043a\u0438\u0439 \u043f\u043e\u0438\u0441\u043a \u0441 <em>\u043e\u043f\u0435\u0447\u0430\u0442\u043a\u0430\u043c\u0438<\/em>) \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0438 \u043d\u0430 \u0420\u0443\u0441\u0441\u043a\u043e\u043c \u044f\u0437\u044b\u043a\u0435.<\/p>\n<h2>\u041f\u043e\u0438\u0441\u043a pg_trgm \u0432 \u0441\u0432\u044f\u0437\u043a\u0435 \u0441 SQLAlchemy<\/h2>\n<h3>\u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u0430 \u043a \u043f\u043e\u0438\u0441\u043a\u0443<\/h3>\n<ul>\n<li>\n<p>\u0414\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0442\u044b \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0442\u0430\u043a\u043e\u0439 \u0442\u0430\u0431\u043b\u0438\u0446\u0435.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">class Base(MappedAsDataclass, DeclarativeBase): id: Mapped[UUID] = mapped_column(primary_key=True, init=False, default_factory=uuid.uuid4)  class Article(Base, kw_only=True): __tablename__ = 'articles'  title: Mapped[str | None] body: Mapped[str | None]<\/code><\/pre>\n<ul>\n<li>\n<p>\u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(text('CREATE EXTENSION IF NOT EXISTS pg_trgm'))<\/code><\/pre>\n<ul>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c.  <\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.add( Article( title='Full Text Search',  body='Full Text Search in PostgreSQL with SQLAlchemy. ' ) )<\/code><\/pre>\n<ul>\n<li>\n<p>\u0414\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u043b\u0435\u0437\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043a\u0430\u043a\u0438\u0435 \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0441\u044f \u0432 \u0437\u0430\u043f\u0438\u0441\u044f\u0445.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(select(func.show_trgm(Article.title))).all()  # \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u044b \u0434\u043b\u044f \u043f\u043e\u043b\u044f title ['  f', '  s', '  t', ' fu', ' se', ' te', 'arc', 'ch ', 'ear',   'ext', 'ful', 'll ', 'rch', 'sea', 'tex', 'ull', 'xt ']<\/code><\/pre>\n<ul>\n<li>\n<p>\u0422\u0430\u043a \u0436\u0435 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c similarity_ratio \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430. \u042d\u0442\u043e\u0442 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430 <em>\u0441\u0445\u043e\u0434\u0441\u0442\u0432\u043e<\/em> \u0434\u0432\u0443\u0445 \u0441\u0442\u0440\u043e\u043a \u043c\u0435\u0436\u0434\u0443 \u0441\u043e\u0431\u043e\u0439.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(select(func.similarity(Article.title, 'search string')).all()<\/code><\/pre>\n<ul>\n<li>\n<p>\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u0443\u044e \u0440\u0430\u0437\u0434\u0430\u0447\u0443 \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0442 \u0432\u0441\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u0441 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442\u043e\u043c similarity \u0431\u043e\u043b\u044c\u0448\u0435 0.3. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0431\u043e\u043b\u0435\u0435 \u043c\u044f\u0433\u043a\u0438\u0439 \u043b\u0438\u043c\u0438\u0442 \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0441\u0435\u0441\u0441\u0438\u0438.<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"python\">session.execute(select(func.set_limit('0.01')))<\/code><\/pre>\n<h3>\u041f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441<\/h3>\n<p>\u0414\u043b\u044f \u043f\u043e\u043b\u043d\u043e\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430 SQLAlchemy \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u0443\u043d\u0438\u0432\u0435\u0440\u0441\u0430\u043b\u044c\u043d\u044b\u0439 \u043c\u0435\u0442\u043e\u0434 <a href=\"https:\/\/docs.sqlalchemy.org\/en\/20\/dialects\/postgresql.html#full-text-search\">match<\/a> . \u0414\u043b\u044f Postgres \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 @@, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u043f\u043e\u0438\u0441\u043a ts_query \u043f\u043e ts_vector.  <\/p>\n<pre><code class=\"sql\">select(Article.body.match('search string')) # \u043f\u043e\u0438\u0441\u043a ts_query \u043f\u043e ts_vector<\/code><\/pre>\n<p>\u041d\u043e \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u0432 \u0410\u043b\u0445\u0438\u043c\u0438\u0438 \u043d\u0435\u0442, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u0443\u043b\u0435\u0432\u044b\u0439 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 pg_trgm: <em>\u0437\u043d\u0430\u043a \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u0430<\/em>.<\/p>\n<pre><code class=\"python\">Article.body.bool_op('%')('search string')<\/code><\/pre>\n<p>\u0426\u0435\u043b\u0438\u043a\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c.<\/p>\n<pre><code class=\"python\">term = 'search string' select( Article.body,  # \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0432 \u0432\u044b\u0434\u0430\u0447\u0443 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043e\u0442\u043b\u0430\u0434\u043a\u0438 func.similarity(Article.body, term),  ) .where( # \u0432\u0441\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0431\u043e\u043b\u044c\u0448\u0435 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043b\u0438\u043c\u0438\u0442\u0430 Article.body.bool_op('%')(term), ) .order_by( # \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442\u0443 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f func.similarity(Article.body, term).desc(),  )<\/code><\/pre>\n<h3>\u041f\u043e\u0438\u0441\u043a \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u043c<\/h3>\n<p>\u0412\u043d\u0438\u043c\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u044c \u0437\u0430\u043c\u0435\u0442\u0438\u043b, \u0447\u0442\u043e \u043c\u044b \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b\u0438 \u043f\u043e\u0438\u0441\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e \u043e\u0434\u043d\u043e\u0439 \u043a\u043e\u043b\u043e\u043d\u043a\u0435 body. \u0414\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u043e\u043b\u044f\u043c \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u043e\u043c concat.<\/p>\n<pre><code class=\"python\">columns = func.coalesce(Article.title, '').concat(func.coalesce(Article.body, '')) columns = columns.self_group() # \u043e\u0431\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u0442 \u043a\u043e\u043d\u043a\u0430\u0442\u0438\u043d\u0430\u0446\u0438\u044e \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432 \u0432 \"\u0441\u043a\u043e\u0431\u043a\u0438\"<\/code><\/pre>\n<p>? \u041f\u043e\u043c\u0438\u043c\u043e concat, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u0435\u0440\u043d\u0443\u0442\u044c \u043a\u0430\u0436\u0434\u043e\u0435 \u043f\u043e\u043b\u0435 \u0432 coalesce, \u0447\u0442\u043e\u0431\u044b \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f None \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u043f\u043e\u043b\u0435\u0439, \u043c\u044b \u043d\u0435 \u0442\u0435\u0440\u044f\u043b\u0438 \u0437\u0430\u043f\u0438\u0441\u044c \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u041f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043f\u0440\u043e concat \u0438 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 || \u0432 Postgres \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c <a href=\"https:\/\/www.postgresqltutorial.com\/postgresql-string-functions\/postgresql-concat-function\/\">\u0442\u0443\u0442<\/a>.<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u043e\u043b\u044f\u043c.<\/p>\n<pre><code class=\"python\">term = 'search string' select( Article.body, Article.title, func.similarity(columns, term),  # \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043a\u043e\u043d\u043a\u0430\u0442\u0435\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0441\u0442\u043e\u043b\u0431\u0446\u044b ) .where( columns.bool_op('%')(term),      # \u0437\u0434\u0435\u0441\u044c \u0442\u043e\u0436\u0435 ) .order_by( func.similarity(columns, term).desc(),  # \u0438 \u0437\u0434\u0435\u0441\u044c )<\/code><\/pre>\n<h3>\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438 pg_trgm<\/h3>\n<p>\u041d\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442 \u0437\u0430\u0431\u044b\u0432\u0430\u0442\u044c \u0438 \u043e \u043c\u0438\u043d\u0443\u0441\u0430\u0445 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u044f. \u0414\u043b\u044f \u043c\u0435\u043d\u044f \u044d\u0442\u043e \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435 \u0440\u0430\u043d\u0436\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u0439 \u0432\u044b\u0434\u0430\u0447\u0438. \u041a \u043f\u0440\u0438\u043c\u0435\u0440\u0443 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u0444\u0440\u0430\u0437\u044b &#8216;Full Text Search&#8217; \u043f\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0441\u0442\u0430\u0442\u044c\u044f\u043c:<\/p>\n<pre><code class=\"python\">Article(title='Full Text Search. ', body='Full Text Search in PostgreSQL with SQLAlchemy. '), Article(title='Full Text Search. ', body=None)<\/code><\/pre>\n<p>\u041c\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u044d\u0442\u043e\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<\/p>\n<pre><code class=\"python\">('Full Text Search. ', None, 0.4166666567325592), ('Full Text Search. ', 'Full Text Search in PostgreSQL with SQLAlchemy. ', 0.22727273404598236),<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0444\u0440\u0430\u0437\u0430 &#8216;Full Text Search&#8217; \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442\u0441\u044f \u0434\u0432\u0430\u0436\u0434\u044b, \u043d\u043e \u043e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430 \u043c\u0435\u043d\u044c\u0448\u0438\u0439 similarity_ratio. \u042d\u0442\u043e \u0432\u044b\u0437\u0432\u0430\u043d\u043e \u0442\u0435\u043c, \u0447\u0442\u043e \u0432 \u043f\u0435\u0440\u0432\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u043f\u043e\u043b\u044f text \u0438 body \u0438\u043c\u0435\u044e\u0442 \u0431\u043e\u043b\u044c\u0448\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432, \u0430 \u0437\u043d\u0430\u0447\u0438\u0442 \u0431\u043e\u043b\u044c\u0448\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0440\u0438\u0433\u0440\u0430\u043c\u043c \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435, \u0447\u0442\u043e \u043f\u043e\u043d\u0438\u0436\u0430\u0435\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c <em>&#171;\u0443\u043c\u043d\u044b\u0439&#187;<\/em> \u043f\u043e\u0438\u0441\u043a, \u0433\u0434\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0440\u0430\u0437\u043d\u044b\u0439 \u0432\u0435\u0441 \u0434\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 (\u043a\u043e\u0433\u0434\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0432 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0435 \u0446\u0435\u043d\u0438\u0442\u0441\u044f \u0431\u043e\u043b\u044c\u0448\u0435, \u0447\u0435\u043c \u0432 \u0442\u0435\u043b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438), \u0440\u0435\u0433\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0443\u0434\u0430\u043b\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u0441\u043b\u043e\u0432 \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430, \u0442\u043e \u0441\u0442\u043e\u0438\u0442 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u0443 \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e ts_query \/ ts_vector. \u0422\u0430\u043a \u0436\u0435 \u0441\u043e\u0432\u0435\u0442\u0443\u044e \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0438\u043d\u0434\u0435\u043a\u0441 <a href=\"https:\/\/github.com\/postgrespro\/rum\">RUM<\/a> \u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u044c <a href=\"https:\/\/github.com\/postgrespro\/hunspell_dicts\">hunspell<\/a> \u043e\u0442 Postgres PRO \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0438\u0441\u043a\u0430.<\/p>\n<h2>\u0418\u043d\u0434\u0435\u043a\u0441<\/h2>\n<h3>\u041f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/h3>\n<p>Postgres \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u044b, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u043c \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u0430 \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c. \u0412 SQLAlchemy \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c.<\/p>\n<pre><code class=\"sql\">Index( 'article_title_body_trgm_idx',  # \u0438\u043d\u0434\u0435\u043a\u0441 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e \u0432\u044b\u0437\u043e\u0432\u0443 \u0442\u043e\u0447\u043d\u043e \u0442\u0430\u043a\u043e\u0439 \u0436\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438, # \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0430 \u043f\u0440\u0438 \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u043e\u043c \u0437\u0430\u043f\u0440\u043e\u0441\u0435 func.coalesce(Article.title, '').concat(func.coalesce(Article.body, '')).label('columns'),  # \u0442\u0430\u043a \u0436\u0435 \u0434\u043b\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u0430 GIN \u043d\u0443\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 gin_trgm_ops,  # \u0442\u0430\u043a Postgres \u043f\u043e\u0439\u043c\u0435\u0442,\u0447\u0442\u043e \u0438\u043d\u0434\u0435\u043a\u0441 \u043d\u0443\u0436\u043d\u043e \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e \u0422\u0440\u0438\u0433\u0440\u0430\u043c\u043c\u0430\u043c postgresql_using='gin', postgresql_ops={ 'columns': 'gin_trgm_ops', }, )<\/code><\/pre>\n<h3>\u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u043d\u0430 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043e\u0431\u044a\u0435\u043c\u0430\u0445, \u043c\u043e\u0436\u043d\u043e <em>\u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c<\/em> Postgres \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c. \u041d\u043e \u043d\u0435 \u0441\u0442\u043e\u0438\u0442 \u044d\u0442\u043e\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0432 \u043f\u0440\u043e\u0434\u0430\u043a\u0448\u0435\u043d\u0435, \u0442\u0430\u043a \u043a\u0430\u043a Postgres \u0438\u043c\u0435\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b \u0434\u043b\u044f \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0430\u0446\u0438\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u0438\u043d\u043e\u0433\u0434\u0430 <em>\u0434\u0435\u0448\u0435\u0432\u043b\u0435<\/em> \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c SeqScan, \u0434\u0430\u0436\u0435 \u043f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430.<\/p>\n<pre><code class=\"python\">session.execute(text('SET enable_seqscan = OFF')) # \u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u0435\u043c \u0432\u044b\u0432\u0435\u0441\u0442\u0438 Query Plan. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u043a\u043b\u0430\u0441\u0441\u043e\u043c explain \u0438\u0437 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 Wiki \u0410\u043b\u0445\u0438\u043c\u0438\u0438: <a href=\"https:\/\/github.com\/sqlalchemy\/sqlalchemy\/wiki\/Query-Plan-SQL-construct\">Query Plan SQL construct<\/a>.<\/p>\n<pre><code class=\"sql\">statement = select(...) session.execute(explain(statement)).scalars() Sort  (cost=42.40..42.42 rows=8 width=68)   Sort Key: (similarity(((COALESCE(title, ''::character varying))::text || (COALESCE(body, ''::character varying))::text), '21345'::text)) DESC   ->  Bitmap Heap Scan on article  (cost=32.07..42.28 rows=8 width=68)         Recheck Cond: (((COALESCE(title, ''::character varying))::text || (COALESCE(body, ''::character varying))::text) % '21345'::text)         ->  Bitmap Index Scan on article_title_body_trgm_idx  (cost=0.00..32.06 rows=8 width=0)               Index Cond: (((COALESCE(title, ''::character varying))::text || (COALESCE(body, ''::character varying))::text) % '21345'::text) <\/code><\/pre>\n<p>\u041a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c \u0438\u0437 \u043f\u043b\u0430\u043d\u0430, \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e\u0439\u0434\u0435\u0442 \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443: Bitmap Index Scan. \u0415\u0441\u043b\u0438 \u043f\u0440\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0431\u0443\u0434\u0435\u0442 \u0434\u0440\u0443\u0433\u0430\u044f \u043a\u043e\u043d\u043a\u0430\u0442\u0435\u043d\u0430\u0446\u0438\u044f \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432, Postgres \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u0442 \u0437\u0430\u043f\u0440\u043e\u0441 \u0447\u0435\u0440\u0435\u0437 Seq Scan.<\/p>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0441\u0430\u043c\u0438.<\/p>\n<pre><code class=\"python\"># \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0432\u0441\u0435\u0445 \u0442\u0430\u0431\u043b\u0438\u0446 Base.metadata.create_all(bind=engine)  # \u0438\u043b\u0438 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e index = Index('article_title_body_trgm_idx', ...) index.create(engine)<\/code><\/pre>\n<p>\u041d\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0443\u0434\u043e\u0431\u043d\u043e \u0434\u043b\u044f \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0439 \u0441\u0445\u0435\u043c\u044b \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c Alembic. \u0412 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043b\u0443\u0447\u0448\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438\u043d\u0434\u0435\u043a\u0441\u0430 \u0432 \u0440\u0435\u0432\u0438\u0437\u0438\u044e.<\/p>\n<pre><code class=\"python\">index = Index('article_title_body_trgm_idx', ...) ...  # \u0444\u0430\u0439\u043b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 def upgrade() -> None: op.create_table('article', ...) op.create_index( index.name, index.table.name, index.expressions, postgresql_using='gin', postgresql_ops={'columns': 'gin_trgm_ops'}, )    def downgrade() -> None: op.drop_index( 'article_title_body_trgm_idx', table_name='article', postgresql_using='gin', postgresql_ops={'columns': 'gin_trgm_ops'}, ) op.drop_table('article') ...<\/code><\/pre>\n<p>? \u041f\u0440\u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0444\u0430\u0439\u043b \u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 alembic revision &#8212;autogenerate \u0438\u043d\u0434\u0435\u043a\u0441 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u043d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0433\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e.<\/p>\n<h2>\u041f\u043e\u0438\u0441\u043a \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c<\/h2>\n<p>\u041d\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438, \u043a\u0430\u043a \u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 pg_trgm \u0434\u043b\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0441\u0442\u043e\u043b\u0431\u0446\u043e\u0432 <em>\u043e\u0434\u043d\u043e\u0439<\/em> \u0442\u0430\u0431\u043b\u0438\u0446\u044b. \u041d\u043e, \u0437\u0430\u0447\u0430\u0441\u0442\u0443\u044e, \u043b\u043e\u0433\u0438\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0438\u0441\u043a\u0430\u0442\u044c \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u043c \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c \u0441\u0440\u0430\u0437\u0443. \u041f\u0440\u0435\u0434\u043f\u043e\u043b\u043e\u0436\u0438\u043c, \u043d\u0430\u0448\u0430 \u0441\u0445\u0435\u043c\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a.<\/p>\n<pre><code class=\"python\">class Author(Base, kw_only=True): __tablename__ = 'authors'  username: Mapped[str] first_name: Mapped[str | None] last_name: Mapped[str | None]  # relations: articles: Mapped[list[Article]] = relationship( back_populates='author', default_factory=list, )  class Article(Base, kw_only=True): __tablename__ = 'articles'  title: Mapped[str | None] body: Mapped[str | None]  # relations: author_id: Mapped[uuid.UUID] = mapped_column(ForeignKey(Author.id), init=False, repr=False) author: Mapped[Author] = relationship( back_populates='articles', init=False, )<\/code><\/pre>\n<p>\u0415\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0447\u0442\u043e \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0438\u0441\u043a\u0430\u0442\u044c \u0441\u0440\u0430\u0437\u0443 \u0438 \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u0430\u0432\u0442\u043e\u0440\u0430 \u0438 \u043f\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044e \u0441\u0442\u0430\u0442\u044c\u0438. \u041f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0432 \u0432\u044b\u0434\u0430\u0447\u0443 \u0432\u0441\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0430\u0432\u0442\u043e\u0440\u0430, \u0435\u0441\u043b\u0438 \u0441\u043e\u0432\u043f\u0430\u043b\u043e \u0435\u0433\u043e \u0438\u043c\u044f \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0430\u0432\u0442\u043e\u0440\u043e\u0432, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u044d\u0442\u043e \u0436\u0435 \u0438\u043c\u044f.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c <\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-349029","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/349029","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=349029"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/349029\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=349029"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=349029"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=349029"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}