peewee是一个轻量级的ORM。用的是大名鼎鼎的sqlalchemy内核,采用纯python编写,显得十分轻便。为了后续方便查看,在这里简单记录下~~
peewee不仅轻量级,还提供了多种数据库的访问,如SqliteDatabase(file or memory)、MYSQLDatabase、PostgresqlDatabase;
接下来就从API上路吧~~~
1. class fn---To express functions in peewee, use the fn object。
For example:
Peewee expression | Equivalent SQL |
---|---|
fn.Count(Tweet.id).alias('count') | Count(t1."id") AS count |
fn.Lower(fn.Substr(User.username, 1, 1)) | Lower(Substr(t1."username", 1, 1)) |
fn.Rand().alias('random') | Rand() AS random |
fn.Stddev(Employee.salary).alias('sdv') | Stddev(t1."salary") AS sdv |
Functions can be used as any part of a query: select where group_by order_by having update query insert query
# user's username starts with a 'g' or a 'G': fn.Lower(fn.Substr(User.username, 1, 1)) == 'g'
2.表达式支持的操作符
a:
Comparison | Meaning |
---|---|
== | x equals y |
< | x is less than y |
<= | x is less than or equal to y |
> | x is greater than y |
>= | x is greater than or equal to y |
!= | x is not equal to y |
<< | x IN y, where y is a list or query |
>> | x IS y, where y is None/NULL |
% | x LIKE y where y may contain wildcards |
** | x ILIKE y where y may contain wildcards |
Employee.select().where(Employee.salary.between(50000, 60000))note: 由于sqlite的like函数在默认下是大小写不敏感的,如果想实现大小写搜索,需要用’*‘做通配符。3.实现用户自定义的操作
Here is how you might add support for modulo and regexp in SQLite: from peewee import * from peewee import Expression # the building block for expressions OP_MOD = 'mod' OP_REGEXP = 'regexp' def mod(lhs, rhs): return Expression(lhs, OP_MOD, rhs) def regexp(lhs, rhs): return Expression(lhs, OP_REGEXP, rhs) SqliteDatabase.register_ops({OP_MOD: '%', OP_REGEXP: 'REGEXP'}) #添加 %、regexp操作 Now you can use these custom operators to build richer queries: # users with even ids User.select().where(mod(User.id, 2) == 0) # users whose username starts with a number User.select().where(regexp(User.username, '[0-9].*'))4.Joining tablesThere are three types of joins by default: JOIN_INNER (default) JOIN_LEFT_OUTER JOIN_FULL Here are some examples: User.select().join(Blog).where( (User.is_staff == True) & (Blog.status == LIVE)) Blog.select().join(User).where( (User.is_staff == True) & (Blog.status == LIVE)) subquery: staff = User.select().where(User.is_staff == True) Blog.select().where( (Blog.status == LIVE) & (Blog.user << staff))
补充:在没有通过ForeignKeyField产生外键的多个models中,也可以做join操作,如:# No explicit foreign key between these models. OutboundShipment.select().join(InboundShipment, on=( OutboundShipment.barcode == InboundShipment.barcode))
5.Performing advanced queriesTo create arbitrarily complex queries, simply use python’s bitwise “and” and “or” operators: sq = User.select().where( (User.is_staff == True) | (User.is_superuser == True)) The WHERE clause will look something like: WHERE (is_staff = ? OR is_superuser = ?) In order to negate an expression, use the bitwise “invert” operator: staff_users = User.select().where(User.is_staff == True) Tweet.select().where( ~(Tweet.user << staff_users)) This query generates roughly the following SQL: SELECT t1.* FROM blog AS t1 WHERE NOT t1.user_id IN ( SELECT t2.id FROM user AS t2 WHERE t2.is_staff = ?) Rather complex lookups are possible: sq = User.select().where( ((User.is_staff == True) | (User.is_superuser == True)) & (User.join_date >= datetime(2009, 1, 1)) This generates roughly the following SQL: SELECT * FROM user WHERE ( (is_staff = ? OR is_superuser = ?) AND (join_date >= ?))6.Aggregating records
#Suppose you have some users and want to get a list of them along with the count of tweets each has made. First I will show y#ou the shortcut: query = User.select().annotate(Tweet) This is equivalent to the following: query = User.select( User, fn.Count(Tweet.id).alias('count') ).join(Tweet).group_by(User) #You can also specify a custom aggregator. In the following query we will annotate the users with the date of their most rece#nt tweet: query = User.select().annotate( Tweet, fn.Max(Tweet.created_date).alias('latest')) #Conversely, sometimes you want to perform an aggregate query that returns a scalar value, like the “max id”. Queries like #this can be executed by using the aggregate() method: most_recent_tweet = Tweet.select().aggregate(fn.Max(Tweet.created_date))7. Window functions
#peewee comes with basic support for SQL window functions, which can be created by calling fn.over() and passing in your parti#tioning or ordering parameters. # Get the list of employees and the average salary for their dept. query = (Employee .select( Employee.name, Employee.department, Employee.salary, fn.Avg(Employee.salary).over( partition_by=[Employee.department])) .order_by(Employee.name)) # Rank employees by salary. query = (Employee .select( Employee.name, Employee.salary, fn.rank().over( order_by=[Employee.salary])))有待继续考究啊~~ click me:<a target=_blank href="http://www.postgresql.org/docs/9.1/static/tutorial-window.html">postgresql docs.</a>8.How to optimize a query?#We can do this pretty easily: for tweet in Tweet.select().order_by(Tweet.created_date.desc()).limit(10): print '%s, posted on %s' % (tweet.message, tweet.user.username) #Looking at the query log, though, this will cause 11 queries: #1 query for the tweets #1 query for every related user (10 total) #This can be optimized into one query very easily, though: tweets = Tweet.select(Tweet, User).join(User) for tweet in tweets.order_by(Tweet.created_date.desc()).limit(10): print '%s, posted on %s' % (tweet.message, tweet.user.username) #Will cause only one query that looks something like this: SELECT t1.id, t1.message, t1.user_id, t1.created_date, t2.id, t2.username FROM tweet AS t1 INNER JOIN user AS t2 ON t1.user_id = t2.id ORDER BY t1.created_date desc LIMIT 10<span style="font-family: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace; font-size: 18px; line-height: 1.5; background-color: rgb(255, 255, 255);"></span><h3 style="box-sizing: border-box; margin-top: 0px; font-family: 'Roboto Slab', ff-tisa-web-pro, Georgia, Arial, sans-serif; font-size: 20px; color: rgb(64, 64, 64); background-color: rgb(252, 252, 252);">Speeding up simple select queries---naive</h3><p style="box-sizing: border-box; line-height: 24px; margin-top: 0px; margin-bottom: 24px; font-size: 16px; color: rgb(64, 64, 64); font-family: Lato, proxima-nova, 'Helvetica Neue', Arial, sans-serif; background-color: rgb(252, 252, 252);">Complex select queries can get a performance boost (especially when iterating over large result sets) by calling <a target=_blank class="reference internal" href="http://peewee.readthedocs.org/en/latest/peewee/api.html#SelectQuery.naive" title="SelectQuery.naive" style="box-sizing: border-box; color: rgb(155, 89, 182); text-decoration: none;"><tt class="xref py py-meth docutils literal" style="box-sizing: border-box; font-family: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace; font-size: 12px; white-space: nowrap; max-width: 100%; background-color: rgb(255, 255, 255); border: 1px solid rgb(225, 228, 229); padding: 0px 5px; color: rgb(41, 128, 185); overflow-x: auto; background-position: initial initial; background-repeat: initial initial;"><span class="pre" style="box-sizing: border-box;"><strong>naive()</strong></span></tt></a>. This method simply patches all attributes directly from the cursor onto the model. For simple queries this will have no noticeable impact. The <span style="box-sizing: border-box;">only</span> difference is when multiple tables are queried, as in the previous example:</p><div class="highlight-python" style="box-sizing: border-box; border: 1px solid rgb(225, 228, 229); padding: 0px; overflow-x: auto; margin: 1px 0px 24px; color: rgb(64, 64, 64); font-family: Lato, proxima-nova, 'Helvetica Neue', Arial, sans-serif; font-size: 16px;"><div class="highlight" style="box-sizing: border-box; border: none; padding: 0px; overflow-x: auto; background-image: none; margin: 0px; background-position: initial initial; background-repeat: initial initial;"><pre style="box-sizing: border-box; font-family: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace; font-size: 12px; margin-top: 0px; margin-bottom: 0px; padding: 12px; line-height: 1.5; overflow: auto;"><span class="c" style="box-sizing: border-box; color: rgb(153, 153, 136); font-style: italic;"># above example</span> <span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">tweets</span> <span class="o" style="box-sizing: border-box; font-weight: bold;">=</span> <span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">Tweet</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">select</span><span class="p" style="box-sizing: border-box;">(</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">Tweet</span><span class="p" style="box-sizing: border-box;">,</span> <span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">User</span><span class="p" style="box-sizing: border-box;">)</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">join</span><span class="p" style="box-sizing: border-box;">(</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">User</span><span class="p" style="box-sizing: border-box;">)</span> <span class="k" style="box-sizing: border-box; font-weight: bold;">for</span> <span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">tweet</span> <span class="ow" style="box-sizing: border-box; font-weight: bold;">in</span> <span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">tweets</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">order_by</span><span class="p" style="box-sizing: border-box;">(</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">Tweet</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">created_date</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">desc</span><span class="p" style="box-sizing: border-box;">())</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">limit</span><span class="p" style="box-sizing: border-box;">(</span><span class="mi" style="box-sizing: border-box; color: rgb(0, 153, 153);">10</span><span class="p" style="box-sizing: border-box;">):</span> <span class="k" style="box-sizing: border-box; font-weight: bold;">print</span> <span class="s" style="box-sizing: border-box; color: rgb(221, 17, 68);">'</span><span class="si" style="box-sizing: border-box; color: rgb(221, 17, 68);">%s</span><span class="s" style="box-sizing: border-box; color: rgb(221, 17, 68);">, posted on </span><span class="si" style="box-sizing: border-box; color: rgb(221, 17, 68);">%s</span><span class="s" style="box-sizing: border-box; color: rgb(221, 17, 68);">'</span> <span class="o" style="box-sizing: border-box; font-weight: bold;">%</span> <span class="p" style="box-sizing: border-box;">(</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">tweet</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">message</span><span class="p" style="box-sizing: border-box;">,</span> <span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">tweet</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">user</span><span class="o" style="box-sizing: border-box; font-weight: bold;">.</span><span class="n" style="box-sizing: border-box; color: rgb(51, 51, 51);">username</span><span class="p" style="box-sizing: border-box;">)</span>And here is how you would do the same if using a naive query:
# very similar query to the above -- main difference is we're # aliasing the blog title to "blog_title" tweets = Tweet.select(Tweet, User.username).join(User).naive() for tweet in tweets.order_by(Tweet.created_date.desc()).limit(10): print '%s, posted on %s' % (tweet.message, tweet.username)To iterate over raw tuples, use the tuples() method:
stats = Stat.select(Stat.url, fn.Count(Stat.url)).group_by(Stat.url).tuples() for stat_url, count in stats: print stat_url, countTo iterate over dictionaries, use the dicts() method:
stats = Stat.select(Stat.url, fn.Count(Stat.url).alias('ct')).group_by(Stat.url).dicts() for stat in stats: print stat['url'], stat['ct']<span style="font-family: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace; font-size: 18px; line-height: 1.5; background-color: rgb(255, 255, 255);">9.</span><span style="font-family: Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace; font-size: 18px; line-height: 1.5; background-color: rgb(255, 255, 255);">Pre-fetching related instances</span>
As a corollary to the previous section in which we selected models going “up” the chain, in this section I will show you to select models going “down” the chain in a 1 -> many relationship. For example, selecting users and all of their tweets.
Assume you want to display a list of users and all of their tweets:
for user in User.select(): print user.username for tweet in user.tweets: print tweet.message
This will generate N queries, however - 1 for the users, and then N for each user’s tweets. Instead of doing N queries, we can do 2 instead:
-- first query -- SELECT t1.id, t1.username FROM users AS t1; -- second query -- SELECT t1.id, t1.message, t1.user_id FROM tweet AS t1 WHERE t1.user_id IN ( SELECT t2.id FROM users AS t2)
users = User.select() tweets = Tweet.select() users_prefetch = prefetch(users, tweets) for user in users_prefetch: print user.username # note we are using a different attr -- it is the "related name" + "_prefetch" for tweet in user.tweets_prefetch: print tweet.message
# let's say we have users -> photos -> comments / tags # such that a user posts photos, assigns tags to those photos, and all those # photos can be commented on users = User.select().where(User.active == True) photos = Photo.select().where(Photo.published == True) tags = Tag.select() comments = Comment.select().where(Comment.is_spam == False, Comment.flags < 3) # this will execute 4 queries, one for each model users_prefetch = prefetch(users, photos, tags, comments) for user in users_prefetch: print user.username for photo in user.photo_set_prefetch: print 'photo: ', photo.filename for tag in photo.tag_set_prefetch: print 'tagged with:', tag.tag for comment in photo.comment_set_prefetch: print 'comment:', comment.comment
10.Query evaluation
In order to execute a query, it is always necessary to call the execute() method.
To get a better idea of how querying works let’s look at some example queries and their return values:
>>> dq = User.delete().where(User.active == False) # <-- returns a DeleteQuery >>> dq <peewee.DeleteQuery object at 0x7fc866ada4d0> >>> dq.execute() # <-- executes the query and returns number of rows deleted 3 >>> uq = User.update(active=True).where(User.id > 3) # <-- returns an UpdateQuery >>> uq <peewee.UpdateQuery object at 0x7fc865beff50> >>> uq.execute() # <-- executes the query and returns number of rows updated 2 >>> iq = User.insert(username='new user') # <-- returns an InsertQuery >>> iq <peewee.InsertQuery object at 0x7fc865beff10> >>> iq.execute() # <-- executes query and returns the new row's PK 8