http://docs.sqlalchemy.org/en/latest/core/tutorial.html

Google 翻译了一下 SQLAlchemy Expression Language, 翻译不通的就凭感觉改, 关键还是代码.

先定一个小目标, 基础的增删改查过一遍, 把当前项目中的裸 SQL 全部替换为 SQLAlchemy.

1. SQLAlchemy 简介

SQLAlchemy, 目前 Python ORM 当之无愧的第一把交椅. 分为两部分, 第一部分是 SQL Expression Language, 可以认为是 DBAPI, 为不同的数据库提供统一的操作抽象, 第二部分是 ORM, 建立在 SQL Expression Language 的基础之上. 两者都要学, 之前 ORM 用的比较多, 但实现复杂的逻辑, 感觉不如直接写 SQL. SQL Expression Language 提供了这样的灵活性, 有一定的入门门槛, 掌握之后收益多多.

2. 版本检查

In [1]: import sqlalchemy

In [2]: sqlalchemy.__version__
Out[2]: '1.1.4'

3. 连接数据库

In [3]: from sqlalchemy import create_engine

In [6]: engine = create_engine('sqlite:///:memory:', encoding='utf-8',echo=True)
...:

这算是第一个不太舒服的地方, 我觉得 create_engine 比较陌生. 不如 DBUtils 里面数据库连接池函数 PooledDB 函数名符合直觉.

create_engine 返回一个 Engine 实例, Engine 中内建一个数据库连接池, 它是操作数据库的核心接口, 为不同数据库和 API 提供统一的抽象.

create_engine 的第一个参数是 Database Urls, 以 MySQL 为例, mysql-python 是其默认DBAPI. SQLite 是 Python 自带数据库, 就不介绍了.

# default
engine = create_engine('mysql://scott:tiger@localhost/foo') # mysql-python
engine = create_engine('mysql+mysqldb://scott:tiger@localhost/foo') # MySQL-connector-python
engine = create_engine('mysql+mysqlconnector://scott:tiger@localhost/foo') # OurSQL
engine = create_engine('mysql+oursql://scott:tiger@localhost/foo')

4. 定义和创建 Tables

这一步本质是建立 SQLAlchemy 与数据表的映射关系.

SQL Expression Language 大多数情况根据表中的字段构建它的表达式. 一个 Column 对象表示一个字段, 每一个 Column 对象都会跟一个 Table 对象相关联. Table 对象和它子对象的集合称为 database metadata, MetaData 对象可以认为是记录 Table 对象的目录. 通过 table reflection(反射) 还可以对已经存在数据库提取其 Table 对象集合.

这是第二个不舒服的地方, metadata 元数据, 既然是类似 Table 的目录, 为什么不干脆叫 catalog. 其实看英英翻译, metadata 的释义是 a set of data that describes and gives information about other data. 那这就说通了.

In [9]: from sqlalchemy import Table, Column, Integer, String, MetaData, Foreign
...: Key In [10]: metadata = MetaData() In [11]: users = Table('users', metadata,
...: Column('id', Integer, primary_key=True),
...: Column('name', String),
...: Column('fullname', String),
...: ) In [12]: addresses = Table('addresses', metadata,
...: Column('id', Integer, primary_key=True),
...: Column('user_id', Integer, ForeignKey('users.id')),
...: Column('email_address', String, nullable=False)
...: )

这里面有两种关系, 第一个种 Column 和 Table, 第二种是  Table 和 MetaData, 实例化的 MetaData 和 Column 都作为 Table 实例化的参数, 以此相关联.

Table 实例化的第一个参数是数据表名, 第二个参数是 MetaData 对象, 之后的是 Column 对象. 另外, Column 的实例化参数, 基本上继承了 SQL, 符合直觉和习惯, 我最开始不理解的一点也只是 metadata.

先不考虑从已有的数据库加载数据表, 假设要创建数据表. 现在有一个 metadata, 有一个 engine, 那么是谁来创建? metadata 是 Tables 的目录, engine 指向我们当前的数据库, 直觉上应该由谁来发起创建数据表的操作?

In [13]: metadata.create_all(engine)
2017-02-05 20:35:52,499 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1

这是第三处不舒服的地方, create_all 的方法在 metadata 上, 而 engine 是该函数的参数. 如果是 engine.create_all(metadata.tables) 会不会更容易接受一些. 或者 metadata.tables.create_all(engine) 也能接受. 关键还是因为 metadata 的中文翻译元数据太抽象导致的. 这些不舒服的细节对于初学者学习阻力很大, 我倾向于想明白缘由, 哪怕没有也编一个能自圆其说的, 情感上能接受了, 就比死记硬背好多了.

最后, 如果使用 MySQL 数据库, Column('email_address', String) 会报错, String 要加长度参数, 比如 String(50).

5. 插入表达式

讲如何构造插入表达式

In [15]: ins = users.insert()

In [16]: ins
Out[16]: <sqlalchemy.sql.dml.Insert object at 0x110a61ad0> In [17]: str(ins)
Out[17]: 'INSERT INTO users (id, name, fullname) VALUES (:id, :name, :fullname)'

user.insert() 从字面上理解是执行了插入动作, 参数为空, 返回值是一个对象, 这个类定义了 __str__ 方法.

注意到, 默认情况下是全字段插入, 通常 id 是个自增字段, 只需要插入 name 和 fullname. 这时候需要 values 方法进行限制.

In [18]: ins = users.insert().values(name='jack', fullname='Jack Jones')

In [19]: str(ins)
Out[19]: 'INSERT INTO users (name, fullname) VALUES (:name, :fullname)' In [20]: ins.compile().params
Out[20]: {'fullname': 'Jack Jones', 'name': 'jack'}

因为返回值是对象, 所以可以链式调用, 注意到 __str__ 方法并没有用参数格式化 SQL, 参数的值是存储在 ins 对象中, 但是仅在 SQL 执行的时候才会进行格式化, 参数的值可以在编译后看到.

虽然 user.insert() 字面上是执行了插入动作, 但是动作并没有执行, 更像是只构造了 SQL, 看来需要一个媒介来承载这个动作.

6. 执行单条语句

先介绍执行的一般方法, 前面讲到 engine 内建一个数据库连接池, 现在要获取一个数据库连接, 就使用 connect 方法.

In [21]: conn = engine.connect()

In [22]: conn
Out[22]: <sqlalchemy.engine.base.Connection at 0x110a8ba50>

connect 方法返回一个 Connection 对象, Connection 对象有一个 execute 方法, 把 Insert 对象作为参数传递给它, 就可以执行 SQL 语句.

In [23]: result = conn.execute(ins)
2017-02-06 09:38:37,630 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, fullname) VALUES (?, ?)
2017-02-06 09:38:37,631 INFO sqlalchemy.engine.base.Engine ('jack', 'Jack Jones')
2017-02-06 09:38:37,637 INFO sqlalchemy.engine.base.Engine COMMIT

说一下返回值 result, 这是一个 ResultProxy 对象, 就类似于 MySQLdb 里的 Cursor, ResultProxy 对象存储了一些比较重要的信息, 就 insert 而言, 可以看到本次插入生成的 primary_key

In [24]: result.inserted_primary_key
Out[24]: [1]

7. 执行多条语句

使用 value 来构造 insert 的方法其实不常用, 下面给出更普通的方法

In [25]: ins = users.insert()

In [26]: conn.execute(ins, id=2, name='wendy', fullname='Wendy Williams')
2017-02-06 16:45:09,498 INFO sqlalchemy.engine.base.Engine INSERT INTO users (id, name, fullname) VALUES (?, ?, ?)
2017-02-06 16:45:09,501 INFO sqlalchemy.engine.base.Engine (2, 'wendy', 'Wendy Williams')
2017-02-06 16:45:09,511 INFO sqlalchemy.engine.base.Engine COMMIT
Out[26]: <sqlalchemy.engine.result.ResultProxy at 0x110a8bb90>

批量执行, 实际调用的是 executemany

In [28]: conn.execute(addresses.insert(), [
...: {'user_id': 1, 'email_address': 'jack@yahoo.com'},
...: {'user_id': 1, 'email_address': 'jack@msn.com'},
...: {'user_id': 2, 'email_address': 'www@www.org'},
...: {'user_id': 2, 'email_address': 'wendy@aol.com'},
...: ])
2017-02-06 16:51:19,314 INFO sqlalchemy.engine.base.Engine INSERT INTO addresses (user_id, email_address) VALUES (?, ?)
2017-02-06 16:51:19,314 INFO sqlalchemy.engine.base.Engine ((1, 'jack@yahoo.com'), (1, 'jack@msn.com'), (2, 'www@www.org'), (2, 'wendy@aol.com'))
2017-02-06 16:51:19,316 INFO sqlalchemy.engine.base.Engine COMMIT
Out[28]: <sqlalchemy.engine.result.ResultProxy at 0x110b6e410>

有两点需要注意, 第一, 插入的字典列表, keys 要保持一致, 因为 SQLAlchemy 默认使用第一个字典作为模板; 第二, insert(), update(), delete() 都支持批量执行.

8. Selecting

In [29]: from sqlalchemy.sql import select

In [30]: s = select([users])

In [31]: result = conn.execute(s)
2017-02-06 17:04:32,831 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname
FROM users
2017-02-06 17:04:32,832 INFO sqlalchemy.engine.base.Engine ()

从 sqlalchemy.sql import select, 这一点与 insert 不同. 并且注意到 select 的参数是一个列表.

返回值 result 仍然是个 ResultProxy, 并且同样具有 fetchone() 和 fetchoneall() 方法. 不过, result 是可遍历的.

In [32]: for row in result:
...: print(row)
...:
(1, u'jack', u'Jack Jones')
(2, u'wendy', u'Wendy Williams') In [33]: for row in result:
...: print(row)
...:

值得注意的是, result 与 cursor 类似, 第二次遍历, 发现结果为空.

在使用 MySQLdb 的时候, 通常会在 cursorclass 做一个选择, 是 MySQLdb.cursors.DictCursor 还是默认的 MySQLdb.cursors.BaseCursor, 前者返回的是字典元组, 后者返回的是元组元组. 现在, 使用 SQLAlchemy, 可以同时支持两种方式.

In [34]: result = conn.execute(s)
2017-02-06 17:47:04,263 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname
FROM users
2017-02-06 17:47:04,265 INFO sqlalchemy.engine.base.Engine () In [35]: row = result.fetchone() In [36]: print("name:", row['name'], "; fullname:", row['fullname'])
('name:', u'jack', '; fullname:', u'Jack Jones') In [37]: row = result.fetchone() In [38]: print("name:", row[1], "; fullname", row[2])
('name:', u'wendy', '; fullname', u'Wendy Williams')

row 的 key, 除了显示的字符串, 其实可以直接使用 Column 对象

In [40]: for row in conn.execute(s):
...: print("name:", row[users.c.name], "; fullname:", row[users.c.fullna
...: me])
...:
2017-02-06 17:53:59,481 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname
FROM users
2017-02-06 17:53:59,482 INFO sqlalchemy.engine.base.Engine ()
('name:', u'jack', '; fullname:', u'Jack Jones')
('name:', u'wendy', '; fullname:', u'Wendy Williams')

有个好的习惯是, result 对象用完随手关闭, 甚至可以说必要, 因为对于一些后端数据库, 对此很挑剔.

In [41]: result.close()

如果要指定 select 的字段, 可以使用 Column 对象

In [42]: s = select([users.c.name, users.c.fullname])

In [43]: result = conn.execute(s)
2017-02-06 19:57:08,927 INFO sqlalchemy.engine.base.Engine SELECT users.name, users.fullname
FROM users
2017-02-06 19:57:08,928 INFO sqlalchemy.engine.base.Engine () In [44]: for row in result:
...: print(row)
...:
(u'jack', u'Jack Jones')
(u'wendy', u'Wendy Williams') In [45]: result.close()

现在有一个问题, 如果把不同的 Table 传给 select 会怎样?

In [46]: for row in conn.execute(select([users, addresses])):
...: print(row)
...:
2017-02-06 20:15:27,877 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname, addresses.id, addresses.user_id, addresses.email_address
FROM users, addresses
2017-02-06 20:15:27,879 INFO sqlalchemy.engine.base.Engine ()
(1, u'jack', u'Jack Jones', 1, 1, u'jack@yahoo.com')
(1, u'jack', u'Jack Jones', 2, 1, u'jack@msn.com')
(1, u'jack', u'Jack Jones', 3, 2, u'www@www.org')
(1, u'jack', u'Jack Jones', 4, 2, u'wendy@aol.com')
(2, u'wendy', u'Wendy Williams', 1, 1, u'jack@yahoo.com')
(2, u'wendy', u'Wendy Williams', 2, 1, u'jack@msn.com')
(2, u'wendy', u'Wendy Williams', 3, 2, u'www@www.org')
(2, u'wendy', u'Wendy Williams', 4, 2, u'wendy@aol.com')

totally a mess. 输出了两张表的笛卡尔积. 我们需要一个 where 条件, Select.where 提供了这个方法.

In [16]: s = select([users, addresses]).where(users.c.id == addresses.c.user_id)
...: In [17]: for row in conn.execute(s):
...: print(row)
...:
2017-02-06 20:40:42,646 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.name, users.fullname, addresses.id, addresses.user_id, addresses.email_address
FROM users, addresses
WHERE users.id = addresses.user_id
2017-02-06 20:40:42,646 INFO sqlalchemy.engine.base.Engine ()
(1, u'jack', u'Jack Jones', 1, 1, u'jack@yahoo.com')
(1, u'jack', u'Jack Jones', 2, 1, u'jack@msn.com')
(2, u'wendy', u'Wendy Williams', 3, 2, u'www@www.org')
(2, u'wendy', u'Wendy Williams', 4, 2, u'wendy@aol.com')

这里符合直觉, 不过需要注意的是 users.c.id == addresses.c.user_id, 这实际上是一个类 BinaryExpression.

In [18]: users.c.id == addresses.c.user_id
Out[18]: <sqlalchemy.sql.elements.BinaryExpression object at 0x10aae2110>

In [19]: str(users.c.id == addresses.c.user_id)
  Out[19]: 'users.id = addresses.user_id'

9. Operators

如果 users.c.id == addresses.c.user_id 是一个类, 那么 >, <, >=, <=, + 这些是不是也是同一个类? 答案是的.

In [21]: print(users.c.id == 7)
users.id = :id_1 In [22]: (users.c.id == 7).compile().params
Out[22]: {u'id_1': 7} In [23]: print(users.c.id != 7)
users.id != :id_1 In [24]: print(users.c.id == None)
users.id IS NULL In [25]: print('fred' > users.c.name)
users.name < :name_1

In [27]: print(users.c.id + addresses.c.id)

users.id + addresses.id

In [28]: print(users.c.name + users.c.fullname)
  users.name || users.fullname

注意到, BinaryExpression 这个类也有 compile 方法, 其实与 Insert 类似, 他们都继承于 ColumnElement 类.

另外, + 如果两边都是数字就是相加, 否则就是字符串拼接, || 不是或, 搁 mysql 里面, 是 concat.

不排除有实在找不到对应的 operator 的情况, 这时候可以使用 ColumnOperators.op() 方法

In [30]: print(users.c.name.op('=')('foo'))
users.name = :name_1 In [31]: print(users.c.name.op('==')('foo'))
users.name == :name_1 In [32]: print(users.c.name.op('tiddlywinks')('foo'))
users.name tiddlywinks :name_1

10. Conjunctions

在 sql 里面, where 后经常跟着 and, or, not, like, 在 SQLAlchemy 中, 它们变成了 and_, or_, not_, ColumnOperators.like()

In [35]: print(and_(
...: users.c.name.like('j%'),
...: users.c.id == addresses.c.user_id,
...: or_(
...: addresses.c.email_address == 'wendy@aol.com',
...: addresses.c.email_address == 'jack@yahoo.com'
...: ),
...: not_(users.c.id > 5)
...: ))
users.name LIKE :name_1 AND users.id = addresses.user_id AND (addresses.email_address = :email_address_1 OR addresses.email_address = :email_address_2) AND users.id <= :id_1

所有 email 地址在 AOL 或 MSN, 名字首字母在 m 和 z 之间, 将他们的 fullname 和 email 地址相加, 以','相分隔, 并将该字段重命名为 title. 这里要用到两个方法, ColumnOperators.between() 和  ColumnOperators.label()

In [44]: s = select([(users.c.fullname + ',' + addresses.c.email_address).label(
...: 'title')]).where(
...: and_(
...: users.c.id == addresses.c.user_id,
...: users.c.name.between('m', 'z'),
...: or_(
...: addresses.c.email_address.like('%@aol.com'),
...: addresses.c.email_address.like('%@msn.com')
...: )
...: )
...: ) In [45]: conn.execute(s).fetchall()
2017-02-06 21:33:29,564 INFO sqlalchemy.engine.base.Engine SELECT users.fullname || ? || addresses.email_address AS title
FROM users, addresses
WHERE users.id = addresses.user_id AND users.name BETWEEN ? AND ? AND (addresses.email_address LIKE ? OR addresses.email_address LIKE ?)
2017-02-06 21:33:29,564 INFO sqlalchemy.engine.base.Engine (',', 'm', 'z', '%@aol.com', '%@msn.com')
Out[45]: [(u'Wendy Williams,wendy@aol.com',)]

and_ 的替代是使用 where 的链式调用.

In [42]: s = select([(users.c.fullname + ',' + addresses.c.email_address).label(
...: 'title')]).\
...: where(users.c.id == addresses.c.user_id).\
...: where(users.c.name.between('m', 'z')).\
...: where(
...: or_(
...: addresses.c.email_address.like('%@aol.com'),
...: addresses.c.email_address.like('%@msn.com')
...: )
...: ) In [43]: conn.execute(s).fetchall()
2017-02-06 21:30:48,623 INFO sqlalchemy.engine.base.Engine SELECT users.fullname || ? || addresses.email_address AS title
FROM users, addresses
WHERE users.id = addresses.user_id AND users.name BETWEEN ? AND ? AND (addresses.email_address LIKE ? OR addresses.email_address LIKE ?)
2017-02-06 21:30:48,624 INFO sqlalchemy.engine.base.Engine (',', 'm', 'z', '%@aol.com', '%@msn.com')
Out[43]: [(u'Wendy Williams,wendy@aol.com',)]

