sqlalchemy

3.1 SQL 表达式语言教程

sqlalchemy是一个使用python来表示关系数据库结构的语言.这个结构尽可能模拟底层数据库.以少量的抽象方法应对不同的数据库后台。针对后台的一致性,允许接受当前一致实现.它们不隐藏那些后端中特定的子集(小部分后端)所独有的有用的概念.这种表达语言因此提供了一种方法来写入后台中立的SQL表达式.但是它不接受标准的sql语句。

ORM与表达式语言正好相反,它是建立于表达式语言(后者)之上的独特的API。ORM引进目标关系教程.提供了一个更高的层次分离了使用模式.ORM本身就是使用表达式用法的例子.sql语言直接提供了一个初级的关系数据库模型系统。

ORM使用模式和sql语言有重叠的部分.相似之处很浅显

3.1.1 版本检查

快速查看当前版本(sqlalchemy版本至少0.9以上):

>>>import sqlalchemy 
>>>sqlalchemy.__version__ 
0.9.0

3.1.2 连接

下面的教程我们将会使用sqlite嵌入式数据库,这是一种简单的测试语句的方法,它不需要设置现有的数据库。我们使用create_engine()进行连接:

>>> from sqlalchemy import create_engine 
>>> engine = create_engine('sqlite:///:memory:', echo=True)

echo标志能快捷的设置sqlalchemy日志,它通过了python标准的日志方法.当语句执行,我们将会看到所有产生的sql语句.如果你按照这个教程不想看到sql语句输出,将echo设置成False。~~教程会格式化sql弹出窗口.他不会在乎我们的方式,只需要点击'sql'链接就会看到它被建立.

create_engine()的返回值是引擎的例子,它表现核心接口在数据库中,允许使用方言来操作数据库和数据库接口的细节部分.为了防止sqlite方言

会向python 内置的sqlite方法解释操作.

Lazy connecting

当Engine.execute()或engine.connect()首次被调用时,Engine建立了一个DBAPI连接数据库,它被用来传输sql语句.

See also: Database urls - 包括了create_engine()通过链接连接不同数据库的例子

3.1.3 定义和创建数据库表

SQL语言构造器的表达大部分和表列相反.在sqlalchemy中列通过column对象来表示.所有的Column都与Tabel关联.Table对象和它联系的子对象的采集都是引用元数据.教程中我们会设计一些Table对象.注意sa可以自动导入现存数据库的所有表(这个过程叫数据库反射)

我们定义所有的表的目录为metadata.使用Table框架,它类似于传统sql创建表的语句.我们将会建立2张表.其中一个表在应用中 代表'user'.另外一个张表示有0个或者多个'email addresses' 'user '表的所有行:

>>> from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey 
>>> metadata = MetaData() 
>>> users = Table('users', metadata, 
...    Column('id', Integer, primary_key=True), 
...    Column('name', String), 
...    Column('fullname', String), 
... ) 
>>> addresses = Table('addresses', metadata, 
...   Column('id', Integer, primary_key=True), 
...   Column('user_id', None, ForeignKey('users.id')), 
...   Column('email_address', String, nullable=False) 
... )

所有关于定义Table 对象,和如何从一个已有的数据库自动创建它们,具体描述在数据库和元数据章节里面

接下来,将要讲解MetaData .我们将会从内置的SQLite数据库创建所选择的表,我们使用create_all(),传递给指向数据库的引擎实例 ,它在创建之前会检查当前的所有表.所有多次调用它也很安全.


>>> metadata.create_all(engine) 
PRAGMA table_info("users") 
() 
PRAGMA table_info("addresses") 
() 
CREATE TABLE users ( 
id INTEGER NOT NULL, 
name VARCHAR, 
fullname VARCHAR, 
PRIMARY KEY (id) 
) 
() 
COMMIT 
CREATE TABLE addresses ( 
id INTEGER NOT NULL, 
user_id INTEGER, 
email_address VARCHAR NOT NULL, 
PRIMARY KEY (id), 
FOREIGN KEY(user_id) REFERENCES users (id) 
) 
() 
COMMIT

提示:熟悉创建数据库表的使用者可能会注意 varchar 表单没用标明长度.在sqlite 和 postgresql .这是一个可变数据类型.但是在其他数据库中这是不允许的.如果你使用这个教程在这些数据库中,你希望用sqlalchemy来解决这个创建数据库表的问题,如下例子提供了一个长度给string 类型

Column('name', String(50))

Interger Numeric的长度也和String的类似.此外它的长度不是参照Sqlalchemy而是表创建时。

另外 firebird 和 oracle 需要通过序列化方式来创建新主键标识符的数据库 , SQLAlchemy 并不会自动为其生成。可以指定Sequence 生成

from sqlalchemy import Sequence 
Column('id', Integer, Sequence('user_id_seq'), primary_key=True)

下面是一个超级简单的Table

users = Table('users', metadata, 
    Column('id', Integer, Sequence('user_id_seq'), primary_key=True), 
    Column('name', String(50)), 
    Column('fullname', String(50)), 
    Column('password', String(12)) 
)

我们包含了许多重复的table框架分别标记


3.1.4 插入操作

在第一个sql表达式我们将会创建一个insert构造,它表示插入语句.它目标表:

>>> ins = user.insert()

为了看到sql 框架如何实现的简单例子,我们使用str()函数:

>>> str(ins) 
'INSERT INTO users (id, name, fullname) VALUES (:id, :name, :fullname)'

从上面可以发现insert语句把users表中的每一列都命名了.它使用values()方法.创建了valuse 插入明确的条例.

>>> ins = users.insert().values(name=’jack’, fullname=’Jack Jones’) 
>>> str(ins) 
'INSERT INTO users (name, fullname) VALUES (:name, :fullname)'

在values方法中它将values里面的值放入到2个列中,我们放置在values中现有的数据不需要在string里面寻找;而是绑定我们命名的元素. 当它执行结束数据将会被放置在insert框架中,但在通常只会在执行相关语句时才会出现.sqlalchemy会自动的绑定这些值,我们可以通过compiled表单语句来获取这些数据的信息。

>>> ins.compile().params 
{’fullname’: ’Jack Jones’, ’name’: ’jack’}

3.1.5 执行操作

执行插入语句是插入操作中是有趣的一部分.在教程中我们将会着重讲解执行sql框架的方法.接着接触用一些shortcut方法来完成相关操作.引擎对象是我们所创建的数据库连接源,它可以对数据库操作SQL语句.我们使用connect()方法来获得这个连接。

>>> conn = engine.connect() 
>>> conn 
<sqlalchemy.engine.base.Connection object at 0x...>

Connection对象表示检测出一个活跃的DBAPI连接资源.我们通过执行insert对象观察它是如何发生的.

>>> result = conn.execute(ins)
INSERT INTO users (name, fullname) VALUES (?, ?)
('jack', 'Jack Jones')
COMMIT

现在插入语句已经发送到数据库.虽然我们输出了'qmark'绑定的参数而不是'named'绑定的参数.这是为什么呢?因为Connection是使用sqlite方言来产生相关语句的.当我们使用str()函数时它不知道sqlite的方言语句,于是就返回我们默认声明的参数.看接下来的帮助:

>>> ins.bind = engine 
>>> str(ins) 
’INSERT INTO users (name, fullname) VALUES (?, ?)’

当我们调用execute()时result变量会发送什么变化?Sqlalchemy的connection对象引用了DBAPI的连接.最后我们了解到ResultProxy对象就是类似于DBAPI游标对象.我们可以从insert中得到很重要的信息.例如从结果语句中查看主码

>>> result.inserted_primary_key 
[1]

1的值通过sqlite自动产生.但这只是因为我们没有在insert语句里声明id列,另外我们已经在使用明确的值.在这2种情况中,sqlalchemy始终清楚如何得到一个新的被引用的主码的值.尽管在操作不同的数据库产生它们的方法不一样,但这些数据库方言明白如何用确定的步骤来确定正确的值(注意inserted_primary_key返回的是一个列表,所有它支持复合主码)


3.1.6 执行复合语句

在上面插入语句的例子中,我们刻意展示一些表达式语言框架不同的操作,在通常情况下,插入语句使用Connection将编译好的参数传送到execute()方法中,它们不需要使用 values。我们创建一个insert语句。

>>> ins = users.insert() 
>>> conn.execute(ins, id=2, name=’wendy’, fullname=’Wendy Williams’) 
INSERT INTO users (id, name, fullname) VALUES (?, ?, ?) 
(2, ’wendy’, ’Wendy Williams’) 
COMMIT 
<sqlalchemy.engine.result.ResultProxy object at 0x...>

我们在上面的execute()方法里定义了3个列,编译的insert语句包含了全部的3列.insert语句在编译时依据我们定义的参数。如果我们定义参数的参数越少,insert语句的values子句就会越短。

我们使用executemany()方法发送一个字典列表其中包含了要插入的参数来执行多条Inserts语句。下面我们有个插入一些email地址的例子

>>> conn.execute(addresses.insert(), [ 
...    {'user_id': 1, 'email_address' : '[email protected]'},
...    {'user_id': 1, 'email_address' : '[email protected]'},
...    {'user_id': 2, 'email_address' : '[email protected]'},
...    {'user_id': 2, 'email_address' : '[email protected]'},
... ])
INSERT INTO addresses (user_id, email_address) VALUES (?, ?)
((1, '[email protected]'), (1, '[email protected]'), (2, '[email protected]'), (2, '[email protected]'))
COMMIT
<sqlalchemy.engine.result.ResultProxy object at 0x...>

我们通过sqlite表示的主码来确定对应的每个地址行。

当执行多个参数设定时,每个字典必须有相同的key设置。这是因为insert语句在编译列表第一个字典时,它是假设接下来的字典的key都与第一列字典的相同。


3.1.7 查询

我们使用inserts语句仅仅是为了测试向数据库里面写入一些数据,更有趣是是查询数据是部分。在后面我们将会介绍更新和删除语句.初级框架通常使用select函数生产select语句:

>>> from sqlalchemy.sql import select 
>>> s = select([users]) 
>>> result = conn.execute(s) 
SELECT users.id, users.name, users.fullname 
FROM users ()

我们在上面写了一个select()基本调用方法的语句.把users表放在select查询子句中再执行.sqlalchemy扩展到users表的每一列中, 同时也为我们产生了FROM子句.返回了resultproxy对象作为结果,这种行为就像dbapi的游标,包含了像ferchone()和fetchall()的方法,然后通过迭代得到每一个行的值。

>>> for row in result:
...     print row
(1, u'jack', u'Jack Jones')
(2, u'wendy', u'Wendy Williams')

我们可以看到上面每一行的结果都是一个简单的元组.我们有很多种方法访问每一行的值,常用的一种方法是通过字典使用表名字符串查询。

>>> result = conn.execute(s)
SELECT users.id, users.name, users.fullname
FROM users
()
>>> row = result.fetchone()
>>> print "name:", row['name'], "; fullname:", row['fullname']
name: jack ; fullname: Jack Jones

整数索引同样适用:

>>> row = result.fetchone()
>>> print "name:", row[1], "; fullname:", row[2]
name: wendy ; fullname: Wendy Williams

下面是一种实用的方法,它使用column对象直接作用在key上:

>>> for row in conn.execute(s):
...     print "name:", row[users.c.name], "; fullname:", row[users.c.fullname]
SELECT users.id, users.name, users.fullname
FROM users
()
name: jack ; fullname: Jack Jones
name: wendy ; fullname: Wendy Williams

还有待查询数据行的结果集在被内存丢弃前应当显示关闭,当处于垃圾对象机制时对象游标和引用resultprox连接代码将会分别被关闭然后返回到连接池中,它让此变得更直接即使一些数据库接口对于这些事非常严格。

>>> result.close()

如果你想控制column查询子句中列的属性,我们可以从表中参考Column对象.他们等同于表对象的列属性。

>>> s = select([users.c.name, users.c.fullname])
SQL>>> result = conn.execute(s)
>>> for row in result:
...     print row
(u'jack', u'Jack Jones')
(u'wendy', u'Wendy Williams')

