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. 【linux系列】centos安装vsftp

    一.检查vsftpd软件 如果发现上不了网可以修改配置文件中的ONBOOT=no改为yes,然后重启服务试试

  2. ubuntu安装vncserver实现图形化访问

    请注意: 如果在安装中部分软件无法安装成功,说明软件源中缺包,先尝试使用命令#apt-get update更新软件源后尝试安装.如果还是不行,需要更换软件源.更换步骤: a)输入命令#cp /etc/ ...

  3. Unity3D protobuf-net使用方式

    1.下载protobuf-net 2.创建Unity工程,创建一个Plugins文件夹,将protobuf-net解压把里面得protobuf-net放到Plugins 3.创建一个名为mcs的文本文 ...

  4. SVG学习笔录(二)

    一.svg动画SMIL SVG采用的是使用文本来定义图形,这种文档结构非常适合于创建动画.要改变图形的位置.大小和颜色,只需要调整相应的属性就可以了.事实上,SVG有为各种事件处理而专门设计的属性,甚 ...

  5. ubuntu16.04下安装Eigen

    请输入以下命令进行安装: sudo apt-get install libeigen3-dev 一个库由头文件和库文件组成.Eigen头文件的默认位置在 “usr/include/eigen3” 中. ...

  6. Numpy基础学习与总结

    Numpy类型学习 1.数组的表示 import numpy as np In [2]: #numpy核心是高维数组,库中的ndarray支持多维数组,同时提供了数值运算,可对向量矩阵进行运算 In ...

  7. python读取文件行号和内容的便捷方法

    处理数据时候,需要得到数据所在和行号,使用enumerate时便捷的方法: file = open('file.txt','r') for (num,value) in enumerate(file) ...

  8. pyAudio介绍

    概要 pyaudio有这么几个功能: - 提取特征 - 训练并且使用分类器 - 语音分割功能 - 内容关系可视化 python实现,好处有这么几个 - 适合做计算分析类型操作(编码少,效率不低) - ...

  9. Ubuntu:如何显示系统托盘图标(systray)

    1. 问题说明 Ubuntu 11版本开始,默认关闭了托盘图标的显示,需要手动执行命令或额外工具配置,添加到白名单.Ubuntu 13.04更彻底,默认配置根本没有托盘图标,除了java和wine等几 ...

  10. c# 实现ListView的排序

    [问题描述]: 当点击列标题的时候,能够完成对该列排序,同时显示排序的箭头,再次点击,按照反序排序. [解决方法]: 1.创建一个类:ListViewColumnSorter继承IComparer接口 ...