SQL Expression Language Tutorial 学习笔记一的更多相关文章

  1. SQL Expression Language Tutorial 学习笔记二

    11. Using Textual SQL 直接使用 SQL 如果实在玩不转, 还是可以通过 test() 直接写 SQL. In [51]: s = text( ...: "SELECT ...

  2. 《SQL 反模式》 学习笔记

    第一章 引言 GoF 所著的的<设计模式>,在软件领域引入了"设计模式"(design pattern)的概念. 而后,Andrew Koenig 在 1995 年造了 ...

  3. Django 1.7 Tutorial 学习笔记

    官方教程在这里 : Here 写在前面的废话:)) 以前学习新东西,第一想到的是找本入门教程,按照书上做一遍.现在看了各种网上的入门教程后,我觉得还是看官方Tutorial靠谱.书的弊端一说一大推 本 ...

  4. 深度学习 Deep Learning UFLDL 最新 Tutorial 学习笔记 1:Linear Regression

    1 前言 Andrew Ng的UFLDL在2014年9月底更新了. 对于開始研究Deep Learning的童鞋们来说这真的是极大的好消息! 新的Tutorial相比旧的Tutorial添加了Conv ...

  5. sql优化详细介绍学习笔记

    因为最近在面试,发现sql优化这个方面问的特别特别的多.之前都是零零星星,不够全面的了解一点,刚刚在网上查了一下,从 http://blog.csdn.net/zhushuai1221/article ...

  6. sql server 2016 JSON 学习笔记

    虽然现在win服务器已经几乎不用了,但是网上看到2016开始原生支持json 还是想试试 建立一个表  id int , json varchar(2000) json字段中输入数据 {"r ...

  7. 《SQL基础教程》+ 《SQL进阶教程》 学习笔记

    写在前面:本文主要注重 SQL 的理论.主流覆盖的功能范围及其基本语法/用法.至于详细的 SQL 语法/用法,因为每家 DBMS 都有些许不同,我会在以后专门介绍某款DBMS(例如 PostgreSQ ...

  8. Programming Language A 学习笔记(二)

    1. 语法糖--元组的"名称引用"与"位置引用": (e1,...,en) <=> {1=e1,...,n=en} 类型:t1 * - * tn & ...

  9. sql server自定义函数学习笔记

    sql server中函数分别有:表值函数.标量函数.聚合函数.系统函数.这些函数中除系统函数外其他函数都需要用户进行自定义. 一.表值函数 简单表值函数 创建 create function fu_ ...

随机推荐

  1. smarty直接在模板中格式化时间的方法

    smarty提供了一个获取时间戳的方法: <%$smarty.now%> 使用该方法获取到当时的时间戳之后,使用格式化修饰符data-format进行修饰: <%$smarty.no ...

  2. python的高级特性:切片,迭代,列表生成式,生成器,迭代器

    python的高级特性:切片,迭代,列表生成式,生成器,迭代器 #演示切片 k="abcdefghijklmnopqrstuvwxyz" #取前5个元素 k[0:5] k[:5] ...

  3. Android NDK学习(4)使用cygwin生成.so库文件

    转:http://www.cnblogs.com/fww330666557/archive/2012/12/14/2817389.html 简单的示例: makefile文件: LOCAL_PATH: ...

  4. sencha touch 在线实战培训 第一期 第四节

    2014.1.4晚上8点开的课 第一节收费课程,还是有几位同学付费了,这些课程也录像了的,以后也会持续销售. 本期培训一共八节,前三堂免费,后面的课程需要付费才可以观看. 本节内容:          ...

  5. MYSQL的索引和常见函数

    MySQL的索引 索引机制 MySQL属于关系型数据库,为了提高查询速度,可以创建索引. 索引:由表中的一个或多个字段生成的键组成,这些键存储在数据结构(B树或者hash表中),于是又分为B树索引(I ...

  6. 前端开发利器 Emmet 介绍与基础语法教程

    在前端开发的过程中,编写 HTML.CSS 代码始终占据了很大的工作比例.特别是手动编写 HTML 代码,效率特别低下,因为需要敲打各种“尖括号”.闭合标签等.而现在 Emmet 就是为了提高代码编写 ...

  7. mysql补充(3)优化sql语句查询常用的30种方法

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索 ...

  8. Centos6.5 (或Linux) 安装Elasticsearch

    一.可以在网上下载对饮的版本:https://github.com/elastic/elasticsearch,本次安装的是5.5.3. 首先保证虚拟机上安装了jdk,jdk的版本只是是1.7或以上 ...

  9. JavaEE JSP 学习笔记

    一.JSP简介 1.也是SUN公司推出的开发动态web资源的技术,属于JavaEE技术之一.由于原理上是Servlet, 所以JSP/Servlet在一起. 二.HTML.Servlet和JSP 1. ...

  10. 搭建linux远程服务器和传输下载文件

    其实,将ubuntu系统设置为服务器很简单,只需要开启ssh服务就可以了.开启了ssh服务以后,其它电脑就可以通过ssh登录你的这台ubuntu服务器.SSH分客户端openssh-client和op ...