SQLAlchemy

一、分类

 1.SQLAlchemy 以两个不同的 API 呈现,一个构建在另一个之上。这些 API 被称为 Core 和 ORM

 2.SQLAlchemy Core 是 SQLAlchemy 作为“数据库工具包”的基础架构。该库提供了用于管理数据库连接、与数据库查询和结果交互以及程序化构建 SQL 语句的工具。主要为 Core-only 的章节不会提及 ORM。在这些章节中使用的 SQLAlchemy 构造将从 sqlalchemy 命名空间导入。作为主题分类的额外指示,它们还将在右侧包含深蓝色边框。当使用 ORM 时,这些概念仍然在起作用,但在用户代码中不常显式出现。ORM 用户应该阅读这些章节,但不希望直接为以 ORM 为中心的代码使用这些 API。

3.SQLAlchemy ORM 构建在 Core 之上,以提供可选的 对象关系映射 功能。ORM 提供了一个额外的配置层,允许用户定义的 Python 类 映射 到数据库表和其他构造,以及一种称为 会话 的对象持久化机制。然后,它扩展了 Core 级别的 SQL 表达式语言,以允许根据用户定义的对象组成和调用 SQL 查询。主要为 ORM-only 的章节应标题包含短语“ORM”,以便清楚表明这是一个与 ORM 相关的主题。在这些章节中使用的 SQLAlchemy 构造将从 sqlalchemy.orm 命名空间导入。最后,作为主题分类的额外指示,它们还将在左侧包含浅蓝色边框

下面是用Core进行讲解的

二、建立连接

1.解释

每个连接到数据库的 SQLAlchemy 应用程序都需要使用 Engine(此对象充当特定数据库连接的中心来源,既提供工厂,又为这些数据库连接提供名为 连接池 的持有空间)。

2.语法:

from sqlalchemy import create_engine
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

参数说明:

        (1)第一个参数表示:与哪个数据库连接进行连接、我们正在使用什么 DBAPI(此时使用的是pysqlite,若省略则默认使用DBAPI)、如何定位数据库(/:内存数据库:,它是 sqlite3 模块的指示符,表明我们将使用仅在内存中的数据库,若是在mysql中的数据库可表示如下/user:password@localhost(或者是IP地址):端口号/要用的数据库名)。

        (2)echo = True: 此标志是设置 python的更正式日志记录的简写方式。

3.使用事务和 DBAPI

Engine 的目的是通过提供 Connection 对象来连接数据库。当直接使用 Core 时,Connection 对象是与数据库进行所有交互的方式。由于 Connection 会创建针对数据库的开放资源,我们希望将此对象的使用限制在特定上下文中。最好的方法是使用 Python 上下文管理器,也称为 with 语句。下面我们使用文本 SQL 语句来显示 “Hello World”。文本 SQL 是使用名为 text() 的构造创建的并返回result对象

#第一种有engine的连接
from sqlalchemy import text

with engine.connect() as conn:
        result = conn.execute(text("select 'hello world'"))
        print(result.all())

#第二种无engine的连接
from sqlalchemy import Connection

con = Connection(
        host='192.168.31.130',
        port=3306,
        user='root',
        password='123456',
        database='flask_1',
        autocommit=True,#自动提交事务
    )
cur = con.cursor()
sql = 'select * from comment where text!=""'
cur.execute(sql)
print(cur.fetchall())
cur.close()#断开连接

        在上面的示例中,上下文管理器创建数据库连接并在事务中执行操作,Python DBAPI 的默认行为是事务始终在进行中;当连接释放时,会发出 ROLLBACK 以结束事务。事务不会自动提交;如果我们想提交数据,我们需要调用 Connection.commit(),此为逐个提交

with engine.connect() as conn:
        conn.execute(text("CREATE TABLE some_table (x int, y int)"))
        conn.execute(
           text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
            [{"x": 1, "y": 1}, {"x": 2, "y": 4}],
        )
        conn.commit()
#mysql中现实的格式
BEGIN (implicit)
CREATE TABLE some_table (x int, y int)
[...] ()
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
INSERT INTO some_table (x, y) VALUES (?, ?)
[...] [(1, 1), (2, 4)]
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
COMMIT

        其中“implicit” 这里意味着 SQLAlchemy 实际上没有向数据库发送任何命令;它只是认为这是 DBAPI 隐式事务的开始。您可以注册 事件钩子 来拦截此事件

        我们使用 Engine.begin() 方法获取连接,而不是Engine.connect() 方法。此方法将理 Cnnection 的范围,并将所有内容都包含在事务中,如果代码块成功,则在末尾使用COMMIT,如果引发异常,则使用 ROLLBACK。这种风格被称为一次开始

 with engine.begin() as conn:
        conn.execute(
            text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
            [{"x": 6, "y": 8}, {"x": 9, "y": 10}],
         )

我们将首先更详细地说明 Result对象,方法是使用我们之前插入的行,对我们创建的表运行文本 SELECT 语句

with engine.connect() as conn:
     result = conn.execute(text("SELECT x, y FROM some_table"))
     for row in result:
         print(f"x: {row.x}  y: {row.y}")

Result有许多用于获取和转换行的方法,例如之前说明的Result.all()方法,它返回所有Row对象的列表。它还实现了 Python 迭代器接口,以便我们可以直接迭代Row对象的集合

Row 对象本身旨在像 Python 命名元组 一样工作

  • 元组赋值 - 这是最符合 Python 习惯的风格,即将变量按位置分配给接收到的每个行

  • 整数索引 - 元组是 Python 序列,因此也提供常规整数访问

    result = conn.execute(text("select x, y from some_table"))
    
    for row in result:
        x = row[0]
  • 属性名称 - 由于这些是 Python 命名元组,因此元组具有与每列名称匹配的动态属性名称。这些名称通常是 SQL 语句分配给每行中列的名称。虽然它们通常相当可预测,并且也可以通过标签来控制,但在不太明确的情况下,它们可能会受到数据库特定行为的影响

    result = conn.execute(text("select x, y from some_table"))
    
    for row in result:
        y = row.y
    
        # illustrate use with Python f-strings
        print(f"Row: {row.x} {y}")
  • 映射访问 - 要将行接收为 Python 映射 对象,这本质上是 Python 用于常见 dict 对象的只读接口,可以使用 Result.mappings() 修饰符将 Result 转换为 MappingResult 对象;这是一个结果对象,它生成类似字典的 RowMapping 对象,而不是 Row 对象

    result = conn.execute(text("select x, y from some_table"))
    
    for dict_row in result.mappings():
        x = dict_row["x"]
        y = dict_row["y"]

三、使用Table对象

1.前提

用Table对象的前提,创建一个集合,该集合是放置表的MetaData对象

        MetaData 是一个容器对象,用于将数据库(或多个数据库)被描述的许多不同功能组合在一起。

from sqlalchemy import MetaData
metadata_obj = MetaData()
from sqlalchemy import Table, Column, Integer, String
user_table = Table(
    "user_account",#数据表名
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(30)),
    Column("fullname", String),
)

用外键约束

from sqlalchemy import ForeignKey
address_table = Table(
    "address",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("user_id", ForeignKey("user_account.id"), nullable=False),
    Column("email_address", String, nullable=False),#not null用nullable表示
)

向数据库发送DDL

MetaData.create_all()#让数据库出发create table语句

MetaData.drop_all()#让数据库触发drop语句

四、处理数据

1.insert():构造用于所有的Insert插入

from sqlalchemy import insert
#stmt为Insert实例
stmt = insert(user_table).values(name="spongebob", fullname="Spongebob Squarepants")
print(stmt)#返回INSERT INTO user_account (name, fullname) VALUES (:name, :fullname)
#字符串化形式是通过生成对象的Compiled形式来创建的
compiled = stmt.compile()
compiled = stmt.compile()#返回{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}
#用下面的语句,提交到数据库中。
with engine.connect() as conn:
     result = conn.execute(stmt)
     conn.commit()

        如果我们采用一个尚未调用Insert.value()的Insert构造,并执行它而不是打印它,则该语句将根据我们传递给Connect.execute()方法的参数编译成字符串,并且仅包含与传递的参数相关的列。这实际上是使用Insert插入行的常用方法,而无需键入显式的 VALUES 子句

with engine.connect() as conn:
    result = conn.execute(
        insert(user_table),
        [
            {"name": "sandy", "fullname": "Sandy Cheeks"},
            {"name": "patrick", "fullname": "Patrick Star"},
        ],
    )
    conn.commit()

        深入了解Insert.values()方法

