2017-02-21 12 views
0

プログラミング方法を自分で教えようと、今までに注文したことのあるすべての書籍を表示するために小さなWebアプリケーション(Flask、SQLAlchemy、Jijna)を作成していますAmazonから。多対多の関係でクエリの速度を向上させよう

"barest bones"の可能な方法で、私はhttp://pinboard.inを複製する方法を学ぼうとしています。これは私のパラゴンです。 MaciejCegłowskiはまっすぐなGです...私は彼のサイトがどんなに速く走っているのか分かりません。私は160のブックマークエントリを読み込むことができます。すべてが関連タグ -in、dunno、500 ms? ...これは、私が下で議論されるように、ひどく間違って何かをやっているのを知っている理由です。

いずれにしても、booksクラスと私のtagクラスの間に多対多の関係を作成しました。これは、ユーザーが(1) bookをクリックし、tagsを参照してください。また、(2)tagをクリックし、関連するすべての書籍を参照してください。ここに私のテーブルのアーキテクチャは次のとおりです。

Entity relationship diagram

ここでは、2つのクラス間の関係のためのコードは次のとおりです。

assoc = db.Table('assoc', 
    db.Column('book_id', db.Integer, db.ForeignKey('books.book_id')), 
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.tag_id')) 
) 

class Book(db.Model): 
    __tablename__ = 'books' 
    book_id = db.Column(db.Integer, primary_key=True) 
    title = db.Column(db.String(120), unique=True) 
    auth = db.Column(db.String(120), unique=True) 
    comment = db.Column(db.String(120), unique=True) 
    date_read = db.Column(db.DateTime) 
    era = db.Column(db.String(36)) 
    url = db.Column(db.String(120)) 
    notable = db.Column(db.String(1)) 

    tagged = db.relationship('Tag', secondary=assoc, backref=db.backref('thebooks',lazy='dynamic')) 

    def __init__(self, title, auth, comment, date_read, url, notable): 
     self.title = title 
     self.auth = auth 
     self.comment = comment 
     self.date_read = date_read 
     self.era = era 
     self.url = url 
     self.notable = notable 

class Tag(db.Model): 
    __tablename__ = 'tags' 
    tag_id = db.Column(db.Integer, primary_key=True) 
    tag_name = db.Column(db.String(120)) 

問題

私はbooksテーブルを反復処理した場合のみ(〜 400行)、照会が実行され、ブラウザーに落雷のスピードでレンダリングされます。問題はない。

{% for i in book_query %} 
    <li> 
     {{i.notable}}{{i.notable}} 
     <a href="{{i.url}}">{{i.title}}</a>, {{i.auth}} 
     <a href="/era/{{i.era}}">{{i.era}}</a> {{i.date_read}} 
     {% if i.comment %} 
      <p>{{i.comment}}</p> 
     {% else %} 
      <!-- print nothing --> 
     {% endif %} 
    </li> 
{% endfor %} 

しかし、私は次のように私はfor loopを入れ子にすることで、コードを変更する、ブックに関連付けられている任意およびすべてのタグを表示したい、場合:

{% for i in book_query %} 
    <li> 
     {{i.notable}}{{i.notable}} 
     <a href="{{i.url}}">{{i.title}}</a>, {{i.auth}} 
     <a href="/era/{{i.era}}">{{i.era}}</a> 
     {% for ii in i.tagged %} 
      <a href="/tag/{{ii.tag_name}}">{{ii.tag_name}}</a> 
     {% endfor %} 
     {{i.date_read}} 
     {% if i.comment %} 
      <p>{{i.comment}}</p> 
     {% else %} 
      <!-- print nothing --> 
     {% endif %} 
    </li> 
    {% endfor %} 

クエリが遅く大幅(約20秒かかります)。私の理解では、bookテーブルのすべての行に対して、assocテーブル全体(つまり、「フルテーブルスキャン」)を繰り返すため、これが起こっていると私は理解しています。

議論(または、「私が考えることは起こっている」)明らかに

、私は完全にnoobのは - 私は〜3ヶ月間のプログラミングされています。物事を動かすことを動機づけていますが、知識ベースに大きなギャップがあることはわかります。右そのバットオフ

