【问题标题】:Need some help accessing a ForeignKey row's data while performing a select with SQLAlchemy在使用 SQLAlchemy 执行选择时需要一些帮助来访问 ForeignKey 行的数据
【发布时间】:2011-12-13 20:58:29
【问题描述】:

后台架构:

class Checkpoint(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    creator = db.Column(db.Integer, db.ForeignKey('user.id'))
    name = db.Column(db.String(255))
    description = db.Column(db.String(255), nullable=True)
    price = db.Column(db.Float, nullable=True)
    expiry = db.Column(db.DateTime, nullable=True)
    date_created = db.Column(db.DateTime)
    type = db.Column(db.String(255))
    image = db.Column(db.String(255))
    longitude = db.Column(db.Float)
    latitude = db.Column(db.Float)

class UserCheckpoint(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship("User")
    checkpoint_id = db.Column(db.Integer, db.ForeignKey('checkpoint.id'))
    checkpoint = db.relationship("Checkpoint")

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255))
    facebook_info = db.Column(db.String(255), db.ForeignKey('facebook_user.id'))
    facebook_user = db.relationship("FacebookUser")

class FriendConnection(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    fb_user_from = db.Column(db.String(255), db.ForeignKey('facebook_user.id'))
    fb_user_to = db.Column(db.String(255), db.ForeignKey('facebook_user.id'))

class FacebookUser(db.Model):
    id = db.Column(db.String(255), primary_key=True)
    name = db.Column(db.String(255))
    first_name = db.Column(db.String(255), nullable=True)
    middle_name = db.Column(db.String(255), nullable=True)
    last_name = db.Column(db.String(255), nullable=True)
    gender = db.Column(db.String(255), nullable=True)
    username = db.Column(db.String(255), nullable=True)
    link = db.Column(db.String(255), nullable=True)

我有一个用户,如您所见,每个用户都有一个 Facebook 个人资料,以及一个描述 Facebook 个人资料间友谊的表格。因此,给定用户,用户将拥有 Facebook 好友列表。我想在给定的检查点条件下获取属于用户或其朋友的所有用户检查点:

coord_conditions = and_(Checkpoint.longitude <= longitude + exp_no,
                                Checkpoint.longitude >= longitude - exp_no,
                                Checkpoint.latitude <= latitude + exp_no,
                                Checkpoint.latitude >= latitude - exp_no,
                                )

如何使用 SQLAlchemy 中的 ORM 执行此操作?谢谢!

总结:鉴于 user_id 属于朋友/自己的列表,如何选择 UserCheckpoints;而 UserCheckpoint.checkpoint 需要满足一组条件。

【问题讨论】:

  • 你不是说coord_conditions = and_(((longitude - Checkpoint.longitude)**2 + (latitude - Checkpoint.latitude)**2) &lt;= exp_no)吗?
  • @bpgergo,也可以这样工作。但我想这无关紧要,因为我想知道如何在 Checkpoint 上选择具有上述条件的 UserCheckpoints,以及作为 Facebook 朋友的用户。

标签: sql sqlalchemy


【解决方案1】:

每个关系都有两种方法来定义相关对象的条件:.has() 用于单个引用对象,.any() 用于集合。这些方法允许将您的任务直接转换为 SQLAlchemy 表达式。让我们将缺失的关系添加到FacebookUser

class FacebookUser(Model):
    # Definitions from question are here
    user = relationship(User, uselist=False)
    friends = relationship('FacebookUser',
                           secondary=FriendConnection.__table__,
                           primaryjoin=(id==FriendConnection.fb_user_from),
                           secondaryjoin=(FriendConnection.fb_user_to==id))

我已经定义 FacebookUser.user 假设一对一关系(通常在外键列上补充唯一约束)。如果您允许多个用户连接到一个 Facebook 帐户,只需删除 uselist=False 并调整名称即可。

对坐标条件的简短定义:

coord_conditions = Checkpoint.longitude.between(longitude - exp_no,
                                                longitude + exp_no) & \
                   Checkpoint.latitude.between(latitude - exp_no,
                                               latitude + exp_no)

即使是近似值,这个条件也肯定是错误的(-179.9° 和 179.9° 非常接近,而差异很大),但这不是问题的主要主题。

感兴趣的用户的条件(id等于user_id的用户和他的朋友):

user_cond = (User.id==user_id) | \
            User.facebook_user.has(
                FacebookUser.friends.any(FacebookUser.user.has(id=user_id)))

现在查询很简单:

session.query(UserCheckpoint).filter(
        UserCheckpoint.checkpoint.has(coord_conditions) & \
        UserCheckpoint.user.has(user_cond))

除非您有(或预期)性能问题,否则我建议避免以牺牲可读性为代价进行优化。

【讨论】:

  • Denis:any(..) 是一个很棒的方法,我认为它不会产生任何不太理想的SQL。我对friendship关系的关注如下: *假设A和B是朋友,关系表中只有一行;在这种情况下,查询只能以一种方式工作(从 A 到 B),而不是另一种方式(从 B 到 A)。因此,您还需要包括friends 关系的另一端......除非每个友谊总是有一对行:[(A,B),(B,A)]。
  • ...实际上,您是否对以下内容有所了解:问题中定义的 M-N 关系与您定义的 relationship(我们称之为 friends_from),但是用backref 支持对方(friends_to)。然后我可以定义一个属性来组合那些def friends(self): \n return self.friends_to + self.friends_from,它可以工作,但是它分别执行两个查询。很酷的是类似于@hybrid_property 的方式能够在@nubela 想要的查询中使用它 - 会很好而且干净。知道该怎么做吗?
  • @van:我不了解 Facebook,但通常将某人添加到朋友中并不意味着我已添加到他的朋友列表中。关于对称关联的问题非常有趣,但在 cmets 中讨论起来并不那么简单,并且不受 SQLAlchemy 的约束。我认为作为单独的sql 问题提出来已经足够有趣了。
  • 就 Facebook 而言,您是对的。但是这方面的 M-N 关系很有趣。在纯SQL 中,可以使用VIEW with UNION ALL 或通过存储关系的双方(2 行)来解决它。 SA 对它的处理实际上更有趣。我会提出关于 SO 的问题,希望得到你(和 Mike)的反馈。
【解决方案2】:

基本上您的查询可以分为两部分:

  1. 给定user_id,创建一个用户列表,其中包含用户本人以及所有直接朋友
  2. 给定来自 1. 的用户列表,获取所有 UserCheckpoint,其 Checkpoint 将满足条件。

未测试代码:

# get direct user for given user_id
u1 = (session.query(User.id.label("user_1_id"), User.id.label("user_id"))
     )

# get friends of the user in one direction (from other user to this one)
User2 = aliased(User)
FacebookUser2 = aliased(FacebookUser)
u2 = (session.query(User2.id.label("user_1_id"), User.id.label("user_id")).
        join(FacebookUser2, User2.facebook_info == FacebookUser2.id).
        join(FriendConnection, FacebookUser2.id == FriendConnection.fb_user_from).
        join(FacebookUser, FacebookUser.id == FriendConnection.fb_user_to).
        join(User, User.facebook_info == FacebookUser.id)
     )

# get friends of the user in other direction (from this user to the other)
User2 = aliased(User)
FacebookUser2 = aliased(FacebookUser)
u3 = (session.query(User2.id.label("user_1_id"), User.id.label("user_id")).
        join(FacebookUser2, User2.facebook_info == FacebookUser2.id).
        join(FriendConnection, FacebookUser2.id == FriendConnection.fb_user_to).
        join(FacebookUser, FacebookUser.id == FriendConnection.fb_user_from).
        join(User, User.facebook_info == FacebookUser.id)
     )

# create a union to have all pairs (me_or_friend_id, user_id)
u_all = union_all(u1, u2, u3)
# **edit-1: added alias **
u_all = u_all.alias("user_list_view")
# final query which adds filters requirested (by user_id and the checkpoint condition)
q = (session.query(UserCheckpoint).
        join(Checkpoint).filter(coord_conditions).
        join(u_all, UserCheckpoint.user_id == u_all.c.user_1_id).
        filter(u_all.c.user_id == user_id)
    )
for u_cp in q.all():
    print u_cp

注意,如果您在模型中定义了更多relationships,则可以稍微简化查询,然后可以从join 子句中删除一些primaryjoin 条件。

【讨论】:

  • 我没有看到错误,因为您提供的链接无效。如果我看到了 - 很乐意提供帮助,但我的代码示例的想法是解释如何做到这一点,而不是提供一个可行的解决方案(即使我相信它应该工作得很好,因为你完全描述了你的模型各种对象之间的相互关系)
  • 这是错误:pastebin.com/Fe0bhLzW,你是绝对正确的。我是 SQLAlchemy 的新手,正在尝试弄清楚标签的作用等等。我将继续深入研究 SQLAlchemy,看看如何解决这个错误。同时,当你看到这个时,如果你冷静地指出我正确的方向,那将是 gr8,谢谢!
  • 好的。如错误消息所示,我将.alias() 添加到u_all,这应该可以解决问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-12
  • 1970-01-01
  • 1970-01-01
  • 2015-06-09
  • 1970-01-01
相关资源
最近更新 更多