Bharad
Bharad

Reputation: 549

SqlAlchemy many to many data modeling

Please help me model this in SQLAlchemy. A user can create a question. A question can have any number of choices (E.g. YES, NO, LATER, MAY BE, DONT KNOW, NEXT YEAR, NOT APPLICABLE). I have created a mapper between questions and choices. How do I model the responses in SQLAlchemy?

question_choices = Table('question_choices', Base.metadata,
    Column('id', Integer, primary_key=True),
    Column('question_id', Integer, ForeignKey('questions.id')),
    Column('choice_id', Integer, ForeignKey('choices.id'))
    )

class Choice(Base):
    __tablename__ = 'choices'
    id = Column(Integer, primary_key=True)
    value = Column(String(30), nullable=False)

class Question(Base):
    __tablename__ = 'questions'
    id = Column(Integer, primary_key=True)
    title   = Column(String(100))
    created = Column(DateTime)

    choices = relationship('Choice', secondary=question_choices)

class questionResponse(Base):
    """A friend's response to a question"""
    __tablename__ = 'question_responses'
    id = Column(Integer, primary_key=True)
    question_id = Column(Integer, ForeignKey('questions.id'))
    choice_id = Column(Integer, ForeignKey('choices.id'))
    user_id = Column(Integer, ForeignKey('users.id'))
    created = Column(DateTime)

The questionResponse model is not normalized. Question_id and listing_id are repeated. I do not have a relationship in the mapper table. I want to be able to count the responses for a given question.

Upvotes: 0

Views: 283

Answers (1)

van
van

Reputation: 76992

Your mapper for QuestionResponse is pretty good already, but it does not restrict an answer of the choice that is not configured: so if LATER is not allowed answer for a question Will you marry me?, the database does not restrict this.

One solution to this would be to also add a two-column foreign key constraint to the QuestionResponse:

class QuestionResponse(Base):
    """A friend's response to a question"""
    __tablename__ = 'question_responses'
    id = Column(Integer, primary_key=True)
    question_id = Column(Integer, ForeignKey('questions.id'))
    choice_id = Column(Integer, ForeignKey('choices.id'))
    # ...
    __table_args__ = (
            ForeignKeyConstraint(['question_id', 'choice_id'], ['question_choices.question_id', 'question_choices.choice_id']),
            )

Alternative (more normalized DB model) is to define the FK only to the question_choices.id:

class QuestionResponse(Base):
    """A friend's response to a question"""
    __tablename__ = 'question_responses'
    id = Column(Integer, primary_key=True)
    question_choice_id = Column(Integer, ForeignKey('question_choices.id'))

edit-1: In this case you can have a relationship between Question and QuestionResponse defined like below, which will provide you with count as well:

class Question(Base):
    # ....
    answers = relationship("QuestionResponse", 
        primaryjoin="Question.id==question_choices.c.question_id",
        secondary=question_choices,
        secondaryjoin="question_choices.c.id==QuestionResponse.question_choice_id",
        backref="question",
        )

In any case you might want to add a UniqueConstraint to the question_choices table on columns (question_id, choice_id).


Now, in order to count responses, you either add the relationship between Question and QuestionResponse and return len(answers) or you just create a query-based property on Question:

class Question(Base):
    # ...
    answer_count = column_property(
                select([func.count(QuestionResponse.__table__.c.id)]).
                where(question_choices.c.question_id==id).
                where(question_choices.c.id==QuestionResponse.__table__.c.question_choice_id)
            )

Upvotes: 1

Related Questions