簡體   English   中英

如何計算 SQLAlchemy 中組的百分比?

[英]How to calculate percentage of a group in SQLAlchemy?

我正在 Python 中構建一個“測驗應用程序”,我需要將結果存儲在 SQL 數據庫中。 我想使用 SQLAlchemy Python 庫與數據庫進行交互。 我的應用程序的每個用戶將被問到從預先確定的 100 個可能問題中隨機選擇的 3 個問題。 每個問題只能回答“是”或“否”(即TrueFalse )。 我將答案存儲在定義如下的表中:

class Answer(Base):
    __tablename__ = "Answers"
    
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("Users.id"), nullable=False)
    question_id = Column(Integer)
    answer = Column(Boolean, nullable=False)
    
    user = relationship("User", back_populates="answers")

在所有用戶完成測驗后,我計算某個問題被用戶回答的次數:

tot_each_question = (db_session
                     .query(Answer.question_id,
                            count_questions.label("tot_answers_for_question"))
                     .group_by(Answer.question_id)
                     )

我還可以計算某個問題被用戶回答“是”(即True )的次數:

tot_true_for_question = (db_session
                         .query(Answer.question_id,
                                count_questions.label("tot_true_for_question"))
                         .filter(Answer.answer == True)
                         .group_by(Answer.question_id)
                         )

如何使用 SQLAlchemy 計算用戶回答“是”的每個問題的百分比? 我可以使用基本的 Python 字典輕松做到這一點:

dict_tot_each_question = {row.question_id: row.tot_answers_for_question
                          for row in tot_each_question.all()}

dict_tot_true_for_question = {row.question_id: row.tot_true_for_question
                              for row in tot_true_for_question.all()}

dict_percent_true_for_question = {}
for question_id, tot_answers in dict_tot_each_question.items():
    tot_true = dict_tot_true_for_question.get(question_id, 0)
    percent_true = tot_true / tot_answers * 100
    dict_percent_true_for_question[question_id] = percent_true

但我更喜歡使用 SQLAlchemy 功能來獲得相同的結果。 是否可以在 SQLAlchemy 中做到這一點? 在 SQLAlchemy 中這樣做是否方便高效,或者我基於 Python 字典的解決方案是否會更好?

只需將您已經擁有的兩個查詢中的兩個表達式組合成一個表達式即可獲得所需的結果:

q = (
    session.query(
        Question.id,
        (100 * func.sum(cast(Answer.answer, Integer)) / func.count(Answer.answer)).label("perc_true"),
    )
    .outerjoin(Answer)
    .group_by(Question.id)
)

正如您在上面看到的,我使用COUNT function 來獲得所有答案。

另一個需要注意的事項是,我的查詢Question開頭並Answer JOINs 這樣做的原因是,如果有沒有答案的Question ,如果只使用Answers表,您仍然會看到返回的(#id, NULL)而不是根本看不到一行。 但是,如果您不關心我所看到的這種極端情況,您可以按照自己的方式進行處理:

q = (
    session.query(
        Answer.question_id,
        (100 * func.sum(Answer.answer) / func.count(Answer.answer)).label("perc_true"),
    )
    .group_by(Answer.question_id)
)

最后,我做出的另一個假設是,在轉換為Integer之后,為了正確的SUM ,您的數據庫將處理true1 如果不是這種情況,請參閱此問題中有關如何處理此問題的多個答案: postgresql - sql - `true` 值的計數


獎金:

當我發現自己在 model 級別上詢問一些與聚合相關的問題時,我經常使用混合屬性擴展直接在 model 上實現這些。

下面的代碼將為您提供並說明如何將其用於您的案例:

class Answer(Base):
    __tablename__ = "answers"

    id = Column(Integer, primary_key=True)
    # user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    question_id = Column(Integer, ForeignKey("questions.id"))
    answer = Column(Boolean, nullable=False)

    # user = relationship("User", back_populates="answers")
    question = relationship("Question", back_populates="answers")


class Question(Base):
    __tablename__ = "questions"

    id = Column(Integer, primary_key=True)
    question = Column(String, nullable=False)

    answers = relationship("Answer", back_populates="question")

    @hybrid_property
    def answers_cnt(self):
        return len(list(self.answers))

    @hybrid_property
    def answers_yes(self):
        return len(list(_ for _ in self.answers if _.answer))

    @hybrid_property
    def answers_yes_percentage(self):
        return (
            100.0 * self.answers_yes / self.answers_cnt if self.answers_cnt != 0 else None
        )

    @answers_cnt.expression
    def answers_cnt(cls):
        return (
            select(func.count(Answer.id))
            .where(Answer.question_id == cls.id)
            .label("answers_cnt")
        )

    @answers_yes.expression
    def answers_yes(cls):
        return (
            select(func.count(Answer.id))
            .where(Answer.question_id == cls.id)
            .where(Answer.answer == True)
            .label("answers_yes")
        )

    @answers_yes_percentage.expression
    def answers_yes_percentage(cls):
        return (
            case(
                [(cls.answers_cnt == 0, None)],
                else_=(
                    100
                    * cast(cls.answers_yes, Numeric)
                    / cast(cls.answers_cnt, Numeric)
                ),
            )
        ).label("answers_yes_percentage")

在這種情況下,您可以在 python 或使用查詢中進行計算。

  1. Python(這將從數據庫中加載所有答案,因此如果數據尚未加載到內存中則效率不高)

     q = session.query(Question) for question in q: print(question, question.answers_yes_percentage)
  2. 數據庫:這非常有效,因為您只需運行一個查詢,類似於您正在查看的答案中的單獨查詢,但結果單獨返回並作為 model 上的屬性

     q = session.query(Question, Question.answers_yes_percentage) for question, percentage in q: print(question, percentage)

請注意,以上適用於 sqlalchemy 的 1.4 版本,但可能需要其他語法用於之前的版本。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM