通过 SQLAlchemy 正确使用 MySQL 全文搜索

问题描述 投票:0回答:2

我希望能够在我的 SQLAlchemy 映射对象之一的多个文本字段中进行全文搜索。我还希望我的映射对象支持外键和事务。

我打算使用MySQL来运行全文搜索。但是,据我所知,MySQL只能在MyISAM表上运行全文搜索,不支持事务和外键。

为了实现我的目标,我计划创建两个表。我的代码看起来像这样:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    description = Column(Text)

users_myisam = Table('users_myisam', Base.metadata,
                     Column('id', Integer),
                     Column('name', String(50)),
                     Column('description', Text),
                     mysql_engine='MyISAM')

conn = Base.metadata.bind.connect()
conn.execute("CREATE FULLTEXT INDEX idx_users_ftxt \
              on users_myisam (name, description)")

然后,为了搜索,我将运行以下命令:

q = 'monkey'
ft_search = users_myisam.select("MATCH (name,description) AGAINST ('%s')" % q)
result = ft_search.execute()
for row in result: print row

这似乎有效,但我有几个问题:

  1. 我创建两个表来解决我的问题的方法合理吗?有没有标准/更好/更干净的方法来做到这一点?

  2. 是否有 SQLAlchemy 方法来创建全文索引,或者我最好像上面那样直接执行“CREATE FULLTEXT INDEX ...”?

  3. 看起来我在搜索/匹配查询时遇到了 SQL 注入问题。我该如何选择“SQLAlchemy 方式”来解决这个问题?

  4. 有没有一种干净的方法将 users_myisam 选择/匹配加入到我的用户表并返回实际的 User 实例,因为这是我真正想要的?

  5. 为了使我的users_myisam表与我的映射对象用户表保持同步,对我来说在我的User类上使用MapperExtension并设置before_insert、before_update和before_delete方法来适当更新users_myisam表是否有意义,或者有更好的方法来实现这个吗?

谢谢, 迈克尔

python mysql full-text-search sqlalchemy
2个回答
16
投票

我创建两个表来解决我的问题的方法合理吗? 有没有标准/更好/更干净的方法来做到这一点?

我以前没有见过这种用例尝试,因为重视事务和约束的开发人员倾向于首先使用 Postgresql。 我知道这在您的具体情况下可能是不可能的。

是否有 SQLAlchemy 方法来创建全文索引,或者我最好 像我上面那样直接执行“CREATE FULLTEXT INDEX ...”?

conn.execute() 很好,但如果你想要更集成的东西,你可以使用 DDL() 构造,通读 http://docs.sqlalchemy.org/en/rel_0_8/core/schema.html?highlight= ddl#customizing-ddl 了解详情

看起来我在搜索/匹配查询时遇到 SQL 注入问题。我该怎么做 选择“SQLAlchemy 方式”来解决这个问题?

注意:此配方仅用于MATCH

同时针对多列 - 如果您只有一列,请更简单地使用 
match() 运算符。

基本上你可以使用 text() 结构:

from sqlalchemy import text, bindparam users_myisam.select( text("MATCH (name,description) AGAINST (:value)", bindparams=[bindparam('value', q)]) )

您可以更全面地定义自定义构造:

from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql.expression import ClauseElement from sqlalchemy import literal class Match(ClauseElement): def __init__(self, columns, value): self.columns = columns self.value = literal(value) @compiles(Match) def _match(element, compiler, **kw): return "MATCH (%s) AGAINST (%s)" % ( ", ".join(compiler.process(c, **kw) for c in element.columns), compiler.process(element.value) ) my_table.select(Match([my_table.c.a, my_table.c.b], "some value"))

文档:

http://docs.sqlalchemy.org/en/rel_0_8/core/compiler.html

有没有一种干净的方法来加入 users_myisam 选择/匹配右后卫 到我的用户表并返回实际的用户实例,因为这是我真正想要的?

您可能应该创建一个 UserMyISAM 类,像 User 一样映射它,然后使用关系()将两个类链接在一起,然后可以进行像这样的简单操作:

query(User).join(User.search_table).\ filter(Match([UserSearch.x, UserSearch.y], "some value"))

为了使我的 users_myisam 表与我的映射对象保持同步 user 表,对我来说在我的表上使用 MapperExtension 有意义吗? 用户类,并设置before_insert、before_update和 before_delete 方法适当更新 users_myisam 表, 或者有更好的方法来实现这一点吗?

MapperExtensions 已弃用,因此您至少需要使用

event API,并且在大多数情况下,我们希望尝试在刷新过程之外应用对象突变。 在这种情况下,我将使用 User 的构造函数,或者使用 init 事件,以及基本的 @validates 装饰器,它将接收 User 上目标属性的值并将这些值复制到 User.search_table

 .

总的来说,如果您从其他来源(例如 Oreilly 的书)学习 SQLAlchemy,它确实已经过时很多年了,我会专注于当前的在线文档。


0
投票
回答问题

是否有 SQLAlchemy 方法来创建全文索引,或者我最好像上面那样直接执行“CREATE FULLTEXT INDEX ...”?

是的,如果您已经定义了

users_myisam

声明式,就像您在问题中对 users
 表所做的那样,您可以通过 SQLAlchemy 创建全文索引。

通过 SQLAlchemy 的全文索引:

from sqlalchemy.schema import Index Index("idx_users_ftxt", UsersMyisam.name, UsersMyisam.description, mysql_prefix="FULLTEXT")
    
© www.soinside.com 2019 - 2024. All rights reserved.