、私はそれがそれぞれの新しい本で、コードが全体関連付けテーブルを反復されることを非常に非効率的だということを理解することができる(つまり、私はそれがあると信じている、何が起こっているのか、実際の場合)。 assocテーブルをクラスタ化(?)またはソート(?)する必要があると思います.テーブル内のbook_id == 1の行を再度チェックすることはありません。言い換えれば

は、私が起こっていると思いますが、この(computerspeakで)です:

  • ああ、彼はbooksテーブル内book_id == 1と本が
  • オーケータグ付けされているか知りたい、私を聞かせてassocテーブル
  • ロー#1 ... assocテーブルのbook_id1に等しいですか?
  • さて、そうです。 Row#1にはtag_idは何ですか? ... [コンピュータはtagテーブルにアクセスしてtag_nameを取得し、ブラウザに返します]
  • 行#2 ... book_idassocのテーブルは1に等しいですか?
  • いいえ、いいえ、行番号3に行きます。
  • 私のプログラマーは愚かで、この表を何らかの方法で並べ替えたり索引付けしなかったので、私は我々はbooks tablebook_id == 2に得れば、コンピュータが本当に怒る、その後

...おそらくこれ以上がないときbook_id == 1を探して全体assocテーブルを通過する必要があります:

  • さて、彼は一緒に行くすべてのタグを知りたいbook_id == 2
  • さて、私はすでにこれをチェックしませんでした...私は... assocテーブル
  • 行#1に行く秒を待ってみましょう?聖なること#t、私はこれを何度も繰り返さなければならないのですか?
  • 大丈夫...いいえ...行番号1 ...はbook_id == 2ですか?

質問

を(...私はそれがない知っている!しかし、私は私のプログラマはDUM-DUMあるので、とにかくチェックしなければならない)そこで問題は、私は(1)ソート(することができますか? )またはクラスタ(?)のassocテーブルをassocテーブルを介してより知的なトラバーサルを保証する何らかの方法で、または私の友人が示唆したようにI(2)は「良いSQLクエリを書くことを学ぶ」ですか? (注、私は気錬金術... SQLAlchemyのですべてを扱うしてきたので、私は秘密やその他もろもろでその魔法をenshrouding ... SQLを学んだことがありません。)

最後の言葉任意の入力のための

感謝。私がstackoverflowで質問する方法を改善する助けとなる提案があれば(これは私の最初の投稿です!)私に教えてください。

答えて

1

答えのほとんどは問題です。

最初の例では、booksテーブルを反復するとSQLクエリが実行されます。第2の例では、Bookごとに個別のassocクエリが実行されます。したがって、それは非常に時間がかかる約400のSQLクエリです。

app.config['SQLALCHEMY_ECHO'] = True 

それともFlask-DebugToolbarをインストールし、Webインターフェイスにこれらのクエリを見ることができます:あなたはSQLALCHEMY_ECHO configパラメータを設定する場合は、アプリのデバッグログでそれらを表示することができます。

この問題を処理する最善の方法は、SQLの基本を学ぶことです。アプリケーションが大きくなると、その必要があります。純粋なSQLでより最適化されたクエリを作成してみてください。あなたのケースのために、それは次のようになります。

SELECT books.*, tags.tag_name FROM books 
JOIN assoc ON assoc.book_id = books.book_id 
JOIN tags ON assoc.tag_id = tags.tag_id 

そして、HTMLレンダラに渡す前にブックでグループそして、SQLAlchemyのコードでそれを書き換えしようとすると:

# Single query to get all books and their tags 
query = db.session.query(Book, Tag.tag_name).join('tagged') 
# Dictionary of data to be passed to renderer 
books = {} 
for book, tag_name in query: 
    book_data = books.setdefault(book.book_id, {'book': book, 'tags': []}) 
    book_data['tags'].append(tag_name) 
# Rendering HTML 
return render_template('yourtemplate.html', books=books) 

テンプレートコードは次のようになります。

{% for book in books %} 
<li> 
    {{ book.book.notable }}{{ book.book.notable }} 
    <a href="{{ book.book.url }}">{{ book.book.title }}</a>, {{ book.book.auth }} 
    <a href="/era/{{ book.book.era }}">{{ book.book.era }}</a> 
    {% for tag in book.tags %} 
    &nbsp;<a href="/tag/{{ tag }}" class="tag-link">{{ tag }}</a>&nbsp; 
    {% endfor %} 
    {{ book.book.date_read }} 
    {% if book.book.comment %} 
     <p>{{ book.book.comment }}</p> 
    {% else %} 
     <!-- print nothing --> 
    {% endif %} 
