[Django]-Django postgres full text search in reverse related models

7👍

This is the way:

  1. Be sure you are working with django -gte 1.10
  2. Be sure you have 'django.contrib.postgres',on your INSTALLED_APPS
  3. Create your both models as in your question.
  4. Just makemigrations, migrate and populate models with some data:

Populating models with data:

from fts.models import Item, Container    
c=Container.objects.create( text = "hello" )  
Item.objects.create( text ="Some word", container = c ) 
  1. At this point you are ready to make the query:

Querying and checking results:

from django.contrib.postgres.search import SearchVector
>>> ( Container
...       .objects
...       .annotate(search=SearchVector('text', 'item__text'),)
...       .filter(search='Some word')
...       .distinct()
...      )

Results as expected:

<QuerySet [<Container: Container object>]>
  1. Just to be sure your query is working using full search postgres capabilities, you can print the underlying sql:

Asking for underlying SQL:

>>> print ( Container
            .objects
            .annotate(search=SearchVector('text', 'item__text'),)
            .filter(search='Some word')
          ).query

And the result is:

SELECT 
    "fts_container".
    "id", "fts_container".
    "text", 
    to_tsvector(COALESCE("fts_container"."text", ) 
                || ' ' || 
                COALESCE("fts_item"."text", )) AS "search"
FROM            
    "fts_container"
LEFT OUTER JOIN 
    "fts_item"
          ON("fts_container"."id" = "fts_item"."container_id") 
WHERE to_tsvector(
        COALESCE("fts_container"."text", ) 
        || ' ' || 
        COALESCE("fts_item"."text", )
      )@@(plainto_tsquery(Some word)) = true

In action:

django and postgres fts

Performance:

I don’t know if postgres is able to take advantage from index on full search capabilities when you are mixing fields from several tables. But is easy to check it. After create full text indexes and ANALYZE your tables you can ask about sql plan:

fts=> EXPLAIN SELECT 
fts->     "fts_container".
fts->     "id", "fts_container".
fts->     "text", 
fts->     to_tsvector(COALESCE("fts_container"."text", '' ) 
fts(>                 || ' ' || 
fts(>                 COALESCE("fts_item"."text", '' )) AS "search"
fts-> FROM            
fts->     "fts_container"
fts-> LEFT OUTER JOIN 
fts->     "fts_item"
fts->           ON("fts_container"."id" = "fts_item"."container_id") 
fts-> WHERE to_tsvector(
fts(>         COALESCE("fts_container"."text", '' ) 
fts(>         || ' ' || 
fts(>         COALESCE("fts_item"."text",'' )
fts(>       )@@(plainto_tsquery('Some word')) = true
fts-> ;
                                                                         QUERY PLAN                                                                          
-------------------------------------------------------------------------------------------------------------------------------------------------------------
 Hash Right Join  (cost=1.04..2.15 rows=1 width=68)
   Hash Cond: (fts_item.container_id = fts_container.id)
   Filter: (to_tsvector(((COALESCE(fts_container.text, ''::text) || ' '::text) || COALESCE(fts_item.text, ''::text))) @@ plainto_tsquery('Some word'::text))
   ->  Seq Scan on fts_item  (cost=0.00..1.04 rows=4 width=36)
   ->  Hash  (cost=1.02..1.02 rows=2 width=36)
         ->  Seq Scan on fts_container  (cost=0.00..1.02 rows=2 width=36)
(6 rows)

Leave a comment