sql >> Database >  >> RDS >> PostgreSQL

sqlalchemy symmetrisch veel op één vriendschap

U kunt onder andere meer willen weten over verenigingsproxy's . Een associatieproxy vertelt SQLAlchemy dat u een veel-op-veel-relatie hebt die wordt gemedieerd door een tussenliggende tabel die aanvullende gegevens kan bevatten. In uw geval heeft elke User kan meerdere verzoeken verzenden en ook meerdere verzoeken ontvangen en Relationship is de bemiddelingstabel die de status . bevat kolom als aanvullende gegevens.

Hier is een variant van je code die relatief dicht bij wat je hebt geschreven blijft:

from sqlalchemy.ext.associationproxy import association_proxy


class User(db.Model):
    __tablename__ = 'User'
    # The above is not necessary. If omitted, __tablename__ will be
    # automatically inferred to be 'user', which is fine.
    # (It is necessary if you have a __table_args__, though.)

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(35), unique=False)
    # and so forth

    requested_rels = db.relationship(
        'Relationship',
        foreign_keys='Relationship.requesting_user_id',
        backref='requesting_user'
    )
    received_rels = db.relationship(
        'Relationship',
        foreign_keys='Relationship.receiving_user_id',
        backref='receiving_user'
    )
    aspiring_friends = association_proxy('received_rels', 'requesting_user')
    desired_friends = association_proxy('requested_rels', 'receiving_user')

    def __repr__(self):
        # and so forth


class Relationship(db.Model):
    # __tablename__ removed, becomes 'relationship'
    # __table_args__ removed, see below

    requesting_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
    receiving_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
    # Marking both columns above as primary_key creates a compound primary
    # key, which at the same time saves you the effort of defining the
    # UNIQUE constraint in __table_args__
    status = db.Column(db.Integer)

    # Implicit one-to-many relations: requesting_user, receiving_user.
    # Normally it would be more convenient to define those relations on
    # this side, but since you have two outgoing relationships with the
    # same table (User), you chose wisely to define them there.

(Merk op hoe ik de regels iets anders heb gerangschikt en hoe ik de _id heb gebruikt achtervoegsel voor vreemde-sleutelkolommen met behoud van dezelfde naam zonder het achtervoegsel voor de corresponderende db.relationship s. Ik stel voor dat je deze stijl ook overneemt.)

Nu heb je een schone manier om toegang te krijgen tot inkomende en uitgaande vriendschapsverzoeken, evenals de bijbehorende gebruikers, rechtstreeks vanaf je User model. Dit is echter nog steeds niet ideaal omdat je de volgende code moet schrijven om alles bevestigd te krijgen vrienden van een gebruiker:

def get_friends(user):
    requested_friends = (
        db.session.query(Relationship.receiving_user)
        .filter(Relationship.requesting_user == user)
        .filter(Relationship.status == CONFIRMED)
    )
    received_friends = (
        db.session.query(Relationship.requesting_user)
        .filter(Relationship.receiving_user == user)
        .filter(Relationship.status == CONFIRMED)
    )
    return requested_friends.union(received_friends).all()

(Ik heb dit niet getest; misschien moet je ook join met User in beide zoekopdrachten om de union aan het werk.)

Om het nog erger te maken, de modelnaam Relationship evenals de namen van verschillende leden binnen de modellen lijken niet goed over te brengen wat ze eigenlijk bedoelen.

U kunt zaken verbeteren door Relationship.status te verwijderen en hernoemen van Relationship naar FriendshipRequest . Voeg vervolgens een tweede User toe -naar-User associatiemodel genaamd Friendship en voeg een overeenkomstige tweede set van db.Relationship . toe s met backref s en association_proxy s naar User . Wanneer iemand een vriendschapsverzoek stuurt, dien je een record in bij FriendshipRequest . Als het verzoek wordt geaccepteerd, verwijdert u de record en vervangt u deze door een nieuwe record in Friendship . Op deze manier wordt, in plaats van een statuscode te gebruiken, de status van een vriendschap gecodeerd door de tabel waarin u een paar gebruikers opslaat. De Friendship model kan er als volgt uitzien:

class Friendship(db.Model):
    user1_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)
    user2_id = db.Column(db.Integer, db.ForeignKey('User.id'), primary_key=True)

    # Implicit one-to-many relations: user1, user2
    # (defined as backrefs in User.)

(Overeenkomende db.relationship s en association_proxy s in User worden als oefening aan de lezer overgelaten.)

Deze aanpak bespaart u de helft van de filterbewerkingen wanneer u de bevestigde vrienden van een gebruiker nodig heeft. Toch moet je een union maken van twee zoekopdrachten omdat uw gebruiker user1 . kan zijn of user2 in elk geval van Friendship . Dit is inherent moeilijk omdat we te maken hebben met een reflexieve symmetrische relatie. Ik denk dat het mogelijk is om nog elegantere manieren te bedenken om het te doen, maar ik denk dat dat ingewikkeld genoeg zou zijn om een ​​nieuwe vraag hier op Stack Overflow te rechtvaardigen.




  1. Schema-onafhankelijke Entiteit Kadercode Eerste Migraties

  2. Python3.4 kan mysql-python niet installeren

  3. Vraag problemen met SQLite3 en Postgres/Heroku Ruby on Rails

  4. snelle selectie van een willekeurige rij uit een grote tabel in mysql