python - practices - sqlalchemy select example
¿Cómo puedo lograr una relación auto-referenciada de muchos a muchos en el SQLAlchemy ORM para hacer referencia al mismo atributo? (2)
Estoy tratando de implementar una relación de muchos a muchos autorreferencial utilizando declarativo en SQLAlchemy.
La relación representa amistad entre dos usuarios. En línea, he encontrado (tanto en la documentación como en Google) cómo establecer una relación de m2m autorreferencial en la que, de alguna manera, los roles se diferencian. Esto significa que en esta relación de m2m, el Usuario A es, por ejemplo, el jefe del UsuarioB, por lo que lo incluye bajo un atributo de "subordinados" o lo que tenga usted. De la misma manera, UserB enumera UserA debajo de ''superiores''.
Esto no constituye ningún problema, ya que podemos declarar un backref a la misma tabla de esta manera:
subordinates = relationship(''User'', backref=''superiors'')
Entonces, por supuesto, el atributo "superiores" no es explícito dentro de la clase.
De todos modos, aquí está mi problema: ¿qué sucede si deseo realizar una referencia al mismo atributo en el que llamo la referencia? Me gusta esto:
friends = relationship(''User'',
secondary=friendship, #this is the table that breaks the m2m
primaryjoin=id==friendship.c.friend_a_id,
secondaryjoin=id==friendship.c.friend_b_id
backref=??????
)
Esto tiene sentido, porque si A se hace amigo de B, los roles de relación son los mismos, y si invoco a los amigos de B, debería obtener una lista con A en ella. Este es el código problemático en su totalidad:
friendship = Table(
''friendships'', Base.metadata,
Column(''friend_a_id'', Integer, ForeignKey(''users.id''), primary_key=True),
Column(''friend_b_id'', Integer, ForeignKey(''users.id''), primary_key=True)
)
class User(Base):
__tablename__ = ''users''
id = Column(Integer, primary_key=True)
friends = relationship(''User'',
secondary=friendship,
primaryjoin=id==friendship.c.friend_a_id,
secondaryjoin=id==friendship.c.friend_b_id,
#HELP NEEDED HERE
)
Lo siento si esto es demasiado texto, solo quiero ser lo más explícito que pueda con esto. Parece que no puedo encontrar ningún material de referencia a esto en la web.
Aquí está el enfoque de UNION que insinué en la lista de correo de hoy.
from sqlalchemy import Integer, Table, Column, ForeignKey, /
create_engine, String, select
from sqlalchemy.orm import Session, relationship
from sqlalchemy.ext.declarative import declarative_base
Base= declarative_base()
friendship = Table(
''friendships'', Base.metadata,
Column(''friend_a_id'', Integer, ForeignKey(''users.id''),
primary_key=True),
Column(''friend_b_id'', Integer, ForeignKey(''users.id''),
primary_key=True)
)
class User(Base):
__tablename__ = ''users''
id = Column(Integer, primary_key=True)
name = Column(String)
# this relationship is used for persistence
friends = relationship("User", secondary=friendship,
primaryjoin=id==friendship.c.friend_a_id,
secondaryjoin=id==friendship.c.friend_b_id,
)
def __repr__(self):
return "User(%r)" % self.name
# this relationship is viewonly and selects across the union of all
# friends
friendship_union = select([
friendship.c.friend_a_id,
friendship.c.friend_b_id
]).union(
select([
friendship.c.friend_b_id,
friendship.c.friend_a_id]
)
).alias()
User.all_friends = relationship(''User'',
secondary=friendship_union,
primaryjoin=User.id==friendship_union.c.friend_a_id,
secondaryjoin=User.id==friendship_union.c.friend_b_id,
viewonly=True)
e = create_engine("sqlite://",echo=True)
Base.metadata.create_all(e)
s = Session(e)
u1, u2, u3, u4, u5 = User(name=''u1''), User(name=''u2''), /
User(name=''u3''), User(name=''u4''), User(name=''u5'')
u1.friends = [u2, u3]
u4.friends = [u2, u5]
u3.friends.append(u5)
s.add_all([u1, u2, u3, u4, u5])
s.commit()
print u2.all_friends
print u5.all_friends
Necesitaba resolver este mismo problema y me equivoqué bastante con la relación auto-referencial de muchos a muchos en la que también estaba subclasificando la clase de User
con una clase de Friend
y ejecutando sqlalchemy.orm.exc.FlushError
. Al final, en lugar de crear una relación autoreferencial de muchos a muchos, creé una relación autorreferencial de uno a varios utilizando una tabla de unión (o tabla secundaria).
Si lo piensas, con objetos autorreferenciales, uno a muchos es muchos a muchos. Se resolvió el problema de la referencia en la pregunta original.
También tengo un ejemplo de trabajo detallado si quieres verlo en acción. También parece que los gistes de formatos de github ahora contienen portátiles ipython. Ordenado.
friendship = Table(
''friendships'', Base.metadata,
Column(''user_id'', Integer, ForeignKey(''users.id''), index=True),
Column(''friend_id'', Integer, ForeignKey(''users.id'')),
UniqueConstraint(''user_id'', ''friend_id'', name=''unique_friendships''))
class User(Base):
__tablename__ = ''users''
id = Column(Integer, primary_key=True)
name = Column(String(255))
friends = relationship(''User'',
secondary=friendship,
primaryjoin=id==friendship.c.user_id,
secondaryjoin=id==friendship.c.friend_id)
def befriend(self, friend):
if friend not in self.friends:
self.friends.append(friend)
friend.friends.append(self)
def unfriend(self, friend):
if friend in self.friends:
self.friends.remove(friend)
friend.friends.remove(self)
def __repr__(self):
return ''<User(name=|%s|)>'' % self.name