我们展示了from子句有趣的部分.然而产生的语句包含了2个不同的选项,“SELECT columns”部分和“FROM table”部分. 我们的select()框架仅仅有一个列表控制colums。它是如何工作的?让我们放2个表在select()语句中:

>>> for row in conn.execute(select([users, addresses])):
...     print row
(1, u'jack', u'Jack Jones', 1, 1, u'[email protected]')
(1, u'jack', u'Jack Jones', 2, 1, u'[email protected]')
(1, u'jack', u'Jack Jones', 3, 2, u'[email protected]')
(1, u'jack', u'Jack Jones', 4, 2, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 1, 1, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 2, 1, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 3, 2, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 4, 2, u'[email protected]')

我们放了2个表在from子句中,但是它其实看起来很混乱.熟悉sql连接查询的人知道这就是一个笛卡尔乘积.user表的每一行都和address表的每一行对应.为了让语句更加清晰我们需要where子句,我们使用Select.where():

>>> for row in conn.execute(select([users, addresses])):
...     print row
(1, u'jack', u'Jack Jones', 1, 1, u'[email protected]')
(1, u'jack', u'Jack Jones', 2, 1, u'[email protected]')
(1, u'jack', u'Jack Jones', 3, 2, u'[email protected]')
(1, u'jack', u'Jack Jones', 4, 2, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 1, 1, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 2, 1, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 3, 2, u'[email protected]')
(2, u'wendy', u'Wendy Williams', 4, 2, u'[email protected]')

这样看起来是不是好了一点.我们在select()语句建立了WHERE users.id = addresses.user_id的联系,我们得到的结果经过处理为了让users和addresses行关联。如果我们观察表达式?它仅仅用在两个不同的column对象中使用了一个等于操作。这样就明白了。1==1 为真 1==2 为假,没有where子句,让我们仔细观察一下这表达式是如何运作的。

>>> users.c.id == addresses.c.user_id 
<sqlalchemy.sql.expression.BinaryExpression object at 0x...>

太奇怪了,这没有真假,它到底是什么。

>>> str(users.c.id == addresses.c.user_id)
'users.id = addresses.user_id'

你可以看到,==操作产生了一个十分类似insert()和select()的对象。非常感谢eq()内置函数,你在里面调用了str()它产生了sql语句,现在我们可以看到所有的事最后作用的都是相同的对象。sqlalchemy所有表达式的基本类都是作用在columnelement

3.1.8 Operators

从刚开始使用sqlalchemy操作范式到现在,我们已经学会了一些相关的操作。让我们看看它的两列是如何进行等于操作的。

>>> print users.c.id == addresses.c.user_id
users.id = addresses.user_id

如果我们使用一个字面量(一个字面的意思,不是sqlalchemy子句对象)我们会得到一个绑定值。

>>> print users.c.id == 7
users.id = :id_1

字面量7已经植入到ColumnElement,我们使用在Insert对象中的方法来查看它:

>>> (users.c.id == 7).compile().params
{u'id_1': 7}

大部分python操作产生的sql表达式的例子,例如等于和不等于:

>>> print users.c.id != 7
users.id != :id_1

>>> # None converts to IS NULL
>>> print users.c.name == None
users.name IS NULL

>>> # reverse works too
>>> print 'fred' > users.c.name
users.name < :name_1

如果想要把2个整形的列加在一起,我们可以使用一个加操作:

>>> print users.c.id + addresses.c.id
users.id + addresses.id

列的类型很重要!如果我们将两个string类型的列相加(回忆我们刚开始的整形列对象和字符型列对象),会得到一些不一样的东西:

>>> print users.c.name + users.c.fullname
users.name || users.fullname

大部分数据库都使用||作为字符串连接操作。但不是全部,mysql就不用:

>>> print (users.c.name + users.c.fullname).\
...      compile(bind=create_engine('mysql://'))
concat(users.name, users.fullname)

上面显示的是Engine连接MySql数据库产生的sql语句,MySql的concat()函数编译了||操作。