简体   繁体   中英

Self-referential relationship with filter in SQLAlchemy

I am attempting to create a self referential SQLAlchemy relationship with custom JOIN logic.

Our database has a table of Events which contains the 'eventID', 'eventDate' and 'userID' columns. The relationship I'm implementing is 'previousEvents'. I want to join the Events table to itself using 'userID' where eventDate > eventDate for every other event with the same userID.

The SQL query constructed should look like so:

SELECT original.eventID, previous.eventID, ... etc
FROM   Events as original
JOIN   Events as previous 
       ON  previous.userID = original.userID 
       AND previous.eventDate < original.eventDate

My table definitions look like so:

from sqlalchemy import Column, ForeignKey, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

class Event(Base):
    __tablename__ = 'event'

    # Attributes
    eventID = Column(String(length=256), primary_key=True)
    eventDate = Column(DateTime)
    userID = Column(String(length=256), ForeignKey('users.userID'))

class Users(Base):
    __tablename__ = 'users'

    # Attributes
    userID = Column(String(length=256), primary_key=True)
    userName = Column(String)

How can I define the relationship in Events to get the desired effect?

You need an alternate join condition and custom foreign conditions , because there's no actual foreign key relationship to join on:

class Event(Base):                                                            
    __tablename__ = 'event'            

    # Attributes
    eventID = Column(String(length=256), primary_key=True)
    eventDate = Column(DateTime)
    userID = Column(String(length=256), ForeignKey('users.userID'))

    previousEvents = relationship(
        "Event", primaryjoin=and_(userID == foreign(userID),
                                  eventDate > foreign(eventDate)),
        viewonly=True)      

Use the foreign() annotation to create a join condition without actual foreign keys. The foreign_keys argument of relationship() could also be used, but you would have to be very careful in how you arrange the comparisons in order to get the columns in the correct order, since the same columns are used on both sides of the join.

When you wish to create a query that joins the previous events you need an alias :

In [13]: prev_event = aliased(Event)

In [14]: print(session.query(Event).join(prev_event, Event.previousEvents))
SELECT event."eventID" AS "event_eventID", event."eventDate" AS "event_eventDate", event."userID" AS "event_userID" 
FROM event JOIN event AS event_1 ON event_1."userID" = event."userID" AND event_1."eventDate" < event."eventDate"

Or if you wish to eager load using a join:

In [17]: print(session.query(Event).options(joinedload(Event.previousEvents)))
SELECT event."eventID" AS "event_eventID", event."eventDate" AS "event_eventDate", event."userID" AS "event_userID", event_1."eventID" AS "event_1_eventID", event_1."eventDate" AS "event_1_eventDate", event_1."userID" AS "event_1_userID" 
FROM event LEFT OUTER JOIN event AS event_1 ON event_1."userID" = event."userID" AND event_1."eventDate" < event."eventDate"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM