我在SQLAlchemy中使用python 2.7,并试图建立一个多对多关系的友情关系。
我需要桌子完全对称;如果A是B的朋友,那么它也必须是另一种方式。
我尝试使用辅助友谊表对关系进行建模,并使用primary-和secondaryjoin将其连接到模型,但我开始觉得我的方向错误。
我发现this post有人试图用一对多的关系模仿同样的东西,但这对我不起作用,因为我的友谊关系不是一对一的。
我已经设法使用多对多的表来实现一个工作模型,如果我正在使用“重复”:当我想添加B作为A的朋友时,我也添加A作为B的朋友。但我觉得建议的解决方案应该更加整洁。
这里的最终游戏类似于Facebook的友谊模特。如果B是A的朋友,A只能是B的朋友。
答案 0 :(得分:2)
使用自定义主要和次要连接条件的第一次尝试可以使用composite "secondary"进行扩充,在这种情况下,它将是从关联表中选择的两种可能方式的并集。给定玩具用户模型,如
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
email = Column(Unicode(255), unique=True)
关联表可能看起来像
friendship = Table(
"friendship", Base.metadata,
Column("left_id", ForeignKey("user.id"), primary_key=True),
Column("right_id", ForeignKey("user.id"), primary_key=True))
和复合“二级”
friends = select([friendship.c.left_id.label("left_id"),
friendship.c.right_id.label("right_id")]).\
union_all(select([friendship.c.right_id,
friendship.c.left_id])).\
alias("friends")
使用上面的User
模型将关系定义为
User.friends = relationship(
"User", secondary=friends,
primaryjoin=User.id == friends.c.left_id,
secondaryjoin=User.id == friends.c.right_id,
viewonly=True)
不幸的副作用是该关系是只读的,您必须手动将行插入friendship
以使用户成为朋友。此外还存在重复问题,例如,friendship
仍可包含(1, 2)
和(2, 1)
。添加一个强制对左右id进行排序的检查约束可以解决重复问题:
# NOTE: This has to be done *before* creating your tables. You could also
# pass the CheckConstraint as an argument to Table directly.
chk = CheckConstraint(friendship.c.left_id < friendship.c.right_id)
friendship.append_constraint(chk)
但是,应用程序必须在插入时订购ID。为了解决这个问题,用作“辅助”的联合可以隐藏在可写视图中。 SQLAlchemy没有用于处理开箱即用视图的构造,但有一个usage recipe for just that。使用配方friends
变为:
friends = view(
"friends",
Base.metadata,
select([friendship.c.left_id.label("left_id"),
friendship.c.right_id.label("right_id")]).\
union_all(select([friendship.c.right_id,
friendship.c.left_id])))
要使视图可写,需要一些触发器:
# For SQLite only. Other databases have their own syntax for triggers.
DDL("""
CREATE TRIGGER friends_insert_trg1 INSTEAD OF INSERT ON friends
WHEN new.left_id < new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.left_id, new.right_id);
END;
""").execute_at("after-create", Base.metadata)
DDL("""
CREATE TRIGGER friends_insert_trg2 INSTEAD OF INSERT ON friends
WHEN new.left_id > new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.right_id, new.left_id);
END;
""").execute_at("after-create", Base.metadata)
将这些更紧密地绑定到视图的创建会很好,但只要在定义视图后注册它们,这也会很好。有了触发器,您可以从viewonly=True
关系中删除User.friends
参数。
全部放在一起:
from sqlalchemy import \
Table, Column, Integer, Unicode, ForeignKey, CheckConstraint, DDL, \
select
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from view import view
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
email = Column(Unicode(255), unique=True)
friendship = Table(
"friendship",
Base.metadata,
Column("left_id", ForeignKey("user.id"), primary_key=True),
Column("right_id", ForeignKey("user.id"), primary_key=True),
CheckConstraint("left_id < right_id"))
friends = view(
"friends",
Base.metadata,
select([friendship.c.left_id.label("left_id"),
friendship.c.right_id.label("right_id")]).\
union_all(select([friendship.c.right_id,
friendship.c.left_id])))
User.friends = relationship(
"User", secondary=friends,
primaryjoin=User.id == friends.c.left_id,
secondaryjoin=User.id == friends.c.right_id)
DDL("""
CREATE TRIGGER friends_insert_trg1 INSTEAD OF INSERT ON friends
WHEN new.left_id < new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.left_id, new.right_id);
END;
""").execute_at("after-create", Base.metadata)
DDL("""
CREATE TRIGGER friends_insert_trg2 INSTEAD OF INSERT ON friends
WHEN new.left_id > new.right_id
BEGIN
INSERT INTO friendship (left_id, right_id)
VALUES (new.right_id, new.left_id);
END;
""").execute_at("after-create", Base.metadata)