Python SQLAlchemy自指多对多对称关系

Python SQLAlchemy自指多对多对称关系,python,sql,sqlalchemy,many-to-many,Python,Sql,Sqlalchemy,Many To Many,我将Python2.7与SQLAlchemy结合使用,并尝试用多对多关系来建模友谊关系 我需要桌子完全对称;如果A是B的朋友,那么它也必须是B的朋友 我曾尝试使用secondary Friendly表对关系进行建模,并使用primary-and secondary-Join将其连接到模型,但我开始觉得我走错了方向 我发现有人试图用一对多的关系来模拟同样的事情,但这对我不起作用,因为我的友谊关系不是一对多的 我已经成功地使用多对多表实现了一个工作模型,如果我正在创建一个“副本”:当我想添加B作为a

我将Python2.7与SQLAlchemy结合使用,并尝试用多对多关系来建模友谊关系

我需要桌子完全对称;如果A是B的朋友,那么它也必须是B的朋友

我曾尝试使用secondary Friendly表对关系进行建模,并使用primary-and secondary-Join将其连接到模型,但我开始觉得我走错了方向

我发现有人试图用一对多的关系来模拟同样的事情,但这对我不起作用,因为我的友谊关系不是一对多的

我已经成功地使用多对多表实现了一个工作模型,如果我正在创建一个“副本”:当我想添加B作为a的朋友时,我也会添加a作为B的朋友。但我觉得提议的解决方案应该更简洁


这里的最终游戏类似于Facebook的友谊建模。如果B是A的朋友,A只能是B的朋友。

第一次尝试使用自定义主连接条件和辅助连接条件时,可以使用A进行扩充,在这种情况下,A将是从关联表中选择的两种可能方式的联合。给定一个玩具用户模型,例如

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))
和复合“次要”

使用上面的,
User
模型将关系定义为

User.friends = relationship(
    "User", secondary=friends,
    primaryjoin=User.id == friends.c.left_id,
    secondaryjoin=User.id == friends.c.right_id,
    viewonly=True)
不幸的副作用是,这种关系是只读的,您必须手动将行插入到
Friendly
以使用户成为朋友。还有重复的问题,因为友谊仍然可以同时包含
(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)
要使视图可写,需要一些触发器:

# 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)

对关联表中存储的ID执行排序以避免重复,并使用两种可能的方法的并集来选择作为辅助“表”。您能提供一个示例吗@ilja Everila你在用什么数据库?我在用Sqlite@IljaEverilä谢谢,但链接指向一个纯SQL解决方案,我知道,我正在寻找一种使用sqlalchemy的方法,如果您希望发布一个,我愿意接受您的答案。everila-感谢非常详细的答案,我很难利用它,它经过测试了吗?如果是这样的话,我会继续寻找我的代码到底出了什么问题,但当然你可能已经发现了一些被忽视的优势。如果元数据中不存在表用户,那么将union定义为次要或视图将失败,因此在添加relationship属性之前必须定义
用户
模型,这可能是一个不明显的问题。
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)
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)