from sqlalchemy import select, bindparam
scalar_subq = (
    select(user_table.c.id)
    .where(user_table.c.name == bindparam("username"))
    .scalar_subquery()
)

with engine.connect() as conn:
    result = conn.execute(
        insert(address_table).values(user_id=scalar_subq),
        [
            {
                "username": "spongebob",
                "email_address": "spongebob@sqlalchemy.org",
            },
            {"username": "sandy", "email_address": "sandy@sqlalchemy.org"},
            {"username": "sandy", "email_address": "sandy@squirrelpower.org"},
        ],
    )
    conn.commit()

#上传到mysql后显示的格式
BEGIN (implicit)
INSERT INTO address (user_id, email_address) VALUES ((SELECT user_account.id
FROM user_account
WHERE user_account.name = ?), ?)
[...] [('spongebob', 'spongebob@sqlalchemy.org'), ('sandy', 'sandy@sqlalchemy.org'),
('sandy', 'sandy@squirrelpower.org')]
COMMIT

(1)RETURNING 子句会自动用于检索最后插入的主键值以及服务器默认值的值

insert_stmt = insert(address_table).returning(address_table.c.id, address_table.c.email_address)
print(insert_stmt)
#mysql中的格式
INSERT INTO address (id, user_id, email_address)
VALUES (:id, :user_id, :email_address)
RETURNING address.id, address.email_address

(2)与from_select()结合使用

select_stmt = select(user_table.c.id, user_table.c.name + "@aol.com")
insert_stmt = insert(address_table).from_select(
    ["user_id", "email_address"], select_stmt
)
print(insert_stmt.returning(address_table.c.id, address_table.c.email_address))
#上传到MySQL中的格式
INSERT INTO address (user_id, email_address)
SELECT user_account.id, user_account.name || :name_1 AS anon_1
FROM user_account RETURNING address.id, address.email_address

2.select():构造用于所有 SELECT 查询

from sqlalchemy import select
stmt = select(user_table).where(user_table.c.name == "spongebob")
print(stmt)
#mysql中显示
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1

由于 SELECT 语句返回行,因此我们始终可以迭代结果对象以获取 Row 对象

with engine.connect() as conn:
    for row in conn.execute(stmt):
        print(row)

使用文本列表达式选择

from sqlalchemy import text
from sqlalchemy import literal_column
#方法一
stmt = select(text("'some phrase'"), user_table.c.name).order_by(user_table.c.name)
with engine.connect() as conn:
    print(conn.execute(stmt).all())
#mysql中
BEGIN (implicit)
SELECT 'some phrase', user_account.name
FROM user_account ORDER BY user_account.name
[generated in ...] ()
'''[('some phrase', 'patrick'), ('some phrase', 'sandy'), ('some phrase', 'spongebob')]'''
ROLLBACK
#方法二
stmt = select(literal_column("'some phrase'").label("p"), user_table.c.name).order_by(
    user_table.c.name
)
with engine.connect() as conn:
    for row in conn.execute(stmt):
        print(f"{row.p}, {row.name}")
#mysql
BEGIN (implicit)
SELECT 'some phrase' AS p, user_account.name
FROM user_account ORDER BY user_account.name
[generated in ...] ()
'''some phrase, patrick
some phrase, sandy
some phrase, spongebob'''
ROLLBACK

(1)WHERE子句

print(select(user_table).where(user_table.c.name == "squidward"))
#mysql
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1

        同时可以通过多个WHERE子句,并由“.”连接生成AND连接的多个表达式;或者是在where中用and_()和or_()函数,进行连接。

(2).join_from():用来连接两个表,显式指示 JOIN 的左侧和右侧

print(
    select(user_table.c.name, address_table.c.email_address).join_from(
        user_table, address_table
    )
)
#mysql
SELECT user_account.name, address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id

(3)join():用来连接两个表,仅只是 JOIN 的右侧,左侧靠推断

3.带有 GROUP BY / HAVING 的聚合函数

