为了自学如何编程,我正在制作一个小型网络应用程序(Flask,SQLAlchemy,Jijna)来显示我从亚马逊订购过的所有书籍。
在“barest bones”可能的方式中,我正在努力学习如何复制http://pinboard.in - 那是我的典范; MaciejCegłowski是一个直接的G ...我不知道他的网站如何运行如此快死:我可以加载160个书签条目 - 所有与相关的标签 -in,我不知道,500毫秒? ......这就是为什么我知道我正在做一些非常非常错误的事情,如下所述。 (如果可以,我会付钱给他指导我.lulz。)
无论如何,我在books
班级和tag
班级之间建立了多对多关系,以便用户可以(1)点击book
并查看所有tags
,以及(2)点击tag
并查看所有相关图书。这是我的表架构:
以下是两个类之间关系的代码:
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
表(即“全表扫描”)。
显然,我是一个完整的菜鸟 - 我已经编程了大约3个月。它只是为了让事情变得有效,但我意识到我的知识库中存在很大的差距,我正在努力填补。
关于那个蝙蝠,我可以理解这是非常低效的,每本新书,代码都在遍历整个关联表(如果这确实发生了什么,我相信它是)。我想我需要对assoc
表进行集群(?)或排序(?),这样一旦我检索book with book_id == 1
的所有标记,我再也不会用{{1}“检查”这些行在book_id == 1
表中。
换句话说,我认为正在发生的事情是这种情况(在计算机语言中):
assoc
表中book_id == 1
的书是如何标记的books
表assoc
表中的book_id
是否等于assoc
?1
是什么? ... [然后计算机转到tag_id
表获取tag
,并将其返回给浏览器] tag_name
表中book_id
等于assoc
?1
表来查找assoc
没有了...... 然后,一旦我们到达book_id == 1
book_id == 2
,计算机真的很生气:
books table
book_id == 2
表assoc
? (我知道事实并非如此!但我还是要检查,因为我的程序员是个笨蛋...)所以问题是,我可以(1)以某种方式对book_id == 2
表进行排序(?)或集群(?),以确保通过assoc
表进行更“智能”的遍历,或者,作为我的朋友建议,我(2)“学会编写好的SQL查询”吗? (注意,我从来没有学过SQL,因为我一直用SQLAlchemy处理所有事情......该死的炼金术士......秘密地把他们的魔法包裹起来等等。)
感谢您的任何意见。如果您有任何建议可以帮助我改进我在stackoverflow上提问的方式(这是我的第一篇文章!),请告诉我。
答案 0 :(得分:1)
大部分答案都在问题中。
在第一个示例中,当您遍历books
表时,将执行SQL查询。在第二个示例中,为每个assoc
执行单独的Book
查询。因此,大约400个SQL查询非常耗时。如果设置SQLALCHEMY_ECHO config参数:
app.config['SQLALCHEMY_ECHO'] = True
或者您可以安装Flask-DebugToolbar并在网络界面中查看这些查询。
解决此问题的最佳方法是学习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
然后尝试在SQLAlchemy代码中重写它,然后在传递给HTML渲染器之前按书分组:
# 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 %}
<a href="/tag/{{ tag }}" class="tag-link">{{ tag }}</a>
{% 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允许您进行此类查询:
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)
模板具有以下结构:
{% 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 %}
<a href="/tag/{{ tag }}" class="tag-link">{{ tag }}</a>
{% endfor %}
{{ book.date_read }}
{% if book.comment %}
<p>{{ book.comment }}</p>
{% else %}
<!-- print nothing -->
{% endif %}
</li>
{% endfor %}
答案 1 :(得分:1)
以下实施改编自@ Sergey-Shubin,是这个问题的可行解决方案:
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
@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)
答案 2 :(得分:0)
如果您的查询包含大量书籍,则在单独的SQL语句中逐个获取每本书的标记将会消耗您在网络I / O中的响应时间。
一种优化方法,如果你知道你总是需要这个查询的标签,那就是提示SQLAlchemy通过join或子查询来获取一个查询中的所有依赖标签。
我没有看到您的查询,但我的猜测是子查询加载最适合您的用例:
session.query(Book).options(subqueryload('tagged')).filter(...).all()