使用SQLAlchemy在PostgreSQL上创建全文搜索索引

25

我需要用SQLAlchemy在Python中创建一个PostgreSQL全文搜索索引。以下是我想要的SQL代码:

CREATE TABLE person ( id INTEGER PRIMARY KEY, name TEXT );
CREATE INDEX person_idx ON person USING GIN (to_tsvector('simple', name));

现在我该如何在使用ORM时使用SQLAlchemy完成第二部分:

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
5个回答

36
你可以使用__table_args__中的Index来创建索引。另外,我使用一个函数来创建ts_vector,以使其更整洁和可重用,如果需要多个字段的话。类似下面这样:
from sqlalchemy.dialects import postgresql
from sqlalchemy.sql import func

def create_tsvector(*args):
    exp = args[0]
    for e in args[1:]:
        exp += ' ' + e
    return func.to_tsvector('english', exp)

class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)

    __ts_vector__ = create_tsvector(
        cast(func.coalesce(name, ''), postgresql.TEXT)
    )

    __table_args__ = (
        Index(
            'idx_person_fts',
            __ts_vector__,
            postgresql_using='gin'
        )
    )

更新: 根据评论修正后的示例查询:
people = Person.query.filter(Person.__ts_vector__.match(expressions, postgresql_regconfig='english')).all()

你能举个例子来说明如何查询向量吗?谢谢。 - Jakob Kristensen
1
@sharez 最新版本中,query.all() 报错:BinaryExpression 对象和 Comparator 对象都没有属性 all。你能提供替代方法吗? - apaleja
2
@apaleja match是一个操作符,因此它应该在filter方法内部,如下所示:Person.query.filter(Person.__ts_vector__.match(expressions, postgresql_regconfig='english')).all() - Irmak Cakmak

21

来自@sharez的回答非常有用(特别是如果您需要在索引中连接列)。对于想要在单个列上创建tsvector GIN索引的任何人,您可以通过以下方式简化原始回答:

from sqlalchemy import Column, Index, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func


Base = declarative_base()

class Example(Base):
    __tablename__ = 'examples'

    id = Column(Integer, primary_key=True)
    textsearch = Column(String)

    __table_args__ = (
        Index(
            'ix_examples_tsv',
            func.to_tsvector('english', textsearch),
            postgresql_using='gin'
            ),
        )

请注意,在__table_args__中的Index(...)后面的逗号不是样式选择,__table_args__的值必须是元组、字典或None
如果您确实需要在多个列上创建tsvector GIN索引,则可以使用text()另一种方法来实现。
from sqlalchemy import Column, Index, Integer, String, text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func


Base = declarative_base()

def to_tsvector_ix(*columns):
    s = " || ' ' || ".join(columns)
    return func.to_tsvector('english', text(s))

class Example(Base):
    __tablename__ = 'examples'

    id = Column(Integer, primary_key=True)
    atext = Column(String)
    btext = Column(String)

    __table_args__ = (
        Index(
            'ix_examples_tsv',
            to_tsvector_ix('atext', 'btext'),
            postgresql_using='gin'
            ),
        )

你能举个例子来说明如何查询向量吗?谢谢。 - apaleja
1
@apaleja - 请查看此答案底部的示例查询:https://dev59.com/SWvXa4cB1Zd3GeqPO_w9 - benvc
注意,我遇到了这个问题:sqlalchemy.exc.CompileError: No literal value renderer is available for literal value "'english'" with datatype REGCONFIG。我需要将"english"更改为sqlalchemy.literal("english") - Dustin Oprea

12

感谢这个问题和答案。

我想补充一点,以防使用alembic通过autogenerate管理版本的人不会被检测到创建索引。

我们最终可能需要编写自己的alter脚本,看起来像这样。

"""add fts idx

Revision ID: e3ce1ce23d7a
Revises: 079c4455d54d
Create Date: 

"""

# revision identifiers, used by Alembic.
revision = 'e3ce1ce23d7a'
down_revision = '079c4455d54d'

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.create_index('idx_content_fts', 'table_name',
            [sa.text("to_tsvector('english', content)")],
            postgresql_using='gin')


def downgrade():
    op.drop_index('idx_content_fts')

10

@sharez和@benvc已经回答了这个问题。不过我需要按照权重来解决它。基于他们的回答,以下是我的做法:

已经有人回答了这个问题,他们是@sharez和@benvc。不过我需要对其进行加权处理。根据他们的回答,以下是我的做法:

from sqlalchemy import Column, func, Index, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.operators import op

CONFIG = 'english'

Base = declarative_base()

def create_tsvector(*args):
    field, weight = args[0]
    exp = func.setweight(func.to_tsvector(CONFIG, field), weight)
    for field, weight in args[1:]:
        exp = op(exp, '||', func.setweight(func.to_tsvector(CONFIG, field), weight))
    return exp

class Example(Base):
    __tablename__ = 'example'

    foo = Column(String)
    bar = Column(String)

    __ts_vector__ = create_tsvector(
        (foo, 'A'),
        (bar, 'B')
    )

    __table_args__ = (
        Index('my_index', __ts_vector__, postgresql_using='gin'),
    )

感谢使用 op :D - Steffo
你能举个例子来说明如何查询吗?我正在使用 query.filter(Example.__ts_vector__.op("@@")(func.websearch_to_tsquery(FTS_CONFIG, search_term)).all() 进行查询。然而,我总是得到一个空列表。 - mrpandey

0

之前的答案指明了正确的方向,非常有帮助。 以下是一种简化的 ORM 方法,使用 sqlalchemy-utilsTSVectorType 辅助工具(如果需要,可以直接复制/粘贴避免外部依赖,详情请参见 https://sqlalchemy-utils.readthedocs.io/en/latest/_modules/sqlalchemy_utils/types/ts_vector.html):

在您的 ORM 模型(声明性)中定义一个 TSVECTOR 列(TSVectorType),自动从源文本字段填充

import sqlalchemy as sa
from sqlalchemy_utils.types.ts_vector import TSVectorType
# ^-- https://sqlalchemy-utils.readthedocs.io/en/latest/_modules/sqlalchemy_utils/types/ts_vector.html


class MyModel(Base):
    __tablename__ = 'mymodel'
    id = sa.Column(sa.Integer, primary_key=True)
    content = sa.Column(sa.String, nullable=False)

    content_tsv = sa.Column(
        TSVectorType("content", regconfig="english"),
        sa.Computed("to_tsvector('english', \"content\")", persisted=True))
    #      ^-- equivalent for SQL:
    #   COLUMN content_tsv TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED;

    __table_args__ = (
        # Indexing the TSVector column
        sa.Index("idx_mymodel_content_tsv", content_tsv, postgresql_using="gin"), 
    )

有关使用ORM进行查询的更多详细信息,请参见https://dev59.com/72Yr5IYBdhLWcg3w1NdY#73999486SQLAlchemy 1.4SQLAlchemy 2.0之间存在重要差异)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接