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. 在centos7 ubuntu15.04 上通过bosh-lite 搭建单机环境cloudfoundry

    Bosh-lite简介       bosh-lite 是一个单机部署cloudfoundry的实验性工具,用于开发人员做poc 验证.Bosh-lite目前支持仅MAC OS X和Linux系统.B ...

  2. Word 2010 制作文档结构之页码从正文开始设置

    一般技术性文档结构划分: 第一页(首页) 第二页(修改记录页/版本记录页) 第三页(目录) 第四页(正文) 需求: 页脚编码 从正文(即第四页)开始,而不是从首页开始,那么该如何实现? 前提准备: 输 ...

  3. truncate表恢复

    Fy_Recover_Data 2014.03.07 更新 -- 现在可以从离线文件中恢复被Truncated的数据了 2014新版本的恢复思路 与 2012的不同 2014年是离线恢复 http:/ ...

  4. Oracle 学习之exists

    不相关子查询:子查询的查询条件不依赖于父查询的称为不相关子查询.相关子查询:子查询的查询条件依赖于外层父查询的某个属性值的称为相关子查询,带EXISTS 的子查询就是相关子查询EXISTS表示存在量词 ...

  5. LeetCode 17 Letter Combinations of a Phone Number (电话号码字符组合)

    题目链接 https://leetcode.com/problems/letter-combinations-of-a-phone-number/?tab=Description HashMap< ...

  6. [原]F5负载均衡激活license

    一.pc用网线连接上f5的管理口,开机,ping   192.168.1.245 二.用浏览器登录:https://192.168.1.245     用户名 admin  密码 admin

  7. requests源码分析

    0.前言 (1) 拆部分reques中感兴趣t的轮子 (2)对一些感兴趣的pythonic写法做一些归纳 1.用object.__setattr__来初始化构造函数 反正我之前就是直接实例对象时把所有 ...

  8. SPClaimsUtility.AuthenticateFormsUser的证书验证问题

    Log Parser Studio查看IIS日志发现调用SPClaimsUtility.AuthenticateFormsUser的部分有time-taken在15秒左右的多个响应,查看call st ...

  9. Linux(CentOS)安装JDK(.tar.gz)并配置

    本文思路转自http://blog.sina.com.cn/s/blog_81631744010137iy.html 点击(此处)折叠或打开 1.到 甲骨文(oracle)下载jdk不用多说 tar ...

  10. Unity3D笔记 GUI 四、实现选项卡三

    一.代码: using UnityEngine; using System.Collections; /// <summary> /// 选项卡二 /// </summary> ...