(1)使用名为func的命名空间来进行构造。

        count()表示SQL COUNT函数

        lower()将字符串转换为小写

    now()用于提供当前日期和时间

        group_by()表示GROUP BY函数

        having()表示HAVING函数

        order_by(' ',asc/desc(' '))表示ORDER BY 函数

        alias()表示AS起别名

from sqlalchemy import func
from sqlalchemy import func, desc

count_fn = func.count(user_table.c.id)
print(count_fn)

with engine.connect() as conn:
    result = conn.execute(
        select(User.name, func.count(Address.id).label("count"))
        .join(Address)
        .group_by(User.name)
        .having(func.count(Address.id) > 1)
    )
    print(result.all())


stmt = (
    select(Address.user_id, func.count(Address.id).label("num_addresses"))
    .group_by("user_id")
    .order_by("user_id", desc("num_addresses"))
)
print(stmt)

user_alias_1 = user_table.alias()
user_alias_2 = user_table.alias()
print(
    select(user_alias_1.c.name, user_alias_2.c.name).join_from(
        user_alias_1, user_alias_2, user_alias_1.c.id > user_alias_2.c.id
    )
)

4.子查询和CTE

(1)Subquery对象表示子查询,CTE表示CTE。

        subquery()

        cte()

        scalar_subquery()表示标准子查询

subq = (
    select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
    .group_by(address_table.c.user_id)
    .subquery()
)

print(subq)

#进行子查询
print(select(subq.c.user_id, subq.c.count))

#共用表表达式
subq = (
    select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
    .group_by(address_table.c.user_id)
    .cte()
)

stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
    user_table, subq
)

print(stmt)

(2)相关子查询

        我们使用correlate()或corrwlate_except()指定要关联的表(相关性不明确的情况下,SQLAlchemy 会让我们知道需要更清晰的说明,容易出错)

subq = (
    select(func.count(address_table.c.id))
    .where(user_table.c.id == address_table.c.user_id)
    .scalar_subquery()
    .correlate(user_table)
)

#返回此列数据
with engine.connect() as conn:
    result = conn.execute(
        select(
            user_table.c.name,
            address_table.c.email_address,
            subq.label("address_count"),
        )
        .join_from(user_table, address_table)
        .order_by(user_table.c.id, address_table.c.id)
    )
    print(result.all())

(3)EXISTS子查询

SQL EXISTS 关键字是一个运算符,与标量子查询一起使用,以返回布尔值 true 或 false,具体取决于 SELECT 语句是否会返回行.

subq = (
    select(func.count(address_table.c.id))
    .where(user_table.c.id == address_table.c.user_id)
    .group_by(address_table.c.user_id)
    .having(func.count(address_table.c.id) > 1)
).exists()
with engine.connect() as conn:
    result = conn.execute(select(user_table.c.name).where(subq))
    print(result.all())

5.update和delete

(1)update

from sqlalchemy import update
stmt = (
    update(user_table)
    .where(user_table.c.name == "patrick")
    .values(fullname="Patrick the Star")
)
print(stmt)

#mysql
UPDATE user_account SET fullname=:fullname WHERE user_account.name = :name_1

        Update.values()方法控制 UPDATE 语句的 SET 元素的内容

        另一个仅 MySQL 的行为是,UPDATE 语句的 SET 子句中参数的顺序实际上会影响每个表达式的评估,ordered_values()接受元组序列,以便可以控制此顺序。

update_stmt = update(some_table).ordered_values(
    (some_table.c.y, 20), (some_table.c.x, some_table.c.y + 10)
)
print(update_stmt)

(2)delete

from sqlalchemy import delete
stmt = delete(user_table).where(user_table.c.name == "patrick")
print(stmt)

多表删除

delete_stmt = (
    delete(user_table)
    .where(user_table.c.id == address_table.c.user_id)
    .where(address_table.c.email_address == "patrick@aol.com")
)
from sqlalchemy.dialects import mysql
print(delete_stmt.compile(dialect=mysql.dialect()))

与returning语句一起使用

update_stmt = (
    update(user_table)
    .where(user_table.c.name == "patrick")
    .values(fullname="Patrick the Star")
    .returning(user_table.c.id, user_table.c.name)
)
print(update_stmt)
delete_stmt = (
    delete(user_table)
    .where(user_table.c.name == "patrick")
    .returning(user_table.c.id, user_table.c.name)
)
print(delete_stmt)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值