126

A tags column has values like "apple banana orange" and "strawberry banana lemon". I want to find the SQLAlchemy equivalent statement to

SELECT * FROM table WHERE tags LIKE "%banana%";

What should I pass to Class.query.filter() to do this?

davidism
  • 121,510
  • 29
  • 395
  • 339
Gary Oldfaber
  • 1,582
  • 3
  • 14
  • 19

10 Answers10

263

Each column has like() method, which can be used in query.filter(). Given a search string, add a % character on either side to search as a substring in both directions.

tag = request.form["tag"]
search = "%{}%".format(tag)
posts = Post.query.filter(Post.tags.like(search)).all()
davidism
  • 121,510
  • 29
  • 395
  • 339
Daniel Kluev
  • 11,025
  • 2
  • 36
  • 36
  • 2
    Perfect! Do you know if there's a better way to distinguish between apple and pineapple than adding a leading space? – Gary Oldfaber Jul 24 '10 at 14:44
  • 3
    Best way would be to just normalize your database and add 2 separate tables for tags and tag-to-task relations, and then use JOINs instead of LIKE. Otherwise, yes, seems like you will have to have some kind of separator around each tag in the string. Leading space is not enough, since there is also pen and pencil, with %pen%. If you do something like "|apple|pineapple|pen|pencil|" and match "%|pen|%" it should not collide. – Daniel Kluev Jul 24 '10 at 15:02
  • 1
    With normalizing, I'm not quite sure how I'll have more than one tag associated with a given task, or vice versa using the tag map. The "Toxi" solution appears to group the collection of tags as a single item, rather than storing each individually? And the method used in this (http://elixir.ematia.de/trac/wiki/Recipes/TagCloud) recipe appears to only allow one tag per item. What resources would be best for elucidating this topic? I've read this (http://dev.mysql.com/tech-resources/articles/intro-to-normalization.html) too, but can't picture how to manage multiple tags. – Gary Oldfaber Jul 24 '10 at 15:11
  • 2
    As I said, you need two tables. Basically, its just typical Many-to-Many relation, so you can follow SQLAlchemy's guide on it: http://www.sqlalchemy.org/docs/ormtutorial.html#building-a-many-to-many-relationship You will have `tags` table, where you store tag name and other tag info, and you will have `task_tags` table, which will have one record for each tag added to the task. So task with 2 tags will just have 2 records in `task_tags` table. – Daniel Kluev Jul 24 '10 at 15:36
  • Is this safe from SQL-injections? I want to specify this as a user input – Peter Mølgaard Pallesen Mar 29 '22 at 14:56
  • Does it work with a blob data type? My table column is encrypted but it won't work. However, if I just perform Post.query.filter(Post.title == 'My Title') it works. – Seven Nov 07 '22 at 08:37
  • @GaryOldfaber - I know this is some time later.. But 1) putting % at start and end is like doing a contains.. But 2) putting a % solely at the end is like doing a begins with, or 3) a % at the start is like doing an ends with .. So adapt the of use of % symbol to accommodate your requirements. I know this is some years later, but I hope this helps for any future readers. – JGFMK Aug 04 '23 at 07:16
16

Adding to the above answer, whoever looks for a solution, you can also try 'match' operator instead of 'like'. Do not want to be biased but it perfectly worked for me in Postgresql.

Note.query.filter(Note.message.match("%somestr%")).all()

It inherits database functions such as CONTAINS and MATCH. However, it is not available in SQLite.

For more info go Common Filter Operators

Nick
  • 3,172
  • 3
  • 37
  • 49
igsm
  • 1,280
  • 2
  • 17
  • 19
  • 10
    What is the difference between these two operators? – buhtz Sep 06 '16 at 13:09
  • @buhtz depends on your DB backend see SQL-Alchemy docs: https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.ColumnOperators.match In Postgres you get `to_tsquery` which lets you add text operators for things like `OR` and `AND` https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES – Nick Nov 03 '20 at 19:29
  • 1
    FWIW: I found that `match` did not fully support partial string matches (eg `abc%` matching `abcd`) where `like` does. – Nick Feb 11 '21 at 23:21
13

Try this code:

output = dbsession.query(<model_class>).filter(
    <model_class>.email.ilike("%" + <email> + "%")
)
swimmer
  • 1,971
  • 2
  • 17
  • 28
waruna k
  • 842
  • 10
  • 20
13

In case you want the case insensitive like clause implementation:

session.query(TableName).filter(TableName.colName.ilike(f'%{search_text}%')).all()
Syscall
  • 19,327
  • 10
  • 37
  • 52
Prason Ghimire
  • 403
  • 4
  • 4
3

In SQLAlchemy 1.4/2.0:

q = session.query(User).filter(User.name.like('e%'))

collinsmarra
  • 148
  • 1
  • 7
2

If you use native sql, you can refer to my code, otherwise just ignore my answer.

SELECT * FROM table WHERE tags LIKE "%banana%";
from sqlalchemy import text

bar_tags = "banana"

# '%' attention to spaces
query_sql = """SELECT * FROM table WHERE tags LIKE '%' :bar_tags '%'"""

# db is sqlalchemy session object
tags_res_list = db.execute(text(query_sql), {"bar_tags": bar_tags}).fetchall()

Toby
  • 89
  • 4
0

Using PostgreSQL like (see accepted answer above) somehow didn't work for me although cases matched, but ilike (case insensisitive like) does.

koks der drache
  • 1,398
  • 1
  • 16
  • 33
0

While the accepted answer works fine, "The ORM Query object is a legacy construct as of SQLAlchemy 2.0" (ref: Legacy Query API - SQLAlchemy 2.0 Documentation).

The corresponding SQLAlchemy v2.0 equivalent to the SQL LIKE statement using the select construct is as shown in the python 3.10 code snippet below:

from typing import List

from sqlalchemy import select
from sqlalchemy.orm import Session

...

def get_multi_like_tag_substring_bidirectional(
    db: Session,
    *,
    tags_search_substring: str,
    skip: int = 0,
    limit: int = 10,
) -> List[Post]:
    return db.scalars(
        select(Post)
        .where(Post.tags.like(f"%{tags_search_substring}%"))
        .offset(skip)
        .limit(limit)
    ).all()

...

banana_tagged_posts = get_multi_like_tag_substring_bidirectional(
    db=db_session,
    tags_search_substring = "banana"
)
    
sidd23
  • 21
  • 1
  • 4
0

What i've been using all these years:

model = MyModel
col = "name_of_my_column"
value = "your-value"

query = model.query.filter(getattr(model, col).like("%{}%".format(value))).all()

You are welcome! ;) Well it seems to me bay bay that's I don't know wtf I'm doing huh

-3

Doing it like this worked for me (Oracle) as native sql

"SELECT * FROM table WHERE tags LIKE '%' || :bar_tags || '%' "
cpunz
  • 5
  • 2