</li> 
{% endfor %} 

別のアプローチ

データベースを使用すると、クエリを書くことができるのPostgreSQLの場合:この場合は

SELECT books.title, books.auth (...), array_agg(tags.tag_name) as book_tags FROM books 
JOIN assoc ON assoc.book_id = books.book_id 
JOIN tags ON assoc.tag_id = tags.tag_id 
GROUP BY books.title, books.auth (...) 

あなたは配列としてすでに集約タグで書籍データを取得します。 SQLAlchemyのは、あなたが、このようなクエリを行うことができます:あなたのクエリが別々に1によって各書籍1のタグをフェッチ、たくさんの本を持っている場合

{% for book, tags in books %} 
<li> 
    {{ book.notable }}{{ book.notable }} 
    <a href="{{ book.url }}">{{ book.title }}</a>, {{ book.auth }} 
    <a href="/era/{{ book.era }}">{{ book.era }}</a> 
    {% for tag in tags %} 
    &nbsp;<a href="/tag/{{ tag }}" class="tag-link">{{ tag }}</a>&nbsp; 
    {% endfor %} 
    {{ book.date_read }} 
    {% if book.comment %} 
     <p>{{ book.comment }}</p> 
    {% else %} 
     <!-- print nothing --> 
    {% endif %} 
</li> 
{% endfor %} 
0

from sqlalchemy import func 

books = db.session.query(Book, func.array_agg(Tag.tag_name)).\ 
    join('tagged').group_by(Book).all() 
return render_template('yourtemplate.html', books=books) 

そして、テンプレートには、次の構造を有しますSQL文はネットワークI/Oの応答時間を無駄にします。

これを最適化する方法の1つは、このクエリのタグが常に必要であることがわかっている場合、SQLAlchemyが1つのクエリですべての依存タグを結合またはサブクエリで取得することです。

私はあなたのクエリが表示されていないが、私の推測では、サブクエリの負荷があなたのユースケースに最適に働くだろうです:

session.query(Book).options(subqueryload('tagged')).filter(...).all() 
1

次の実装、@セルゲイ・シュービンから適応への実行可能なソリューションでしたこの質問:

クラス&テーブルの関連付け宣言

assoc = db.Table('assoc', 
    db.Column('book_id', db.Integer, db.ForeignKey('books.book_id')), 
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.tag_id')) 
    ) 

class Book(db.Model): 
    __tablename__ = 'books' 
    book_id = db.Column(db.Integer, primary_key=True) 
    title = db.Column(db.String(120), unique=True) 
    auth = db.Column(db.String(120), unique=True) 
    comment = db.Column(db.String(120), unique=True) 
    date_read = db.Column(db.DateTime) 
    era = db.Column(db.String(36)) 
    url = db.Column(db.String(120)) 
    notable = db.Column(db.String(1))  

    tagged = db.relationship('Tag', secondary=assoc, backref=db.backref('thebooks',lazy='dynamic')) 

class Tag(db.Model): 
    __tablename__ = 'tags' 
    tag_id = db.Column(db.Integer, primary_key=True) 
    tag_name = db.Column(db.String(120)) 

def construct_dict(query): 
     books_dict = {} 
     for each in query: # query is {<Book object>, <Tag object>} in the style of assoc table - therefore, must make a dictionary bc of the multiple tags per Book object 
      book_data = books_dict.setdefault(each[0].book_id, {'bookkey':each[0], 'tagkey':[]}) # query is a list of like this {index-book_id, {<Book object>}, {<Tag object #1>, <Tag object #2>, ... }} 
      book_data['tagkey'].append(each[1]) 
     return books_dict 

ルート、SQL-錬金術クエリ

@app.route('/query') 
def query(): 
    query = db.session.query(Book, Tag).outerjoin('tagged') # query to get all books and their tags 
    books_dict = construct_dict(query) 

    return render_template("query.html", query=query, books_dict=books_dict)