楔子
关于sqlalchemy有很多的黑科技,以及很多你不知道的用法,掌握它们能够极大地方便我们操作数据库,下面我们就来看看。至于一些简单的语法网上一大堆可以自己去搜,我这里不会提,我这里只会说一些比较高级的特性。
引擎相关
引擎,直接使用create_engine创建不就好了,这有啥?但是你知道create_engine里面都支持哪些参数吗?
创建引擎
首先创建引擎通过create_engine("dialect+driver://user:password@host:port/database")
,但是我们还可以通过其它方式指定。比如连接postgresql,我们就可以通过create_engine("postgresql+psycopg2://root:123456@localhost:5432/postgres")
创建,而且postgresql+psycopg2
我们一般可以简写成postgresql
,也就是只写数据库种类而不写驱动,那么会去找默认所使用的驱动。
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
"""
我们使用create_engine创建出来的就是sqlalchemy.engine.Engine对象
"""
from sqlalchemy.engine.url import URL
"""
我们是使用user:password@ip:port/database这样的形式传到create_engine里面
但是还可以使用URL这个类,会自动帮助我们进行拼接
"""
url = URL(
"postgres", # 数据库类型、mysql、Oracle、SqlServer等等
username="xxx",
password="xxx",
host="localhost",
port="5432",
database="postgres"
)
engine = create_engine(url)
"""
这种创建引擎的方式也是可以的
"""
create_engine支持的其它参数
创建引擎的这个方法,第一个参数接收的显然是我们用来创建引擎的url,但是它还支持很多其它参数,我们需要通过关键字参数来指定,那么下面一起来看看。
case_sensitive=True
默认是大小写敏感,如果为False,匹配列的时候将忽略大小写
connect_args
很重要的一个参数,字典的形式,可以附加一些属性参数。
from sqlalchemy import create_engine
# url我们可以只写一部分,剩余的连接参数放在connect_args里面
engine = create_engine(
"postgres://",
connect_args={"user": "root", # 注意:这是user,不是username
"password": "123456",
"host": "localhost",
"port": 5432,
"database": "postgres"
}
)
# 或者
engine = create_engine(
"postgres://localhost:5432",
connect_args={"user": "root",
"password": "123456",
"database": "postgres"
}
)
engine = create_engine(
"postgres://localhost:5432/postgres",
connect_args={"user": "root",
"password": "123456"
}
)
# 关于用户名、密码、host、端口等等,不要同时指定
# 比如:postgres://localhost:5432/postgres,这里面已经指定了host和端口、database
# 因此在connect_args字典中不要再出现"host"、"port"、"datbase"了
# 其实以上没有太大意义,我们不会这样指定连接引擎的,只是知道可以这么连接即可
# 这个参数最重要的意义是这里面可以指定一些更高级的参数,比如连接hive
engine = create_engine(
"hive://localhost:10000/default",
connect_args={"auth": "KERBEROS",
"kerberos_service_name": "hive",
'session_props': {'query_max_run_time': '1234m'},
"configuration": {'hive.exec.reducers.max': '123'}
}
)
# 像这样的一些参数,是要附加到具体的驱动里面的,便可以通过connec_args指定
echo=False
如果为True,那么会将执行语句以及参数列表输出到控制台;如果设置为字符串"debug",那么查询的行也会输出到控制台。Engine的echo属性可以随时修改从而打开和关闭日志记录,当然还可以使用python标准库的日志记录模块来控制日志的记录。
echo_pool=False
如果为True,连接池将记录信息输出:比如连接何时无效、以及连接何时被回收。
max_overflow=10
允许连接池内溢出的连接数,即超过pool_size之后的最大数量。
pool_recycle=-1
超过多少秒后重新连接,-1表示没有超市限制
pool_size=5
在连接池中保持打开状态的连接数,如果为0则表示没有限制。
pool_timeout=30
在放弃从连接池里面获取连接之前所等待的秒数。
engine.execute
我们可以使用engine.execute来执行sql语句,但也是有讲究的。
from sqlalchemy import create_engine
from sqlalchemy import text
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
row = engine.execute(
# 使用sqlalchemy.text将sql语句包装一下
# 我们可以使用:var作为占位符,
# 比如这里的age>:_age和level=:_level表示, 字段age要大于_age并且level要等于_level
text("select age, level from people where age>:_age and level=:_level"),
# 但是这里的_age和_level是什么,我们还不知道,所以下面通过一个字典来指定。
# 当然占位符叫什么是无所谓的,只是为了和字段区分,我刻意加上了一个下划线,但是最好要按照主流编程语言的变量命名规范、并且最好见名知意
{"_age": 35, "_level": "会计师"}
# 但是需要注意:原生的sql语句不可以这么写,必须在sqlalchemy.text里面
)
print(row.fetchall()) # [(36, '会计师'), (51, '会计师')]
实现事务
我们也可以通过engine实现事务
from sqlalchemy import create_engine
from sqlalchemy import text
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
# with语句里面的sql执行语句,整体会作为一个事务
with engine.begin() as conn:
conn.execute("...")
conn.execute("...")
conn.execute("...")
将数据库的表反射成python的类
有时候我们需要使用orm语法去查询数据库中某个表的数据,这就需要我们有一个类来与数据库中的表进行对应。当然我们可以手动定义,但是这样做是不是很麻烦呢?那么有没有一个办法,可以直接将数据库中的表反射成类呢?答案是有的,我们来看看怎么做。
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
# 绑定引擎,生成一个MetaData对象
metadata = MetaData(bind=engine)
# 通过engine将数据库中表信息反射到metadata上,指定schema,这里是public。
# only参数则是反射我们指定的表,如果不指定only,那么会将当前schema下存在的所有表全部反射出来
metadata.reflect(schema="public", only=["people"])
# 调用automap_base,传入metadata,从名字也能看出来,自动映射成类
# 将metadata里面保存的表信息映射成Base
Base = automap_base(metadata=metadata)
# 调用prepare方法,设置被映射的类和关系
Base.prepare()
# 然后当指定的表都被映射类之后,会保存在Base.classes下面,直接通过Base.classes.表名 即可得到对应的类,表里面的一个字段就是类里面的一个属性
# 比如我们的表叫people,那么获取对应的类,就可以使用Base.classes.people。如果反射的还有其它表,那么其它表对应的类也是一样的获取方式,它们都在Base.classes下面,通过表名获取对应的类
# 此时获取得到的类就跟我们初学sqlalchemy时创建的类是一样的,只不过我们以前是先定义类然后映射成表,这里是由表反过来推出类
cls = Base.classes.people
# 那么就可以创建session了
# 创建session的方式还是和以前一样,没什么区别
Session = sessionmaker()
session = Session(bind=engine)
# 查询
res = session.query(cls.age, cls.degree).first()
print(res) # (31, '大学本科毕业')
怎么样,是不是很简单呢?但是上面有一个问题,当然这是sqlalchemy的规定,那就是数据库中的表必须要有主键,否则反射不出来。其实在我们初学sqlalchemy的时候,貌似也必须要定义主键才行。但是问题来了,如果数据库中就是存在一张没有主键的表,我要怎么映射呢?
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
metadata.reflect(schema="public", only=["people"])
# 我们看到我们没有在使用automap_base了,其实此时通过metadata.tables我们也能够获取
tables = metadata.tables
# 这个metadata.tables是一个不可变的字典,里面key就是"schema.table",value则是sqlalchemy.schema.Table对象
# 至于这个Table对象是什么,我们暂时不需要关心,后面会说。目前对于我们获取数据来说,没有太大影响。
# 通过schema.table获取对应的Table对象,schema是public,table是people
cls = tables["public.people"]
# 为了和刚才的反射方式做对比,我这里还起名为cls
# 此时我们拿到了表示表people对应的Table对象,那么怎么用呢?
# 对于获取数据来说,之前的反射方式中,我们使用`cls.字段`即可获取对应属性
# 但是现在的Table对象则不行,我们需要通过`cls.columns.字段`或者`cls.c.字段`来获取属性
Session = sessionmaker()
session = Session(bind=engine)
# 查询
res = session.query(cls.columns.age, cls.c.degree).first()
print(res) # (31, '大学本科毕业')
此时即便是表没有主键,我们也能反射出来。当然我这里的表是有主键的,有兴趣的你可以将自己表中的主键删掉,看看能不能使用这种方法反射出来。另外这种反射方式,还可以反射出数据库中的视图。
探查表属性以及表的字段属性
有时候,我们需要得到一张表的属性,以及表中所有字段的属性。我们可能会使用原生的sql、再或者使用Navicat这样的工具查看,但如果是在项目中肯定不能这样,而且考虑要支持不同的数据库,所以还是要通过sqlalchemy的方式去查看。
from sqlalchemy import create_engine
from sqlalchemy.ext.automap import automap_base
from sqlalchemy import MetaData
from sqlalchemy import inspect # 下面的主角
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
# 老规矩,这里是需要先将数据库的表反射出来
metadata = MetaData(bind=engine)
metadata.reflect(bind=engine, schema="anime", only=["overwatch"])
Base = automap_base(metadata=metadata)
Base.prepare()
# ow是数据库中overwatch表对应的类
ow = getattr(Base.classes, "overwatch")
# 使用inspect进行查看
# 先来获取主键吧
primary_key = inspect(ow).primary_key
# 由于会存在联合主键,所以是一个序列。里面是一个个的Column对象,至于Column对象我们后面也会单独说,先知道怎么用即可
# 不过从里面的内容也能看出来都是些什么
print(primary_key) # (Column('id', INTEGER(), table=<overwatch>, primary_key=True, nullable=False),)
# 这里我们只有一个主键,所以取第一个,然后拿到名字
print(primary_key[0].name) # id
# 那么如何查看表中都存在哪些字段呢?
print(inspect(ow).c.keys()) # ['id', 'name', 'age', 'hp', 'attack', 'role', 'ultimate', 'country']
# 那么如何查看这些字段的类型、注释、一些约束呢?
# 首先拿到所有的字段,此时的字段则不是简单的字符串,而是字段对应的Column对象
columns = inspect(ow).columns
print(list(columns))
"""
[Column('id', INTEGER(), table=<overwatch>, primary_key=True, nullable=False),
Column('name', VARCHAR(length=255), table=<overwatch>, nullable=False),
Column('age', INTEGER(), table=<overwatch>), Column('hp', INTEGER(), table=<overwatch>),
Column('attack', VARCHAR(length=255), table=<overwatch>),
Column('role', VARCHAR(length=255), table=<overwatch>),
Column('ultimate', VARCHAR(length=255), table=<overwatch>),
Column('country', VARCHAR(), table=<overwatch>)]
"""
# 以上便是每一个字段的属性组成的列表,每一个元素都是<class 'sqlalchemy.sql.schema.Column'>类型
# 那么我们便可以拿到相应的属性
for col_attr in columns: # 遍历每一个Column对象
print(f"字段名:{col_attr.name},"
f"是否为主键:{col_attr.primary_key},"
f"字段类型:{str(col_attr.type)},"
f"是否允许非空:{col_attr.nullable}",
f"注释:{col_attr.comment}")
"""
字段名:id,是否为主键:True,字段类型:INTEGER,是否允许非空:False 注释:英雄的id
字段名:name,是否为主键:False,字段类型:VARCHAR(50),是否允许非空:False 注释:英雄的姓名
字段名:age,是否为主键:False,字段类型:INTEGER,是否允许非空:True 注释:英雄的年龄
字段名:hp,是否为主键:False,字段类型:INTEGER,是否允许非空:True 注释:英雄的血量
字段名:attack,是否为主键:False,字段类型:VARCHAR(255),是否允许非空:True 注释:攻击类型
字段名:role,是否为主键:False,字段类型:VARCHAR(255),是否允许非空:True 注释:英雄定位
字段名:ultimate,是否为主键:False,字段类型:VARCHAR(255),是否允许非空:True 注释:终极技能
字段名:country,是否为主键:False,字段类型:TEXT,是否允许非空:True 注释:英雄的国籍
"""
# 其实,由于我们已经反射出ow这个类了,而且通过inspect(ow).c.keys()获取了所有的字段
# 那么通过col_attr = getattr(ow, "字段")去获取也是可以的,得到的也是一个Column对象
# 至于还有哪些属性,直接dir(Column)查看即可,比如还有unique,查看是否要求唯一等等。
肿么样,一张表里面有哪些字段、字段都有哪些属性,我们是不是很清晰啦。
下面我们再来看看如何通过sqlalchemy查看一张表的属性,刚才是表里面的字段的属性。
from sqlalchemy import create_engine
from sqlalchemy.ext.automap import automap_base
from sqlalchemy import MetaData
from sqlalchemy import inspect # 下面的主角
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
# 老规矩,这里是需要先将数据库的表反射出来
metadata = MetaData(bind=engine)
metadata.reflect(bind=engine, schema="public", only=["people"])
Base = automap_base(metadata=metadata)
Base.prepare()
# 通过类,获取本地表,这是一个Table对象,但是Table对象我们后面说
local_table = inspect(Base.classes.people).local_table
# 我们获取到本地表之后调用.c.keys()也是可以拿到所有的字段名的
print(local_table.c.keys()) # ['pk', 'id', 'age', 'seniority', 'degree', 'level', 'type', 'start']
# 调用.columns,得到是schema名和字段名的组合,当然还可以调用.c, .c和.columns是一样的
# 但是注意:这只是打印的时候是这样,但是得到的并不是列表,列表里面也不是字符串,而是Column对象
print(local_table.columns) # ['people.pk', 'people.id', 'people.age', 'people.seniority', 'people.degree', 'people.level', 'people.type', 'people.start']
# 我们转换成list对象,拿到第一个元素调用name方法,得到了字段名
# 没错这个和inspect(Base.classes.people).columns得到结果是一样的
print(list(local_table.columns)[0].name) # pk
# 拿到表注释,我这张表没有,所以是None
print(local_table.comment) # None
# 获取外键,当然这张表没有
print(local_table.foreign_keys) # set()
# 获取该表的全名
print(local_table.fullname) # public.people
# 获取该表的名字
print(local_table.name) # people
# 获取该表所在的schema
print(local_table.schema) # public
# 获取该表存在的索引,一个集合类型
print(local_table.indexes)
"""
{Index('ix_t_pk', Column('pk', BIGINT(), table=<people>, primary_key=True, nullable=False,
server_default=DefaultClause(<sqlalchemy.sql.elements.TextClause object at 0x000002C6DEA2C250>, for_update=False)))}
"""
# 获取主键,同理通过本地表也是可以的,但是注意的是,这种方式得到的是一个PrimaryKeyConstraint
# 我们不能直接通过[0].name获取名字,而是要先转成list对象
print(list(local_table.primary_key)[0].name) # pk
# 获取表的记录总数,这个没有什么好的办法,需要通过查询来实现
print(local_table.count())
"""
SELECT count(public.people.pk) AS tbl_row_count
FROM public.people
"""
print(engine.execute(local_table.count()).fetchone()) # (24,)
Table对象
终于到了Table对象了,Table在sqlalchemy是一个类,不过在介绍Table之前,我们需要先介绍一下MetaData。MetaData在sqlalchemy中也是一个类,MataData对象就是用来存储表的元信息(Table对象)
的,我们在反射表的时候就说过,反射完毕之后,表的元信息都会存储在MetaData对象里面。
from sqlalchemy import MetaData
metadata = MetaData(bind=None, schema=None)
"""
是的,MetaData里面除了引擎之外,还可以指定schema
我们在metadata.reflect()里面也可以指定schema
如果都指定了,那么在反射的时候会以metadata.reflect()里面的schema为准。
不光如此,metadata.reflect()里面也可以指定引擎,但是我们不在里面指定。
在反射表的时候,我们一般在MetaData里面指定引擎,在reflect里面指定schema和only
也就是说反射表的时候,我们是不适用MetaData里面的schema参数的
但是在其它场景下,我们会使用,至于什么场景后面会说。
总之:MetaData对象是用来存储表的元信息的。可以是一个表、也可以是多个表
"""
Table对象,你可以把它想象成数据库中的表在python中的一个抽象,我们当初自己定义的类可以看成是Table对象的进一层封装。
from sqlalchemy import create_engine
from sqlalchemy import MetaData, Table, Column, Integer, String
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
# 此时在MetaData里面指定了schema
metadata = MetaData(bind=engine, schema="anime")
# 创建一个Table对象,等于创建了数据库里面的一张表。
# 这里接收如下参数:表名、MeatData对象、所有的列...
Girl = Table('girl', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16), nullable=False),
Column('email_address', String(60)),
Column('nickname', String(50), nullable=False)
)
# 此时这张表的元信息就存储在了metadata中
# 调用metadata.create_all()即可将里面的表创建到数据中。
# 其实我们以前通过Base = declarative_base(bind=engine); Base.metadata.create_all()也正是通过调用了这里的metadata
metadata.create_all()
# 所以这张名为girl的表就会被创建到数据库里面,哪个schema呢,当然是anime啦,我们在MataData里面指定的schema。所以在这个场景下,MetaData里面参数schema派上用场了
# 同理既然有create_all,那么肯定有drop_all()
# 于是这张表就被删除了
metadata.drop_all()
这个create_all和drop_all里面还可以指定tables参数。
from sqlalchemy import create_engine
from sqlalchemy import MetaData, Table, Column, Integer, String
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine, schema="anime")
Girl1 = Table('girl1', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16), nullable=False),
Column('email_address', String(60)),
Column('nickname', String(50), nullable=False)
)
Girl2 = Table('girl2', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16), nullable=False),
Column('email_address', String(60)),
Column('nickname', String(50), nullable=False)
)
Girl3 = Table('girl3', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16), nullable=False),
Column('email_address', String(60)),
Column('nickname', String(50), nullable=False)
)
# 本来应该创建三张表的,但是我们指定了tables参数,只把Girl1和Girl2写了进去,所以数据库中只会创建girl1和girl两张表,没有girl3
metadata.create_all(tables=[Girl1, Girl2])
# 同理这里也只会删除girl1和girl2两张表,假设数据库里面有girl3的话,girl3也不会被删除,因为我们在tables里面没有指定
# 当然是如果tables为None的话,那么针对的就是metadata里面存储的元信息中对应的所有的表。
# 也就是在我们初学sqlalchemy的时候,所有继承Base的类。当然使用继承Base的这种方式,底层还是依赖metadata的
metadata.drop_all(tables=[Girl1, Girl2])
关于schema,其实MetaData里面的schema我们依旧可以不用指定。
from sqlalchemy import create_engine
from sqlalchemy import MetaData, Table, Column, Integer, String
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine, schema="anime")
# 我们可以在Table中指定schema
Girl1 = Table('girl1', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16), nullable=False),
Column('email_address', String(60)),
Column('nickname', String(50), nullable=False),
schema="anime" # 指定为anime
)
Girl2 = Table('girl2', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(16), nullable=False),
Column('email_address', String(60)),
Column('nickname', String(50), nullable=False),
schema="public" # 指定为public
)
# 那么girl1会被创建到anime中,girl2会被创建到public中。
# 如果MetaData和Table里面都指定了schema,那么会以Table里面的schema为准
metadata.create_all()
我们发现这种创建表的方式简单多了,以后建议使用这种方式创建表,会方便很多,而且这种方式操作起来更简单,而且更酷。
相关操作
Table对象的操作,我们之前实际上都已经说过了。我们说,metadata存储了所有表的元信息(Table对象)
,通过metadata.tables拿到的就是所有"schema.table": "Table对象"组成的不可变字典。
from pprint import pprint
from sqlalchemy import create_engine
from sqlalchemy import MetaData
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
metadata.reflect(schema="public", only=["people"])
# 我们说通过指定schema.table拿到的就是这张表对应的Table对象
# 它和我们定义的继承自Base = declarative_base(bind=engine)的类是类似的,只不过我们通过这种方式定义的类可以看成是Table对象的进一层封装
people = metadata.tables["public.people"]
# 这个Table对象有哪些属性呢?
print(people.columns)
# ['people.pk', 'people.id', 'people.age', 'people.seniority', 'people.degree', 'people.level', 'people.type', 'people.start']
print(people.c)
# ['people.pk', 'people.id', 'people.age', 'people.seniority', 'people.degree', 'people.level', 'people.type', 'people.start']
"""
首先我们说过调用columns属性能够拿到所有的列,其实调用c也是一样的
而且这虽然显示的是一个列表,但是其实里面并不是列表,里面存储的也不是字符串,而是Column对象
另外我们在"探查表属性的时候"说过,使用inspect(Base.classes.table).local_table得到的就是一个Table对象
这么一来,我们在获取表属性和表字段属性的时候,完全就没有必要使用inspect了。
因为表的字段:我们可以通过Table().c获取,进而拿到所有属性
至于表本身:Table()就是啊,inspect().local_table兜了一大圈子得到的就是我们这里的结果
由于上面已经通过inspect演示过了,所以这里随便挑几个
"""
# 获取全名
print(people.fullname, type(people.fullname)) # public.people <class 'str'>
# 获取表名, people.name不是str类型,而是帮你进行了转义。用的时候转成str类型
print(people.name, str(people.name)) # people people
# 获取记录总数
print(engine.execute(people.count()).fetchone()) # (24,)
# 获取所有的列
# 之前通过inspect(class).columns,现在通过Table().columns或者Table().c
print(people.c)
"""
['people.pk', 'people.id', 'people.age', 'people.seniority', 'people.degree', 'people.level', 'people.type', 'people.start']
"""
# 里面是Column对象
pprint(list(people.c))
"""
[Column('pk', BIGINT(), table=<people>, primary_key=True,
nullable=False,
server_default=DefaultClause(<sqlalchemy.sql.elements.TextClause object at 0x0000021623507C70>,
for_update=False)),
Column('id', TEXT(), table=<people>, nullable=False),
Column('age', INTEGER(), table=<people>),
Column('seniority', DOUBLE_PRECISION(precision=53), table=<people>),
Column('degree', TEXT(), table=<people>),
Column('level', TEXT(), table=<people>),
Column('type', TEXT(), table=<people>),
Column('start', TEXT(), table=<people>)]
"""
# 如果要获取所有列名,这里面的元素就可以当成普通的字符串使用了
print(people.c.keys(), type(people.c.keys())) # ['pk', 'id', 'age', 'seniority', 'degree', 'level', 'type', 'start'] <class 'list'>
# 如果要拿到某一个具体的字段的话
print(people.c.pk, type(people.c.pk)) # people.pk <class 'sqlalchemy.sql.schema.Column'>
# 也能查看类型,当然这个是Column对象的属性,什么查看字段名、类型、是否允许为空啊等等
print(people.c.pk.name, people.c.pk.type) # pk BIGINT
# 获取某个字段除了.的方式 还可以通过[""]
print(people.c["age"].name, people.c["age"].type) # age INTEGER
# 当然对people.c使用for循环也是可以的
for _ in people.c:
print(_)
"""
people.pk
people.id
people.age
people.seniority
people.degree
people.level
people.type
people.start
"""
# 获取主键,之前是通过inspect(class).primary_key
# 现在也是一样,只不过要是获取里面指定索引的元素,需要先转化为list对象
# 注意:筛选出来后,得到的依旧是一个Column对象,只不过它是主键的列对应的Column对象。查看一下字段名、是否自增
print(list(people.primary_key)[0].name, list(people.primary_key)[0].autoincrement) # pk True
print(type(list(people.primary_key)[0])) # <class 'sqlalchemy.sql.schema.Column'>
# orm查询,我们说使用p1 = Base.classess.people反射出来的类 和 p2 = metadata.tables["public.people"]反射出来的Table对象,或者说我们自定义的类和Table对象
# 这两种方式在查询上面没有什么区别,对于前者来讲直接可以通过session.query(p1.属性1, p1.属性2, ...)来查询
# 而对于后者,则需要session.query(p2.c.属性1, p2.c.属性2, ...)来查询
from sqlalchemy.orm import sessionmaker
session = sessionmaker(bind=engine)()
print(session.query(people.c.degree, people.c.id).first()) # ('大学本科毕业', '01010005306')
# 如果查询所有字段的话,那么直接通过people即可,不需要people.c
print(session.query(people).first()) # (7, '01010005306', 31, 9.6, '大学本科毕业', '助理经济师', '管理人员', '2009-11-05')
# 至于在工作中,你想使用哪种反射方式,看你自己的需求
# 在这里主要是希望你能明白,Table对象的一些操作,以及它和inspect的一些关系
# 我们之前是通过Base.classes.table这种方式反射出来的,然后传到inspect里面,假设返回值叫"_"好吧
# 那么我们通过_.local_table获取本地表的时候,其实得到的就是这里的Table对象
# 通过_.columns获取所有的Column对象的时候,我们通过Table对象下的c或者columns也能获取
# 通过_.c.keys()获取所有列名的时候,通过Table对象下的.c.keys()也能
# 通过_.primary_key[0]获取主键对应的Column对象的时候,通过list(Table().primary_key)[0]也可以
# 因此如果获取表和字段信息的话,我个人建议还是反射成Table对象直接获取比较方便。
# 至于反射出来的Table对象和反射出来的类之间的在查询数据的差别
# 也在于类直接通过".列名"就可以调用,因为"列名"就是类的一个属性
# 而Table对象需要通过".c.列名"来进行调用
我们本来讲的是Table的,结果发现里面也说了不少Column的知识,因此Column就不单独说了,这里再总结一下。
Column:数据库表中每一个字段、或者说每一列就对应一个Column对象。通过Column对象下的name、type、comment等等可以查看数据库表中对应字段的名称、类型、注释等等属性。
Table对象:数据库中的一张表则对应一个Table对象,一张表不是有很多字段吗?同理一个Table对象里面有很多的Column。表有名字,Table对象里面也可以指定名字;表有注释,Table里面也可以指定注释,这个我们没有说,但是通过参数comment是可以指定的。注意:Table里面的参数顺序要按照 表名、metadata、Column对象(一个或者多个) 这样的顺序,然后像什么schema、comment跟在后面通过关键字参数指定即可。
MetaData对象:这个是用来存储元信息的,我们创建的Table对象的表名、列信息等等都存储在metadata里面,然后使用metadata.create_all(),那么metadata会根据自身保存的Table对象来映射出数据库中对应的表,数据库中的表是什么样子,则取决于我们的Table对象,是一一对应的。当然metadata里面可以存储多个Table对象,这样就可以一次性映射多张表。同理,我们还可以反射表,那么会将数据库中的表反射成Table对象,存储在metadata里面,我们可以通过metadata.tables去获取,获取到的Table对象是什么样子,则取决于数据库中的表是什么样子。
表对应的类对象:我们说可以像初学sqlalchemy那样,通过继承Base = declarative_base(bind=engine)那样,自定义一个类映射成表。同理也可以通过automap_base来反射一张表、变成类,自定义的类和反射出来的类本质是一样的,它们可以看成是对Table对象的一个封装,虽然不知道这样说是否准确,但是通过inspect(类).local_table得到的和Table对象是一样的,就这么理解吧。封装之后的结果就是:之前通过Table对象获取字段对应Column对象时,需要通过.c.列名,但是对于类来讲,直接通过.列名即可获取对应的Column对象,因为它已经变成了该类的同名属性了,比如:通过"类.id"获取到的就是数据库中"id"字段对应的Column,但是对于Tbale对象还是要通过Table().c.id获取。
orm查询:其实上面已经说得很明白了,这里不再赘述了。
通过Table对象来创建和删除表
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
# 我想把t1,t2,t3这三张表删除,我们不需要知道这三张表的字段是什么,只要有表名就可以
print(engine.table_names()) # ['interface', 'girl2', 'people', 't1', 't2', 't3']
metadata.drop_all(tables=[Table("t1", metadata),
Table("t2", metadata),
])
# 当然除了metadata之外,对Table对象调用create或drop,也是可以创建和删除对应表的
Table("t3", metadata).drop()
print(engine.table_names()) # ['interface', 'girl2', 'people']
肿么样,是不是很简单呢?
通过Table对象实现对表的增删改查
关于查,我们之前已经见识过了,当时我们是通过session来实现的,现在我们来看看Table对象自身如何进行增加改查。
查:
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
session = sessionmaker(bind=engine)()
metadata = MetaData(bind=engine)
metadata.reflect(schema="public", only=["people"])
people = metadata.tables["public.people"]
# 关于查询数据,我个人还是推荐使用session的方式
print(session.query(people).filter(people.c.age > 50).all())
# [(20, '01010005392', 51, 26.7, '大学本科毕业', '会计师', '管理人员', '1992-10-05')]
增:
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
session = sessionmaker(bind=engine)()
metadata = MetaData(bind=engine)
metadata.reflect(schema="public", only=["t1"])
t1 = metadata.tables["public.t1"]
print(t1.c.keys()) # ['pk', 'name', 'age']
# 此时表为空
print(session.query(t1).all()) # []
# 注意需要使用engine执行一下
engine.execute(
# 需要指定哪些字段,就在字典里面写上哪些字段即可
# 没有指定的就按照数据库表的设计原则,该自增自增、该为空为空,如果不能为空就违反约束了
# 至于下面这一行如果直接打印的话是一条sql语句,所以需要放在engine.execute里面
t1.insert().values({"name": "古明地觉1"})
)
# 当然我们还可指定多条记录
engine.execute(
# 以列表的形式传递,但是需要注意:增加多条记录的时候,所有的字典的key要保持一致
# 假设第一个字典,指定了"name"和"age",那么第二个字典也要出现"name"和"age"否则报错。
t1.insert().values([{"name": "古明地觉2"}, {"name": "古明地觉3"}])
)
print(session.query(t1).all()) # [(1, '古明地觉1', None), (2, '古明地觉2', None), (3, '古明地觉3', None)]
改:
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
session = sessionmaker(bind=engine)()
metadata = MetaData(bind=engine)
metadata.reflect(schema="public", only=["t1"])
t1 = metadata.tables["public.t1"]
print(t1.c.keys()) # ['pk', 'name', 'age']
# 刚才创建的3条数据
print(session.query(t1).all()) # [(1, '古明地觉1', None), (2, '古明地觉2', None), (3, '古明地觉3', None)]
engine.execute(
# 将pk为1的记录对应的name和age字段进行修改
t1.update().where(t1.c.pk == 1).values({"name": "古明地觉1号", "age": 17})
)
# 修改成功,但是获取的时候跑在了后面,不过顺序啥的无所谓
print(session.query(t1).all()) # [(2, '古明地觉2', None), (3, '古明地觉3', None), (1, '古明地觉1号', 17)]
engine.execute(
# 不加where,则是全部修改,但是比较危险。注意:最好加上where,如果你不是修改所以记录的话
t1.update().values({"age": 18})
)
print(session.query(t1).all()) # [(2, '古明地觉2', 18), (3, '古明地觉3', 18), (1, '古明地觉1号', 18)]
删:
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
session = sessionmaker(bind=engine)()
metadata = MetaData(bind=engine)
metadata.reflect(schema="public", only=["t1"])
t1 = metadata.tables["public.t1"]
print(t1.c.keys()) # ['pk', 'name', 'age']
# 新增几条数据
print(session.query(t1).all())
# [(2, '古明地觉2', 18), (3, '古明地觉3', 18), (1, '古明地觉1号', 18), (4, '古明地觉4', 17), (5, '古明地觉5', 17), (6, '古明地觉6', 17)]
# 删除数据
engine.execute(
t1.delete(t1.c.pk == 1)
)
# pk为1的记录已经没了
print(session.query(t1).all())
# [(2, '古明地觉2', 18), (3, '古明地觉3', 18), (4, '古明地觉4', 17), (5, '古明地觉5', 17), (6, '古明地觉6', 17)]
engine.execute(
# > < == != >= <=什么的是可以直接写的
# 但是in、is None什么的不能这么干
t1.delete(t1.c.pk in (2, 3, 4))
)
# 会发现记录根本没有删除
print(session.query(t1).all())
# [(2, '古明地觉2', 18), (3, '古明地觉3', 18), (4, '古明地觉4', 17), (5, '古明地觉5', 17), (6, '古明地觉6', 17)]
# 我们来看看打印的sql语句,因为t1.c.pk in (2, 3, 4)得到的结果为False
print(t1.delete(t1.c.pk in (2, 3, 4))) # DELETE FROM public.t1 WHERE false
# 所以这就涉及到sqlalchemy的基础了,我们应该这么做
engine.execute(
t1.delete(t1.c.pk.in_([2, 3, 4]))
)
# 这样做就没有问题了,同理:删除为null的,也不能直接 t1.c.pk is None,而是要t1.c.pk.is_(None)
print(session.query(t1).all()) # [(5, '古明地觉5', 17), (6, '古明地觉6', 17)]
# 如果delete里面啥也没有,那么就是全部删除
engine.execute(
# 这种做法比较危险
t1.delete()
)
print(session.query(t1).all()) # []
print(t1.delete()) # DELETE FROM public.t1
反射引擎查看相关属性
我们刚才是反射表来查看属性,但是反射引擎可以得到更多有用的信息。不信我们来看看:
from pprint import pprint
from sqlalchemy import create_engine
from sqlalchemy import inspect
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
"""
from sqlalchemy.engine import reflection
res = reflection.Inspector.from_engine(engine)
"""
# 两种方式都是一样的,返回的是什么结果则与你所查询的数据库有关。没错,我们没有反射表,我们反射的是引擎。
res = inspect(engine)
# 有些时候,我们可以不需要得到Table对象,如果反射引擎能够帮我们获取到想要的结果的话
# 那么反射引擎能够给我们带来什么结果呢?
# 查看默认的schema
print(res.default_schema_name) # public
# 查看指定的表中存在的约束,我目前的表中没有约束
# 我们来增加一个吧,alter table people add constraint age_ge_20 check(age >= 20);
print(res.get_check_constraints("people", schema="public")) # [{'name': 'age_ge_20', 'sqltext': 'age >= 20'}]
# 如果不存在check定义的约束,那么返回的是[],注意:这里返回的约束是check定义的
# 如果是alter table people add constraint unique_id unique(id); 那么是无法得到的,因为它不是check定义的约束
# 返回指定的表中的所有列的信息
pprint(res.get_columns("people", schema="public"))
# 返回的是一个列表,列表里面是一个个的字典,每个字典代表每一列的信息
# 至于含义我就不解释了,从名字也能看出来
"""
[{'autoincrement': True,
'comment': None,
'default': 'nextval(\'"public".serial_user_id\'::regclass)',
'name': 'pk',
'nullable': False,
'type': BIGINT()},
{'autoincrement': False,
'comment': None,
'default': None,
'name': 'id',
'nullable': False,
'type': TEXT()},
......
......
......
"""
# 查询指定表的外键
print(res.get_foreign_keys("people", schema="public")) # []
# 我这里没有外键,但是返回的格式如下:
"""
[
{
"name": "外键约束的名字",
"constrained_columns": ["外键作用的列"], # 可能有多个,所以是一个列表
"referred_schema": "外键引用的表所在的schema",
"referred_table": "外键引用的表",
"referred_columns": ["外键引用的列"] # 可能引用多个列,所以是一个列表
},
{
... # 由于一张表可能存在多个外键,几个外键,那么列表里面就几个字典
}
]
"""
# 查看一张表的索引
pprint(res.get_indexes("people", schema="public"))
"""
[{'column_names': ['pk'], 'name': 'ix_t_pk', 'unique': False},
{'column_names': ['id'],
'duplicates_constraint': 'unique_id',
'name': 'unique_id',
'unique': True}]
"""
# 查看一张表的主键约束
print(res.get_pk_constraint("people", schema="public"))
# {'constrained_columns': ['pk'], 'name': 't_pkey'}
# 查看所有的schema
print(res.get_schema_names()) # ['anime', 'information_schema', 'public']
# 查看表注释,这里没有注释
print(res.get_table_comment("people", schema="public")) # {'text': None}
# 查看存在的所有表,默认是public
print(res.get_table_names()) # ['interface', 'girl2', 'people']
print(res.get_table_names(schema="anime")) # ['girl1']
# 返回当给定名称的表被创建时所指定的选项字典
# 当前包括一些适用于MySQL中的表的选项
print(res.get_table_options("people", schema="public")) # {}
# 返回唯一约束
print(res.get_unique_constraints("people", schema="public")) # [{'name': 'unique_id', 'column_names': ['id']}]
"""
get_check_constraints: 返回check定义的约束,一个列表
get_pk_constraint: 返回主键约束,一个字典
get_unique_constraints: 返回unique定义的约束,我们刚才在介绍get_check_constraints的时候定义的
"""
# 传入一个视图名,返回这个视图的定义语法
# 我们来定义一个吧: create view people_view as select pk, id, degree from public.people
print(res.get_view_definition("people_view", schema="public"))
"""
SELECT people.pk,
people.id,
people.degree
FROM people;
"""
# 返回存在的所有的视图名
print(res.get_view_names(schema="public")) # ['people_view']
sqlalchemy真的。。。好强大
Column的默认值操作
我们在创建表的时候,可以给某个字段指定默认值,那么sqlalchemy支持哪些默认值相关的操作呢,我们来看一下。
默认值: 普通的标量
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Text
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
girl = Table(
"girl",
metadata,
Column("pk", Integer, primary_key=True,autoincrement=True),
# default: 当插入一条数据的时候,如果不指定age,那么默认是16
Column("age", Integer, default=16),
# onupdate: 当修改一条数据的时候,如果没有设置gender,那么gender自动会变成"female"
# 这个功能一般会用于记录"修改时间",比如onupdate为当前时间,每当你修改记录的时候,那么会自动记录为你修改的时间
Column("gender", Text, onupdate="female"),
schema="anime"
)
metadata.create_all()
insert = girl.insert().values({"gender": "male"})
engine.execute(insert)
session = sessionmaker(bind=engine)()
# 我们看到age变成了16,这是我们设置的默认值
print(session.query(girl).first()) # (1, 16, 'male')
update = girl.update().where(girl.c.age == 16).values({"age": 17})
engine.execute(update)
# 我们看到,当我们修改一条记录的时候,如果没有指定gender,那么gender会默认变成"female",因为我们的onupdate指定的就是"female"
print(session.query(girl).first()) # (1, 17, 'female')
# 因此我个人觉得onupdate存在的意义就在于时间,设置为当前时间
# 如果你编辑了这条记录,那么就会变成你当前编辑这条记录的时间
默认值: python的可执行函数
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Text
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
session = sessionmaker(bind=engine)()
i = 0
def default():
global i
i += 1
return f"古明地觉{i}号"
girl = Table(
"girl",
metadata,
Column("pk", Integer, primary_key=True,autoincrement=True),
# 我们把default指定成我们创建的函数default
Column("name", Text, default=default),
schema="anime"
)
metadata.drop_all()
metadata.create_all()
# 不指定values,那么默认会插入一条全部都是null的记录
engine.execute(girl.insert())
# 或者指定为空字典
engine.execute(girl.insert().values([{}] * 5))
print(session.query(girl).all())
# [(1, '古明地觉1号'), (2, '古明地觉2号'), (3, '古明地觉3号'), (4, '古明地觉4号'), (5, '古明地觉5号'), (6, '古明地觉6号')]
"""
神奇的事情发生了,我们看到默认值的生成方式和我们函数指定的一样。
因为每创建一条记录,会指定一遍default函数,当然sqlalchemy肯定是把我们定义的default函数翻译成了数据库中对应的函数
只是生成的规则,我们可以按照python的方式来理解。而因为每次都会执行default函数,所以这个变量i必须是全局的,否则每一次返回的都是1
只要我们按照python的方式可以理解,就行,至于sqlalchemy是怎么翻译的,我们不需要关心。
"""
再比如:
import datetime, time
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Text, DateTime
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
session = sessionmaker(bind=engine)()
girl = Table(
"girl",
metadata,
Column("pk", Integer, primary_key=True,autoincrement=True),
Column("name", Text),
# 设置为当前时间,当然onupdate也是可以的
# 如果是onupdate,那么当你把记录修改的时候,时间会变成当前修改记录时所处的时间
Column("modify_time", DateTime, default=datetime.datetime.now),
schema="anime"
)
metadata.drop_all()
metadata.create_all()
engine.execute(girl.insert())
time.sleep(2)
engine.execute(girl.insert())
time.sleep(2)
engine.execute(girl.insert())
print(session.query(girl).all())
"""
[(1, None, datetime.datetime(2020, 2, 22, 18, 17, 26, 46777)),
(2, None, datetime.datetime(2020, 2, 22, 18, 17, 28, 48015)),
(3, None, datetime.datetime(2020, 2, 22, 18, 17, 30, 49788))]
"""
除此之外,还可以根据你当前的某个字段来定义默认值,比如:
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Text, DateTime
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
session = sessionmaker(bind=engine)()
def default(context):
# 这里面接收一个context,可以理解为插入或者修改的某个行记录的上下文
# 调用get_current_parameters方法,可以拿到内部的所有参数。就是当前行记录中每个字段对应的值,可以理解为一个字典
# 通过"age"字段拿到当前行记录中age这个列对应的值
age = context.get_current_parameters()["age"]
if not age:
return None
elif age >= 18:
return "成年"
else:
return "未成年"
girl = Table(
"girl",
metadata,
Column("pk", Integer, primary_key=True, autoincrement=True),
Column("age", Integer),
# 指定为我们的default函数,那么插入记录的时候,会自动把当前记录的上下文传给context
Column("status", Text, default=default),
schema="anime"
)
metadata.drop_all()
metadata.create_all()
engine.execute(girl.insert().values([{"age": 18}, {"age": None}, {"age": 15}]))
print(session.query(girl).all()) # [(1, 18, '成年'), (2, None, None), (3, 15, '未成年')]
默认值:SQL表达式
SQL中的函数都在sqlalchemy.func里面。
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Text, DateTime, Date,func
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
session = sessionmaker(bind=engine)()
girl = Table(
"girl",
metadata,
Column("pk", Integer, primary_key=True, autoincrement=True),
Column("age", Integer),
Column("create_date", Date, default=func.current_date()),
schema="anime"
)
metadata.drop_all()
metadata.create_all()
engine.execute(girl.insert().values([{"age": 18}, {"age": None}, {"age": 15}]))
print(session.query(girl).all())
# [(1, 18, datetime.date(2020, 2, 22)), (2, None, datetime.date(2020, 2, 22)), (3, 15, datetime.date(2020, 2, 22))]
还有其它黑科技
from sqlalchemy import create_engine
from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Computed
from sqlalchemy.orm import sessionmaker
engine = create_engine("postgresql://postgres:zgghyys123@localhost:5432/postgres")
metadata = MetaData(bind=engine)
session = sessionmaker(bind=engine)()
girl = Table(
"girl",
metadata,
Column("pk", Integer, primary_key=True, autoincrement=True),
Column("age", Integer),
# 会自动生成一个新列,age_incr_1
# age + 1里面的age就是数据库中字段age对应的值,但是注意:这个语法是在pgsql12中才有的,之前的版本不支持
Column("age_incr_1", Integer, Computed("age + 1")),
schema="anime"
)
# 我这里的版本比较低,是9.几 的版本,所以不支持,
# 但是对应的底层sql语句如下
"""
CREATE TABLE girl (
pk SERIAL NOT NULL,
age INTEGER,
age_incr_1 INTEGER GENERATED ALWAYS AS (age + 1) STORED,
PRIMARY KEY (id)
)
"""