--- title: Filtering description: Filter search results based on metadata from other fields canonical: https://docs.paradedb.com/documentation/filtering --- Adding filters to text search is as simple as using PostgreSQL's built-in `WHERE` clauses and operators. For instance, the following query filters out results that do not meet `rating > 2`. ```sql SQL SELECT description, rating, category FROM mock_items WHERE description ||| 'running shoes' AND rating > 2; ``` ```python Django from paradedb import Match, ParadeDB MockItem.objects.filter( description=ParadeDB(Match('running shoes', operator='OR')), rating__gt=2 ).values('description', 'rating', 'category') ``` ```ruby Rails MockItem.search(:description) .matching_any("running shoes") .where(rating: 3..) .select(:description, :rating, :category) ``` ## Filter Pushdown ### Non-Text Fields While not required, filtering performance over non-text columns can be improved by including them in the BM25 index. When these columns are part of the index, `WHERE` clauses that reference them can be pushed down into the index scan itself. This can result in faster query execution over large datasets. For example, if `rating` and `created_at` are frequently used in filters, they can be added to the BM25 index during index creation: ```sql CREATE INDEX search_idx ON mock_items USING bm25(id, description, rating, created_at) WITH (key_field = 'id'); ``` Filter pushdown is currently supported for the following combinations of types and operators: | Operator | Left Operand Type | Right Operand Type | Example | | ------------------------------------------ | ----------------- | ------------------ | -------------------------- | | `=`, `<`, `>`, `<=`, `>=`, `<>`, `BETWEEN` | `int2` | `int2` | `WHERE rating = 2` | | | `int4` | `int4` | | | `int8` | `int8` | | | `int2` | `int4` | | | `int2` | `int8` | | | `int4` | `int8` | | | `float4` | `float4` | | | `float8` | `float8` | | | `float4` | `float8` | | | `numeric` | `numeric` | `WHERE price = 99.99` | | | `date` | `date` | | | `time` | `time` | | | `timetz` | `timetz` | | | `timestamp` | `timestamp` | | | `timestamptz` | `timestamptz` | | | `uuid` | `uuid` | | `=` | `bool` | `bool` | `WHERE in_stock = true` | | `IN`, `ANY`, `ALL` | `bool` | `bool[]` | `WHERE rating IN (1,2,3)` | | | `int2` | `int2[]` | | | `int4` | `int4[]` | | | `int8` | `int8[]` | | | `int2` | `int4[]` | | | `int2` | `int8[]` | | | `int4` | `int8[]` | | | `float4` | `float4[]` | | | `float8` | `float8[]` | | | `float4` | `float8[]` | | | `date` | `date[]` | | | `timetz` | `timetz[]` | | | `timestamp` | `timestamp[]` | | | `timestamptz` | `timestamptz[]` | | | `uuid` | `uuid[]` | | `IS`, `IS NOT` | `bool` | `bool` | `WHERE in_stock IS true` | | `IS NULL`, `IS NOT NULL` | `bool` | | `WHERE rating IS NOT NULL` | | | `int2` | | | | `int4` | | | | `int8` | | | | `int2` | | | | `int2` | | | | `int4` | | | | `float4` | | | | `float8` | | | | `float4` | | | | `date` | | | | `time` | | | | `timetz` | | | | `timestamp` | | | | `timestamptz` | | | | `uuid` | | ### Text Fields Suppose we have a text filter that looks for an exact string match like `category = 'Footwear'`: ```sql SQL SELECT description, rating, category FROM mock_items WHERE description @@@ 'shoes' AND category = 'Footwear'; ``` ```python Django from paradedb import ParadeDB, Term MockItem.objects.filter( description=ParadeDB(Term('shoes')), category='Footwear' ).values('description', 'rating', 'category') ``` ```ruby Rails MockItem.search(:description) .term("shoes") .where(category: "Footwear") .select(:description, :rating, :category) ``` To push down the `category = 'Footwear'` filter, `category` must be indexed using the [literal](/documentation/tokenizers/available-tokenizers/literal) tokenizer: ```sql CREATE INDEX search_idx ON mock_items USING bm25(id, description, (category::pdb.literal)) WITH (key_field = 'id'); ``` Pushdown of set filters over text fields also requires the literal tokenizer: ```sql SQL SELECT description, rating, category FROM mock_items WHERE description @@@ 'shoes' AND category IN ('Footwear', 'Apparel'); ``` ```python Django from paradedb import ParadeDB, Term MockItem.objects.filter( description=ParadeDB(Term('shoes')), category__in=['Footwear', 'Apparel'] ).values('description', 'rating', 'category') ``` ```ruby Rails MockItem.search(:description) .term("shoes") .where(category: ["Footwear", "Apparel"]) .select(:description, :rating, :category) ```