【问题标题】:sqlalchemy - Limit for joined table as if they where not joinedsqlalchemy - 加入表的限制,就好像它们没有加入一样
【发布时间】:2018-12-23 01:36:48
【问题描述】:

我正在使用 sqlalchemy,我想从用户那里获取以下数据并按照给定表的顺序应用这些操作:

过滤数据的关键字、排序依据的列、限制和页面 编号

现在我有很多桌子。大多数“儿童”表 - 没有孩子的表 - 工作。但是我有一张桌子,里面有很多各种各样的关系......两边都是一对多,一对一和多对多

为了实现上述操作,我预先加入了所有的表。过滤和排序工作正常,但限制并没有给我想要的结果

加入声明:

records = m.Activity.query.join(m.Event, m.Activity.events) \
            .join(m.DateLocation, m.Activity.date_locations) \
            .join(m.Goal, m.Activity.goals) \
            .join(m.Type, m.Activity.type)

过滤和排序包含很多不必要的信息,基本上是这样的:

# filtering if column == event
records = records.filter(m.Event.name == keyword) 
# ordering if column == type and desc was chosen
records = records.order_by(m.Type.name.desc())

最后是限制和分页:

records = records.limit(limit)
records = records.offset((page - 1) * limit)

让我解释一下限制行为与我想要什么:

此代码中的限制工作正常。因为我加入了所有表,它将返回我给它的加入行数..如果加入导致额外的 5 行并且我要求限制 5 例如,它将返回前 5 行,而不管原始表 ID 是什么

我想要的是加入前的限制行为。我加入他们只是为了按他们过滤或订购。之后,当我说限制 (5) 时,我想返回具有不同 ID 的前 5 个结果

我尝试了以下方法(一次一个),但没有奏效:

records = records.distinct(m.Activity.id).limit(limit)
records = records.group_by(m.Activity.id).limit(limit)
records = records.from_self().limit(limit)

我尝试了here 提供的解决方案。它确实有效,但是它在加入之前限制了数据集。这在我的情况下不起作用,因为我需要限制过滤后的数据

编辑:模型:

EventsInActivities = db.Table(
    'events_in_activities',
    db.Column('activity_id', db.String, db.ForeignKey('activity.id')),
    db.Column('event_id', db.Integer(), db.ForeignKey('event.id'))
)


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


class Type(db.Model, BaseMixin):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String, unique=True)
    activities = db.relationship("Activity", backref="type", lazy='dynamic')


class Goal(db.Model, BaseMixin):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    activity_id = db.Column(db.String, db.ForeignKey('activity.id'), primary_key=True)
    name = db.Column(db.String())


class DateLocation(db.Model, BaseMixin):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    activity_id = db.Column(db.String, db.ForeignKey('activity.id'), primary_key=True)
    start_date = db.Column(db.DateTime)
    end_date = db.Column(db.DateTime)
    location = db.Column(db.String())


class Activity(db.Model, BaseMixin):
    id = db.Column(db.String, primary_key=True)
    name = db.Column(db.String())
    type_id = db.Column(db.Integer, db.ForeignKey('type.id'))
    date_locations = db.relationship("DateLocation", order_by='DateLocation.start_date', cascade="all, delete", backref="activity", lazy='dynamic')
    goals = db.relationship("Goal", cascade="all, delete", backref="activity", lazy='dynamic')
    events = db.relationship('Event', secondary=EventsInActivities, backref=db.backref('activities', lazy='dynamic'))

【问题讨论】:

    标签: python sql postgresql sqlalchemy


    【解决方案1】:

    您可以用EXISTS 子查询表达式或semijoins 在某种程度上替换过滤器的至少一些连接。这样,您的查询就可以避免为单个活动生成多行。仍然可以加入反对Type,因为这是多对一的关系:

    records = m.Activity.query.\
        join(m.Activity.type).\
        filter(m.Activity.events.any(name=keyword)).\
        filter(m.Activity.goals.any(name=...)).\
        filter(...).\
        order_by(m.Type.name.desc()).\
        limit(limit).\
        offset((page - 1) * limit)
    

    将关键字参数传递给any() 是与filter_by() 类似的简写。它也接受复杂的标准表达式,作为位置参数。

    distinct(m.Activity.id) 或 DISTINCT ON 应该也能正常工作,只要您随后将结果用作子查询,然后对其应用排序和限制:

    records = m.Activity.query.\
        join(m.Activity.events).\
        join(m.Activity.date_locations).\
        join(m.Activity.goals).\
        filter(m.Event.name == keyword).\
        filter(...).\
        distinct(m.Activity.id).\
        from_self().\
        join(m.Activity.type).\
        order_by(m.Type.name.desc()).\
        limit(limit).\
        offset((page - 1) * limit)
    

    【讨论】:

    • 成功了!谢谢!对于任何有兴趣的人,我使用了第一个解决方案,与我的其余代码一起使用会更好
    • 只是一个简单的问题 .. order_by 如何处理目标?
    • 这取决于。每个活动的所有相关目标是否都使用相同的名称?如果没有,你会订购哪一个?
    • 每个活动都有不同的目标.. 大多数人都有一个目标,但一个活动可能有多个目标。事件也是如此。真正的问题是日期位置,因为每个活动很容易 +2 具有不同的值。我想做一些事情,比如在开始日期之前订购它们。然后获取不同的活动 ID 并将它们显示为“按开始日期排序”
    • “开始日期”是指您希望按Activity 至少按DateLocation.start_date 订购?这是greatest-n-per-group 的各种问题。总而言之,您将如何从您订购活动的各个组中选择GoalEvent 有点不清楚,或者我误解了。你的意思是你想急切地加载目标等,排序?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2019-01-23
    • 1970-01-01
    • 2019-04-29
    • 1970-01-01
    相关资源
    最近更新 更多