Reputation: 23
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?
Upvotes: 2
Views: 2489
Reputation: 52929
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"
Upvotes: 4