3

PostgreSQL で Flask-SQLAlchemy を使用しています。次の2つのモデルがあります。

class Course(db.Model):
    id = db.Column(db.Integer, primary_key = True )
    course_name =db.Column(db.String(120))
    course_description = db.Column(db.Text)
    course_reviews = db.relationship('Review', backref ='course', lazy ='dynamic')

class Review(db.Model):
    __table_args__ = ( db.UniqueConstraint('course_id', 'user_id'), { } )
    id = db.Column(db.Integer, primary_key = True )
    review_date = db.Column(db.DateTime)#default=db.func.now()
    review_comment = db.Column(db.Text)
    rating = db.Column(db.SmallInteger)
    course_id = db.Column(db.Integer, db.ForeignKey('course.id') )
    user_id = db.Column(db.Integer, db.ForeignKey('user.id') )

少なくとも 2 つのレビューから始めて、最もレビューされているコースを選択したいと考えています。次の SQLAlchemy クエリは、SQLite で正常に機能しました。

most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
          having(func.count(models.Review.course_id) >1) \   .order_by(func.count(models.Review.course_id).desc()).all()

しかし、本番環境で PostgreSQL に切り替えると、次のエラーが表示されます。

ProgrammingError: (ProgrammingError) column "review.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT review.id AS review_id, review.review_date AS review_...
               ^
 'SELECT review.id AS review_id, review.review_date AS review_review_date, review.review_comment AS review_review_comment, review.rating AS review_rating, review.course_id AS review_course_id, review.user_id AS review_user_id, count(review.course_id) AS count_1 \nFROM review GROUP BY review.course_id \nHAVING count(review.course_id) > %(count_2)s ORDER BY count(review.course_id) DESC' {'count_2': 1}

GROUP BY 句に models.Review を追加してクエリを修正しようとしましたが、うまくいきませんでした:

most_rated_courses = db.session.query(models.Review, func.count(models.Review.course_id)).group_by(models.Review.course_id).\
          having(func.count(models.Review.course_id) >1) \.order_by(func.count(models.Review.course_id).desc()).all()

誰でもこの問題で私を助けてくれませんか。どうもありがとう

4

1 に答える 1

5

SQLite と MySQL はどちらも、他のすべての列に GROUP BY を適用せずに集計 (count() など) を持つクエリを許可するという動作をします。これは、標準 SQL に関しては無効です。グループの場合、返される最初のものを選択する必要がありますが、これは基本的にランダムです。

したがって、レビューのクエリは基本的に、個別のコース ID ごとに最初の「レビュー」行を返します。コース ID 3 のように、7 つの「レビュー」行がある場合、グループ内の本質的にランダムな「レビュー」行を選択するだけです。 "course_id=3". 私はあなたが本当に望む答えを集めます。「コース」は、半ランダムに選択されたレビューオブジェクトを取得して「.course」を呼び出すだけで、正しいコースを提供できるため、ここで入手できますが、これは逆の方法です.

しかし、Postgresql のような適切なデータベースにアクセスしたら、正しい SQL を使用する必要があります。「review」テーブルから必要なデータは course_id とカウントだけです。そのため、クエリを実行します (最初に、カウントを実際に表示する必要はないと仮定します。これは 1 分以内です)。

most_rated_course_ids = session.query(
                        Review.course_id,
                    ).\
                    group_by(Review.course_id).\
                    having(func.count(Review.course_id) > 1).\
                    order_by(func.count(Review.course_id).desc()).\
                    all()

しかし、それは Course オブジェクトではありません。ID のリストを取得して、コース テーブルに適用する必要があります。まず、データをロードする代わりに、コース ID のリストを SQL コンストラクトとして保持する必要があります。つまり、クエリをサブクエリに変換して派生テーブルに変換します (単語 .all() を .subquery() に変更します) ):

most_rated_course_id_subquery = session.query(
                    Review.course_id,
                ).\
                group_by(Review.course_id).\
                having(func.count(Review.course_id) > 1).\
                order_by(func.count(Review.course_id).desc()).\
                subquery()

これを Course にリンクする簡単な方法の 1 つは、IN を使用することです。

 courses = session.query(Course).filter(
       Course.id.in_(most_rated_course_id_subquery)).all()

しかし、それは本質的にあなたが探している「ORDER BY」を捨てることになり、コースの結果とともにそれらのカウントを実際に報告する良い方法も提供しません. それを報告し、それによって注文できるように、コースと一緒にそれをカウントする必要があります。このために、"course" テーブルから派生テーブルへの JOIN を使用します。SQLAlchemy は、次のように呼び出すだけで「course_id」外部キーに参加できるほどスマートですjoin()

courses = session.query(Course).join(most_rated_course_id_subquery).all()

次に、カウントを取得するには、それを参照できるように、サブクエリによって返される列にラベルとともに追加する必要があります。

most_rated_course_id_subquery = session.query(
                        Review.course_id,
                        func.count(Review.course_id).label("count")
                    ).\
                    group_by(Review.course_id).\
                    having(func.count(Review.course_id) > 1).\
                    subquery()

courses = session.query(
                Course, most_rated_course_id_subquery.c.count
            ).join(
                most_rated_course_id_subquery
            ).order_by(
                most_rated_course_id_subquery.c.count.desc()
            ).all()

GROUP BY について人々に指摘したい素晴らしい記事であり、この種のクエリはSQL GROUP BY テクニックであり、「A からの選択から結合 (集約/GROUP BY による B のサブクエリ) への選択」パターンの一般的な必要性を指摘しています。

于 2013-08-04T05:08:01.157 に答える