使用 Python、SQLite 和 SQLAlchemy 进行数据管理

2023-10-11

所有程序都以一种或另一种形式处理数据,并且许多程序需要能够在一次调用到下一次调用之间保存和检索该数据。 Python,SQLite, 和SQL炼金术为您的程序提供数据库功能,允许您将数据存储在单个文件中,而无需数据库服务器。

您可以使用以下方法获得类似的结果平面文件多种格式,包括 CSV、JSON、XML,甚至自定义格式。平面文件通常是人类可读的文本文件(尽管它们也可以是二进制数据),具有可由计算机程序解析的结构。下面,您将探索使用 SQL 数据库和平面文件进行数据存储和操作,并了解如何确定哪种方法适合您的程序。

在本教程中,您将学习如何使用:

  • 平面文件用于数据存储
  • SQL改善对持久数据的访问
  • SQLite用于数据存储
  • SQL炼金术将数据作为 Python 对象进行处理

您可以通过单击下面的链接获取本教程中看到的所有代码和数据:

下载示例代码: 单击此处获取您将使用的代码在本教程中了解如何使用 SQLite 和 SQLAlchemy 进行数据管理。

使用平面文件进行数据存储

A 平面文件是一个包含数据的文件,没有内部层次结构,通常没有对外部文件的引用。平面文件包含人类可读的字符,对于创建和读取数据非常有用。因为它们不必使用固定的字段宽度,所以平面文件通常使用其他结构来使程序能够解析文本。

例如,逗号分隔值 (CSV)文件是纯文本行,其中逗号字符分隔数据元素。每行文本代表一行数据,每个逗号分隔值都是该行中的一个字段。逗号字符分隔符指示数据值之间的边界。

Python 擅长读取和保存到文件。通过使用 Python 读取数据文件,您可以在稍后重新运行应用程序时将其恢复到有用状态。通过将数据保存在文件中,您可以在用户和运行应用程序的站点之间共享程序中的信息。

在程序可以读取数据文件之前,它必须能够理解数据。通常,这意味着数据文件需要具有某种结构,应用程序可以使用该结构来读取和解析文件中的文本。

下面是一个名为author_book_publisher.csv,由本教程中的第一个示例程序使用:

first_name,last_name,title,publisher
Isaac,Asimov,Foundation,Random House
Pearl,Buck,The Good Earth,Random House
Pearl,Buck,The Good Earth,Simon & Schuster
Tom,Clancy,The Hunt For Red October,Berkley
Tom,Clancy,Patriot Games,Simon & Schuster
Stephen,King,It,Random House
Stephen,King,It,Penguin Random House
Stephen,King,Dead Zone,Random House
Stephen,King,The Shining,Penguin Random House
John,Le Carre,"Tinker, Tailor, Soldier, Spy: A George Smiley Novel",Berkley
Alex,Michaelides,The Silent Patient,Simon & Schuster
Carol,Shaben,Into The Abyss,Simon & Schuster

第一行提供以逗号分隔的字段列表,这些字段是其余行中后面的数据的列名称。其余行包含数据,每行代表一条记录。

笔记:尽管作者、书籍和出版商都是真实的,但书籍和出版商之间的关系是虚构的,并且是为了本教程的目的而创建的。

接下来,您将了解使用上面的 CSV 等平面文件来处理数据的一些优点和缺点。

平面文件的优点

使用平面文件中的数据易于管理且易于实施。以人类可读的格式保存数据不仅有助于使用文本编辑器创建数据文件,而且有助于检查数据并查找任何不一致或问题。

许多应用程序可以导出文件生成的数据的平面文件版本。例如,Excel可以将 CSV 文件导入电子表格或从电子表格导出。如果您想共享数据,平面文件还具有独立且可传输的优点。

几乎每种编程语言都有工具和库,可以让您更轻松地处理 CSV 文件。 Python 有内置的csv模块和强大熊猫模块可用,使得使用 CSV 文件成为一个有效的解决方案。

平面文件的缺点

随着数据变大,使用平面文件的优势开始减弱。大文件仍然是人类可读的,但编辑它们以创建数据或查找问题变得更加困难。如果您的应用程序将更改文件中的数据,那么一种解决方案是将整个文件读入内存,进行更改,并将数据写入另一个文件。

使用平面文件的另一个问题是,您需要在文件语法中显式创建和维护数据部分与应用程序之间的任何关系。此外,您需要在应用程序中生成代码才能使用这些关系。

最后一个复杂之处是,您想要与之共享数据文件的人还需要了解您在数据中创建的结构和关系并采取行动。为了访问信息,这些用户不仅需要了解数据的结构,还需要了解访问数据所需的编程工具。

平面文件示例

示例程序examples/example_1/main.py使用author_book_publisher.csv文件以获取其中的数据和关系。此 CSV 文件维护作者列表、他们出版的书籍以及每本书的出版商。

笔记:示例中使用的数据文件可在project/data目录。里面还有一个程序文件project/build_data生成数据的目录。如果您更改数据并希望恢复到已知状态,则该应用程序非常有用。

要访问本节和整个教程中使用的数据文件,请单击下面的链接:

下载示例代码: 单击此处获取您将使用的代码在本教程中了解如何使用 SQLite 和 SQLAlchemy 进行数据管理。

上面显示的 CSV 文件是一个非常小的数据文件,仅包含少数作者、书籍和出版商。您还应该注意有关数据的一些事情:

  • 作者史蒂芬·金 (Stephen King) 和汤姆·克兰西 (Tom Clancy) 出现不止一次,因为数据中包含了他们出版的多本书。

  • 作者斯蒂芬·金和赛珍珠的同一本书由多个出版商出版。

这些重复的数据字段在数据的其他部分之间创建了关系。一位作者可以写多本书,一位出版商可以与多位作者合作。作者和出版商与单本书共享关系。

中的关系author_book_publisher.csv文件由在数据文件的不同行中多次出现的字段表示。由于这种数据冗余,数据代表的不仅仅是一个二维表。当您使用该文件创建 SQLite 数据库文件时,您会看到更多内容。

示例程序examples/example_1/main.py使用嵌入的关系author_book_publisher.csv文件来生成一些数据。它首先列出作者列表以及每位作者撰写的书籍数量。然后,它显示出版商列表以及每个出版商出版书籍的作者数量。

它还使用树库模块显示作者、书籍和出版商的树状层次结构。

最后,它向数据添加一本新书,并重新显示新书所在位置的树层次结构。这是主要的()该程序的入口点函数:

 1def main():
 2    """The main entry point of the program"""
 3    # Get the resources for the program
 4    with resources.path(
 5        "project.data", "author_book_publisher.csv"
 6    ) as filepath:
 7        data = get_data(filepath)
 8
 9    # Get the number of books printed by each publisher
10    books_by_publisher = get_books_by_publisher(data, ascending=False)
11    for publisher, total_books in books_by_publisher.items():
12        print(f"Publisher: {publisher}, total books: {total_books}")
13    print()
14
15    # Get the number of authors each publisher publishes
16    authors_by_publisher = get_authors_by_publisher(data, ascending=False)
17    for publisher, total_authors in authors_by_publisher.items():
18        print(f"Publisher: {publisher}, total authors: {total_authors}")
19    print()
20
21    # Output hierarchical authors data
22    output_author_hierarchy(data)
23
24    # Add a new book to the data structure
25    data = add_new_book(
26        data,
27        author_name="Stephen King",
28        book_title="The Stand",
29        publisher_name="Random House",
30    )
31
32    # Output the updated hierarchical authors data
33    output_author_hierarchy(data)

上面的Python代码执行以下步骤:

  • 4 至 7 号线阅读author_book_publisher.csv文件到 pandas DataFrame 中。
  • 第 10 至 13 行打印每个出版商出版的书籍数量。
  • 第 16 至 19 行打印与每个出版商相关的作者数量。
  • 22号线将图书数据输出为按作者排序的层次结构。
  • 第 25 至 30 行将一本新书添加到内存结构中。
  • 33号线将图书数据输出为按作者排序的层次结构,包括新添加的图书。

运行该程序会生成以下输出:

$ python main.py
Publisher: Simon & Schuster, total books: 4
Publisher: Random House, total books: 4
Publisher: Penguin Random House, total books: 2
Publisher: Berkley, total books: 2

Publisher: Simon & Schuster, total authors: 4
Publisher: Random House, total authors: 3
Publisher: Berkley, total authors: 2
Publisher: Penguin Random House, total authors: 1

Authors
├── Alex Michaelides
│   └── The Silent Patient
│       └── Simon & Schuster
├── Carol Shaben
│   └── Into The Abyss
│       └── Simon & Schuster
├── Isaac Asimov
│   └── Foundation
│       └── Random House
├── John Le Carre
│   └── Tinker, Tailor, Soldier, Spy: A George Smiley Novel
│       └── Berkley
├── Pearl Buck
│   └── The Good Earth
│       ├── Random House
│       └── Simon & Schuster
├── Stephen King
│   ├── Dead Zone
│   │   └── Random House
│   ├── It
│   │   ├── Penguin Random House
│   │   └── Random House
│   └── The Shining
│       └── Penguin Random House
└── Tom Clancy
    ├── Patriot Games
    │   └── Simon & Schuster
    └── The Hunt For Red October
        └── Berkley

上面的作者层次结构在输出中出现了两次,并添加了 Stephen King 的展台,由兰登书屋出版。上面的实际输出已被编辑,并且仅显示第一个层次结构输出以节省空间。

main()调用其他函数来执行大部分工作。它调用的第一个函数是get_data():

def get_data(filepath):
    """Get book data from the csv file"""
    return pd.read_csv(filepath)

该函数获取 CSV 文件的文件路径,并使用 pandas 将其读入熊猫数据框,然后将其传递回调用者。该函数的返回值成为传递给组成程序的其他函数的数据结构。

get_books_by_publisher()计算每个出版商出版的书籍数量。由此产生的熊猫系列使用熊猫通过...分组按发布者分组的功能,然后种类基于ascending旗帜:

def get_books_by_publisher(data, ascending=True):
    """Return the number of books by each publisher as a pandas series"""
    return data.groupby("publisher").size().sort_values(ascending=ascending)

get_authors_by_publisher()本质上与之前的函数执行相同的操作,但对于作者来说:

def get_authors_by_publisher(data, ascending=True):
    """Returns the number of authors by each publisher as a pandas series"""
    return (
        data.assign(name=data.first_name.str.cat(data.last_name, sep=" "))
        .groupby("publisher")
        .nunique()
        .loc[:, "name"]
        .sort_values(ascending=ascending)
    )

add_new_book()在 pandas DataFrame 中创建一本新书。该代码检查作者、书籍或出版商是否已存在。如果没有,那么它会创建一本新书并将其附加到 pandas DataFrame 中:

def add_new_book(data, author_name, book_title, publisher_name):
    """Adds a new book to the system"""
    # Does the book exist?
    first_name, _, last_name = author_name.partition(" ")
    if any(
        (data.first_name == first_name)
        & (data.last_name == last_name)
        & (data.title == book_title)
        & (data.publisher == publisher_name)
    ):
        return data
    # Add the new book
    return data.append(
        {
            "first_name": first_name,
            "last_name": last_name,
            "title": book_title,
            "publisher": publisher_name,
        },
        ignore_index=True,
    )

output_author_hierarchy()使用嵌套for 循环迭代数据结构的各个级别。然后它使用treelib模块输出作者、他们出版的书籍以及出版这些书籍的出版商的分层列表:

def output_author_hierarchy(data):
    """Output the data as a hierarchy list of authors"""
    authors = data.assign(
        name=data.first_name.str.cat(data.last_name, sep=" ")
    )
    authors_tree = Tree()
    authors_tree.create_node("Authors", "authors")
    for author, books in authors.groupby("name"):
        authors_tree.create_node(author, author, parent="authors")
        for book, publishers in books.groupby("title")["publisher"]:
            book_id = f"{author}:{book}"
            authors_tree.create_node(book, book_id, parent=author)
            for publisher in publishers:
                authors_tree.create_node(publisher, parent=book_id)

    # Output the hierarchical authors data
    authors_tree.show()

该应用程序运行良好,并说明了 pandas 模块的可用功能。该模块提供了读取 CSV 文件和与数据交互的出色功能。

让我们继续使用 Python、作者和出版物数据的 SQLite 数据库版本以及 SQLAlchemy 来创建一个功能相同的程序来与该数据进行交互。

使用 SQLite 保存数据

正如您之前所看到的,数据中存在冗余数据author_book_publisher.csv文件。例如,有关赛珍珠的所有信息美好地球被列出两次,因为两个不同的出版商出版了这本书。

想象一下,如果这个数据文件包含更多相关数据,例如作者的地址和电话号码、书籍的出版日期和 ISBN,或者地址、电话号码,也许还有出版商的年收入。对于每个根数据项(例如作者、书籍或出版商),该数据都会重复。

可以通过这种方式创建数据,但会非常笨重。考虑一下保持此数据文件最新的问题。如果什么斯蒂芬·金想改名?您必须更新包含他的名字的多条记录,并确保没有拼写错误。

比数据重复更糟糕的是向数据添加其他关系的复杂性。如果您决定添加作者的电话号码,而他们有家庭、工作、手机等的电话号码,该怎么办?您想要为任何根项目添加的每个新关系都会将记录数乘以该新关系中的项目数。

这个问题是数据库系统中存在关系的原因之一。数据库工程中的一个重要主题是数据库规范化,或分解数据以减少冗余并提高完整性的过程。当使用新类型的数据扩展数据库结构时,预先对其进行规范化可以最大限度地减少对现有结构的更改。

SQLite 数据库可在 Python 中使用,并且根据SQLite 主页,它的使用量超过了所有其他数据库系统的总和。它提供了一个功能齐全的关系数据库管理系统(RDBMS)它使用单个文件来维护所有数据库功能。

它还具有不需要单独的数据库服务器即可运行的优点。数据库文件格式是跨平台的,任何支持 SQLite 的编程语言都可以访问。

所有这些都是有趣的信息,但它与使用平面文件进行数据存储有何关系?下面你就知道了!

创建数据库结构

暴力方法来获得author_book_publisher.csv将数据导入 SQLite 数据库将创建一个与 CSV 文件结构匹配的单个表。这样做会忽略 SQLite 的大部分功能。

关系数据库提供一种在表中存储结构化数据并在这些表之间建立关系的方法。他们通常使用结构化查询语言 (SQL)作为与数据交互的主要方式。这是对 RDBMS 提供的功能的过度简化,但对于本教程的目的来说已经足够了。

SQLite 数据库提供使用 SQL 与数据表交互的支持。 SQLite 数据库文件不仅包含数据,而且还具有与数据交互的标准化方式。此支持嵌入在文件中,这意味着任何可以使用 SQLite 文件的编程语言也可以使用 SQL 来处理它。

使用 SQL 与数据库交互

SQL 是一个陈述性语言用于创建、管理和查询数据库中包含的数据。声明性语言描述什么是要完成而不是how它应该完成。稍后当您开始创建数据库表时,您将看到 SQL 语句的示例。

使用 SQL 构建数据库

要利用 SQL 的强大功能,您需要对以下数据中的数据应用一些数据库规范化:author_book_publisher.csv文件。为此,您需要将作者、书籍和出版商分成单独的数据库表。

从概念上讲,数据以二维表结构存储在数据库中。每个表由行组成记录,每条记录由列组成,或者领域,包含数据。

字段中包含的数据是预定义的类型,包括文本、整数、浮点数, 和更多。 CSV 文件有所不同,因为所有字段都是文本,并且必须由程序解析才能为其分配数据类型。

表中的每条记录都有一个首要的关键定义为给记录一个唯一的标识符。主键类似于Python字典。数据库引擎本身通常会为插入数据库表中的每条记录生成主键作为递增整数值。

尽管主键通常是由数据库引擎自动生成的,但事实并非如此。如果某个字段中存储的数据在该字段的表中的所有其他数据中是唯一的,则它可以是主键。例如,包含书籍数据的表可以使用书籍的 ISBN 作为主键。

使用 SQL 创建表

以下是如何使用 SQL 语句在 CSV 文件中创建代表作者、书籍和出版商的三个表:

CREATE TABLE author (
    author_id INTEGER NOT NULL PRIMARY KEY,
    first_name VARCHAR,
    last_name VARCHAR
);

CREATE TABLE book (
    book_id INTEGER NOT NULL PRIMARY KEY,
    author_id INTEGER REFERENCES author,
    title VARCHAR
);

CREATE TABLE publisher (
    publisher_id INTEGER NOT NULL PRIMARY KEY,
    name VARCHAR
);

请注意,没有文件操作,没有创建变量,也没有保存它们的结构。这些语句仅描述所需的结果:创建具有特定属性的表。数据库引擎决定如何执行此操作。

创建并使用来自以下网站的作者数据填充此表后author_book_publisher.csv文件,您可以使用 SQL 语句访问它。以下语句(也称为询问) 使用通配符 (*) 获取所有数据author表并输出:

SELECT * FROM author;

您可以使用sqlite3与交互的命令行工具author_book_publisher.db数据库文件在project/data目录:

$ sqlite3 author_book_publisher.db

一旦 SQLite 命令行工具在数据库打开的情况下运行,您就可以输入 SQL 命令。这是上面的 SQL 命令及其输出,后面是.q退出程序的命令:

sqlite> SELECT * FROM author;
1|Isaac|Asimov
2|Pearl|Buck
3|Tom|Clancy
4|Stephen|King
5|John|Le Carre
6|Alex|Michaelides
7|Carol|Shaben

sqlite> .q

请注意,每个作者在表中仅存在一次。与 CSV 文件不同,CSV 文件具有某些作者的多个条目,在这里,每个作者只需要一个唯一的记录。

使用 SQL 维护数据库

SQL 提供了通过插入新数据以及更新或删除现有数据来处理现有数据库和表的方法。下面是一个示例 SQL 语句,用于将新作者插入到author桌子:

INSERT INTO author
    (first_name, last_name)
VALUES ('Paul', 'Mendez');

此 SQL 语句插入值‘Paul' 和 'Mendez' 进入相应的列first_namelast_nameauthor桌子。

请注意,author_id未指定列。由于该列是主键,因此数据库引擎会生成该值并将其作为语句执行的一部分插入。

更新数据库表中的记录是一个并不复杂的过程。例如,假设斯蒂芬·金想以他的笔名理查德·巴赫曼为人所知。这是更新数据库记录的 SQL 语句:

UPDATE author
SET first_name = 'Richard', last_name = 'Bachman'
WHERE first_name = 'Stephen' AND last_name = 'King';

SQL 语句定位单个记录'Stephen King'使用条件语句WHERE first_name = 'Stephen' AND last_name = 'King'然后更新first_namelast_name具有新值的字段。 SQL 使用等号 (=) 作为比较运算符和赋值运算符.

您还可以从数据库中删除记录。下面是一个删除记录的 SQL 语句示例author桌子:

DELETE FROM author
WHERE first_name = 'Paul'
AND last_name = 'Mendez';

此 SQL 语句从author表,其中first_name等于'Paul'last_name等于'Mendez'.

删除记录时要小心!您设置的条件必须尽可能具体。条件过于宽泛可能会导致删除的记录数量超出您的预期。例如,如果条件仅基于行first_name = 'Paul',那么所有名字为 Paul 的作者都将从数据库中删除。

笔记:为了避免意外删除记录,许多应用程序根本不允许删除。相反,该记录还有另一列来指示它是否正在使用。该列可能被命名为active并包含一个计算结果为 True 或 False 的值,指示在查询数据库时是否应包含该记录。

例如,下面的 SQL 查询将获取所有活动记录的所有列some_table:

SELECT
  *
FROM some_table
WHERE active = 1;

SQLite 没有布尔数据类型, 所以active列由值为 的整数表示0或者1来指示记录的状态。其他数据库系统可能有也可能没有本机布尔数据类型。

完全可以在代码中直接使用 SQL 语句在 Python 中构建数据库应用程序。这样做会将数据作为列表返回到应用程序列表或列表字典.

使用原始 SQL 是一种完全可以接受的处理数据库查询返回的数据的方法。然而,您将直接使用 SQLAlchemy 来处理数据库,而不是这样做。

建立关系

您可能会发现比数据持久性和检索更强大、更有用的数据库系统的另一个功能是关系。支持关系的数据库允许您将数据分解为多个表并在它们之间建立连接。

中的数据author_book_publisher.csv文件通过复制数据来表示数据和关系。数据库通过将数据分成三个表来处理这个问题:author, book, 和publisher——并在他们之间建立关系。

将所需的所有数据放入 CSV 文件中的一处后,为什么要将其分解为多个表?重新创建和重新组合起来不是需要更多工作吗?在某种程度上确实如此,但是使用 SQL 分解数据并将其重新组合在一起的优势可能会赢得您的青睐!

一对多关系

A 一对多关系就像客户在线订购商品的关系。一个客户可以有多个订单,但每个订单属于一个客户。这author_book_publisher.db数据库以作者和书籍的形式存在一对多的关系。每个作者可以写很多本书,但每一本书都是由一个作者写的。

正如您在上面的表创建中看到的,这些单独实体的实现是将每个实体放入一个数据库表中,一个用于作者,一个用于书籍。但这两个表之间的一对多关系是如何实现的呢?

请记住,数据库中的每个表都有一个指定为该表的主键的字段。上面的每个表都有一个使用以下模式命名的主键字段:<table name>_id.

book上面显示的表包含一个字段,author_id,它引用了author桌子。这author_id字段在作者和书籍之间建立了一对多的关系,如下所示:

ERD diagram for the author_book relationship produced with JetBrains DataGrip application

上图是一个简单的图实体关系图(ERD)创建与JetBrains DataGrip显示表格的应用程序authorbook作为具有各自主键和数据字段的框。两个图形项添加有关关系的信息:

  1. 黄色和蓝色的小钥匙图标分别表示表的主键和外键。

  2. 箭头连接bookauthor表示表之间的关系基于author_id中的外键book桌子。

当您将一本新书添加到book表,数据包括author_id对于现有作者的价值author桌子。这样,某个作者写的所有书籍都具有与该唯一作者的查找关系。

现在您已经有了针对作者和书籍的单独表,那么如何使用它们之间的关系呢? SQL 支持所谓的加入操作,您可以使用它告诉数据库如何连接两个或多个表。

下面的 SQL 查询连接authorbook使用 SQLite 命令行应用程序将表放在一起:

sqlite> SELECT
   ...> a.first_name || ' ' || a.last_name AS author_name,
   ...> b.title AS book_title
   ...> FROM author a
   ...> JOIN book b ON b.author_id = a.author_id
   ...> ORDER BY a.last_name ASC;
Isaac Asimov|Foundation
Pearl Buck|The Good Earth
Tom Clancy|The Hunt For Red October
Tom Clancy|Patriot Games
Stephen King|It
Stephen King|Dead Zone
Stephen King|The Shining
John Le Carre|Tinker, Tailor, Soldier, Spy: A George Smiley Novel
Alex Michaelides|The Silent Patient
Carol Shaben|Into The Abyss

上面的 SQL 查询通过使用作者和书籍表之间建立的关系连接这两个表来收集信息。 SQL字符串连接将作者的全名分配给别名author_name。查询收集的数据按升序排序last_name场地。

SQL语句中有几点需要注意。首先,作者的全名显示在一列中,并按姓氏排序。此外,由于一对多关系,作者会多次出现在输出中。数据库中每本书的作者姓名都会重复。

通过为作者和书籍创建单独的表并建立它们之间的关系,您可以减少数据的冗余。现在,您只需在一处编辑作者的数据,并且该更改会出现在访问该数据的任何 SQL 查询中。

多对多关系

多对多关系存在于author_book_publisher.db作者和出版商之间以及书籍和出版商之间的数据库。一位作者可以与许多出版商合作,一位出版商可以与许多作者合作。同样,一本书可以由多家出版商出版,一个出版商可以出版多本书。

在数据库中处理这种情况比一对多关系更复杂,因为这种关系是双向的。多对多关系是由关联表充当两个相关表之间的桥梁。

关联表至少包含两个外键字段,它们是两个关联表各自的主键。此 SQL 语句创建关联表authorpublisher表:

CREATE TABLE author_publisher (
    author_id INTEGER REFERENCES author,
    publisher_id INTEGER REFERENCES publisher
);

SQL 语句创建一个新的author_publisher引用现有主键的表authorpublisher表。这author_publisher表是建立作者和出版商之间关系的关联表。

由于关系是两个主键之间的关系,因此不需要为关联表本身创建主键。两个相关键的组合为一行数据创建唯一标识符。

和以前一样,您使用JOIN关键字将两个表连接在一起。连接author表到publisher表是一个两步过程:

  1. JOINauthor表与author_publisher桌子。
  2. JOINauthor_publisher表与publisher桌子。

author_publisher关联表提供了桥梁JOIN连接两个表。下面是一个返回作者列表和出版书籍的出版商列表的 SQL 查询示例:

 1sqlite> SELECT
 2   ...> a.first_name || ' ' || a.last_name AS author_name,
 3   ...> p.name AS publisher_name
 4   ...> FROM author a
 5   ...> JOIN author_publisher ap ON ap.author_id = a.author_id
 6   ...> JOIN publisher p ON p.publisher_id = ap.publisher_id
 7   ...> ORDER BY a.last_name ASC;
 8Isaac Asimov|Random House
 9Pearl Buck|Random House
10Pearl Buck|Simon & Schuster
11Tom Clancy|Berkley
12Tom Clancy|Simon & Schuster
13Stephen King|Random House
14Stephen King|Penguin Random House
15John Le Carre|Berkley
16Alex Michaelides|Simon & Schuster
17Carol Shaben|Simon & Schuster

上述语句执行以下操作:

  • 1号线开始一个SELECT语句从数据库获取数据。

  • 2号线从中选择名字和姓氏author表使用a的别名author表并用空格字符将它们连接在一起。

  • 3号线选择别名为的发布者名称publisher_name.

  • 4号线使用author表作为从中检索数据的第一个源并将其分配给别名a.

  • 5号线是上述连接过程的第一步author表到publisher桌子。它使用别名ap为了author_publisher关联表并执行JOIN操作来连接ap.author_id外键引用a.author_id主键在author桌子。

  • 6号线是上述两步过程中的第二步。它使用别名p为了publisher表并执行JOIN操作来关联ap.publisher_id外键引用p.publisher_id主键在publisher桌子。

  • 7号线按作者姓氏字母升序对数据进行排序并结束 SQL 查询。

  • 8 至 17 号线是 SQL 查询的输出。

注意源中的数据authorpublisher表已标准化,没有数据重复。然而,返回的结果在回答 SQL 查询所需的地方有重复的数据。

上面的 SQL 查询演示了如何使用 SQL 来利用关系JOIN关键字,但结果数据是部分重新创建的author_book_publisher.csvCSV 数据。完成创建数据库来分离数据的工作有什么好处?

这是另一个 SQL 查询,展示了 SQL 和数据库引擎的一些强大功能:

 1sqlite> SELECT
 2   ...> a.first_name || ' ' || a.last_name AS author_name,
 3   ...> COUNT(b.title) AS total_books
 4   ...> FROM author a
 5   ...> JOIN book b ON b.author_id = a.author_id
 6   ...> GROUP BY author_name
 7   ...> ORDER BY total_books DESC, a.last_name ASC;
 8Stephen King|3
 9Tom Clancy|2
10Isaac Asimov|1
11Pearl Buck|1
12John Le Carre|1
13Alex Michaelides|1
14Carol Shaben|1

上面的 SQL 查询返回作者列表以及他们所写的书籍数量。该列表首先按书籍数量降序排列,然后按作者姓名按字母顺序排列:

  • 1号线SQL 查询开始于SELECT关键词。

  • 2号线选择作者的名字和姓氏,以空格字符分隔,并创建别名author_name.

  • 3号线统计每个作者写的书的数量,稍后将被使用ORDER BY子句对列表进行排序。

  • 4号线选择author表从中获取数据并创建a别名。

  • 5号线连接到相关的book表通过一个JOINauthor_id并创建了b的别名book桌子。

  • 6号线使用以下方法生成汇总的作者和图书总数数据GROUP BY关键词。GROUP BY每个组是什么author_name并控制哪些书籍被统计COUNT()对于那个作者。

  • 7号线首先按图书数量降序对输出进行排序,然后按作者姓氏字母升序排序。

  • 第 8 至 14 行是 SQL 查询的输出。

在上面的示例中,您利用 SQL 执行聚合计算并将结果排序为有用的顺序。让数据库基于其内置的数据组织能力执行计算通常比在 Python 中对原始数据集执行同类计算更快。 SQL 提供了使用的优点集合论嵌入 RDBMS 数据库中。

实体关系图

An 实体关系图(ERD)是数据库或数据库的一部分的实体关系模型的直观描述。这author_book_publisher.dbSQLite 数据库足够小,整个数据库可以通过下图可视化:

ERD diagram for the author_book_publisher Sqlite database produced with JetBrains DataGrip application

该图展示了数据库中的表结构以及它们之间的关系。每个框代表一个表并包含表中定义的字段,如果存在则首先指示主键。

箭头显示表之间的关系,将一个表中的外键字段连接到另一表中的字段(通常是主键)。桌子book_publisher有两个箭头,一个将其连接到book表和另一个将其连接到publisher桌子。箭头表示多对多关系bookpublisher表。这author_publisher表提供了相同的关系authorpublisher.

使用 SQLAlchemy 和 Python 对象

SQL炼金术是一个强大的Python数据库访问工具包,具有对象关系映射器 (ORM)是其最著名的组件之一,也是这里讨论和使用的组件之一。

当你在一个面向对象像 Python 这样的语言,从对象的角度思考通常很有用。可以将 SQL 查询返回的结果映射到对象,但这样做会违背数据库的工作原理。坚持使用 SQL 提供的标量结果与 Python 开发人员的工作方式背道而驰。这个问题被称为对象关系阻抗不匹配.

SQLAlchemy 提供的 ORM 位于 SQLite 数据库和 Python 程序之间,并转换数据库引擎和 Python 对象之间的数据流。 SQLAlchemy 允许您从对象的角度进行思考,同时仍然保留数据库引擎的强大功能。

该模型

将 SQLAlchemy 连接到数据库的基本元素之一是创建一个模型。该模型是一个Python类定义作为数据库查询结果返回的 Python 对象与底层数据库表之间的数据映射。

前面显示的实体关系图显示了用箭头连接的框。这些框是使用 SQL 命令构建的表,也是 Python 类将建模的内容。箭头表示表之间的关系。

这些模型是继承自 SQLAlchemy 的 Python 类Base班级。这Base类提供模型实例和数据库表之间的接口操作。

下面是models.py创建模型来表示的文件author_book_publisher.db数据库:

 1from sqlalchemy import Column, Integer, String, ForeignKey, Table
 2from sqlalchemy.orm import relationship, backref
 3from sqlalchemy.ext.declarative import declarative_base
 4
 5Base = declarative_base()
 6
 7author_publisher = Table(
 8    "author_publisher",
 9    Base.metadata,
10    Column("author_id", Integer, ForeignKey("author.author_id")),
11    Column("publisher_id", Integer, ForeignKey("publisher.publisher_id")),
12)
13
14book_publisher = Table(
15    "book_publisher",
16    Base.metadata,
17    Column("book_id", Integer, ForeignKey("book.book_id")),
18    Column("publisher_id", Integer, ForeignKey("publisher.publisher_id")),
19)
20
21class Author(Base):
22    __tablename__ = "author"
23    author_id = Column(Integer, primary_key=True)
24    first_name = Column(String)
25    last_name = Column(String)
26    books = relationship("Book", backref=backref("author"))
27    publishers = relationship(
28        "Publisher", secondary=author_publisher, back_populates="authors"
29    )
30
31class Book(Base):
32    __tablename__ = "book"
33    book_id = Column(Integer, primary_key=True)
34    author_id = Column(Integer, ForeignKey("author.author_id"))
35    title = Column(String)
36    publishers = relationship(
37        "Publisher", secondary=book_publisher, back_populates="books"
38    )
39
40class Publisher(Base):
41    __tablename__ = "publisher"
42    publisher_id = Column(Integer, primary_key=True)
43    name = Column(String)
44    authors = relationship(
45        "Author", secondary=author_publisher, back_populates="publishers"
46    )
47    books = relationship(
48        "Book", secondary=book_publisher, back_populates="publishers"
49    )

以下是该模块中发生的事情:

  • 1号线进口Column, Integer, String, ForeignKey, 和TableSQLAlchemy 中的类,用于帮助定义模型属性。

  • 2号线进口relationship()backref对象,用于创建对象之间的关系。

  • 3号线进口declarative_base对象,它将数据库引擎连接到模型的 SQLAlchemy 功能。

  • 5号线创造了Base类,这是所有模型继承的类,也是它们获取 SQLAlchemy ORM 功能的方式。

  • 第 7 至 12 行创建author_publisher关联表模型。

  • 第 14 至 19 行创建book_publisher关联表模型。

  • 第 21 至 29 行定义Author类模型为author数据库表。

  • 第 31 至 38 行定义Book类模型为book数据库表。

  • 第 40 至 49 行定义Publisher类模型为publisher数据库表。

上面的描述展示了五个表的映射author_book_publisher.db数据库。但它掩盖了一些 SQLAlchemy ORM 功能,包括Table, ForeignKey, relationship(), 和backref。现在让我们开始讨论这些。

Table创建协会

author_publisherbook_publisher都是以下实例Table创建在之间使用的多对多关联表的类authorpublisher表和bookpublisher表,分别。

SQL炼金术Table类在数据库中创建 ORM 映射表的唯一实例。第一个参数是数据库中定义的表名,第二个是Base.metadata,它提供 SQLAlchemy 功能和数据库引擎之间的连接。

其余参数是Column类按名称、类型定义表字段,在上面的示例中,是 a 的实例ForeignKey.

ForeignKey创建连接

SQL炼金术ForeignKey类定义了两个之间的依赖关系Column不同表中的字段。 AForeignKey是让 SQLAlchemy 了解表之间关系的方式。例如,这一行来自author_publisher实例创建建立外键关系:

Column("author_id", Integer, ForeignKey("author.author_id"))

上面的语句告诉 SQLAlchemy 中有一个列author_publisher表名为author_id。该列的类型是Integer, 和author_id是与主键相关的外键author桌子。

两者兼具author_idpublisher_id定义在author_publisher Table实例从以下位置创建连接author表到publisher表,反之亦然,建立多对多关系。

relationship()建立收藏

有一个ForeignKey定义了表之间关系的存在,但不定义作者可以拥有的书籍集合。看看下面的这一行Author类定义:

books = relationship("Book", backref=backref("author"))

上面的代码定义了一个父子集合。这books属性是复数(这不是要求,只是约定)表明它是一个集合。

第一个参数为relationship(), 类名Book(这是not表名book),是类books属性相关。这relationship通知 SQLAlchemy 之间存在关系AuthorBook类。 SQLAlchemy 会在以下位置找到关系Book类定义:

author_id = Column(Integer, ForeignKey("author.author_id"))

SQLAlchemy 认识到这是ForeignKey两个类之间的连接点。您将到达backref参数输入relationship()一会儿。

另一种关系是Author是到Publisher班级。这是通过以下语句创建的Author类定义:

publishers = relationship(
    "Publisher", secondary=author_publisher, back_populates="authors"
)

喜欢books,属性publishers表示与作者关联的出版商的集合。第一个参数,"Publisher",告知 SQLAlchemy 相关类是什么。第二个和第三个参数是secondary=author_publisherback_populates="authors":

  • secondary告诉 SQLAlchemy 与Publisher类是通过辅助表来实现的,该表是author_publisher之前创建的关联表models.py。这secondary参数使 SQLAlchemy 找到publisher_id ForeignKey定义在author_publisher关联表。

  • back_populates是一个方便的配置,告诉 SQLAlchemy 中有一个补充集合Publisher类称为authors.

backref镜子属性

backref的参数books收藏relationship()创建一个author每个属性Book实例。该属性引用父级Author认为Book实例相关。

例如,如果您执行以下 Python 代码,则Book实例将从 SQLAlchemy 查询返回。这Book实例具有可用于打印有关书籍的信息的属性:

book = session.query(Book).filter_by(Book.title == "The Stand").one_or_none()
print(f"Authors name: {book.author.first_name} {book.author.last_name}")

的存在author属性中的Book上面是因为backref定义。 Abackref当您需要引用父实例而您所拥有的只是一个子实例时,使用它会非常方便。

查询 回答问题

您可以进行基本查询,例如SELECT * FROM author;在 SQLAlchemy 中是这样的:

results = session.query(Author).all()

session是一个 SQLAlchemy 对象,用于在 Python 示例程序中与 SQLite 进行通信。在这里,您告诉会话您要针对Author模型并返回所有记录。

此时,使用 SQLAlchemy 代替普通 SQL 的优势可能并不明显,特别是考虑到创建表示数据库的模型所需的设置。这results查询返回的结果就是奇迹发生的地方。您将得到的不是标量数据列表的列表,而是实例的列表Author属性与您定义的列名称匹配的对象。

bookspublishers由 SQLAlchemy 维护的集合创建了一个作者和他们所写的书籍以及出版这些书籍的出版商的分层列表。

在幕后,SQLAlchemy 将对象和方法调用转换为 SQL 语句以针对 SQLite 数据库引擎执行。 SQLAlchemy 将 SQL 查询返回的数据转换为 Python 对象。

使用 SQLAlchemy,您可以执行前面显示的更复杂的聚合查询,以获取作者列表和他们撰写的书籍数量,如下所示:

author_book_totals = (
    session.query(
        Author.first_name,
        Author.last_name,
        func.count(Book.title).label("book_total")
    )
    .join(Book)
    .group_by(Author.last_name)
    .order_by(desc("book_total"))
    .all()
)

上面的查询获取作者的名字和姓氏,以及作者所写书籍的数量。聚合count所使用的group_by条款基于作者的姓氏。最后,根据聚合和别名对结果进行降序排序book_total.

示例程序

示例程序examples/example_2/main.py具有相同的功能examples/example_1/main.py但专门使用 SQLAlchemy 来与author_book_publisher.dbSQLite 数据库。该程序分为main()函数及其调用的函数:

 1def main():
 2    """Main entry point of program"""
 3    # Connect to the database using SQLAlchemy
 4    with resources.path(
 5        "project.data", "author_book_publisher.db"
 6    ) as sqlite_filepath:
 7        engine = create_engine(f"sqlite:///{sqlite_filepath}")
 8    Session = sessionmaker()
 9    Session.configure(bind=engine)
10    session = Session()
11
12    # Get the number of books printed by each publisher
13    books_by_publisher = get_books_by_publishers(session, ascending=False)
14    for row in books_by_publisher:
15        print(f"Publisher: {row.name}, total books: {row.total_books}")
16    print()
17
18    # Get the number of authors each publisher publishes
19    authors_by_publisher = get_authors_by_publishers(session)
20    for row in authors_by_publisher:
21        print(f"Publisher: {row.name}, total authors: {row.total_authors}")
22    print()
23
24    # Output hierarchical author data
25    authors = get_authors(session)
26    output_author_hierarchy(authors)
27
28    # Add a new book
29    add_new_book(
30        session,
31        author_name="Stephen King",
32        book_title="The Stand",
33        publisher_name="Random House",
34    )
35    # Output the updated hierarchical author data
36    authors = get_authors(session)
37    output_author_hierarchy(authors)

这个程序是一个修改版本examples/example_1/main.py。让我们回顾一下差异:

  • 4 至 7 号线首先初始化sqlite_filepath变量到数据库文件路径。然后他们创建engine与 SQLite 进行通信的变量author_book_publisher.db数据库文件,这是 SQLAlchemy 对数据库的访问点。

  • 8号线创造了Session来自 SQLAlchemy 的类sessionmaker().

  • 9号线绑定Session到第 8 行创建的引擎。

  • 10号线创造了session实例,程序使用它与 SQLAlchemy 进行通信。

其余功能类似,除了替换datasession作为调用的所有函数的第一个参数main().

get_books_by_publisher()已被重构为使用 SQLAlchemy 和您之前定义的模型来获取请求的数据:

 1def get_books_by_publishers(session, ascending=True):
 2    """Get a list of publishers and the number of books they've published"""
 3    if not isinstance(ascending, bool):
 4        raise ValueError(f"Sorting value invalid: {ascending}")
 5
 6    direction = asc if ascending else desc
 7
 8    return (
 9        session.query(
10            Publisher.name, func.count(Book.title).label("total_books")
11        )
12        .join(Publisher.books)
13        .group_by(Publisher.name)
14        .order_by(direction("total_books"))
15    )

这是新功能,get_books_by_publishers(), 是在做:

  • 6号线创造了direction变量并将其设置为等于 SQLAlchemydesc或者asc函数取决于的值ascending范围。

  • 第 9 至 11 行查询Publisher要返回的数据的表,在本例中是Publisher.name和总计Book与作者关联的对象,别名为total_books.

  • 12号线加入到Publisher.books收藏。

  • 13号线汇总图书数量Publisher.name属性。

  • 14号线根据定义的运算符按图书计数对输出进行排序direction.

  • 15号线关闭对象,执行查询,并将结果返回给调用者。

上面的所有代码都表达了想要什么,而不是如何检索它。现在,您不再使用 SQL 来描述所需内容,而是使用 Python 对象和方法。返回的是 Python 对象列表,而不是数据元组列表。

get_authors_by_publisher()还经过修改以专门与 SQLAlchemy 一起使用。它的功能与前面的函数非常相似,因此省略了函数说明:

def get_authors_by_publishers(session, ascending=True):
    """Get a list of publishers and the number of authors they've published"""
    if not isinstance(ascending, bool):
        raise ValueError(f"Sorting value invalid: {ascending}")

    direction = asc if ascending else desc

    return (
        session.query(
            Publisher.name,
            func.count(Author.first_name).label("total_authors"),
        )
        .join(Publisher.authors)
        .group_by(Publisher.name)
        .order_by(direction("total_authors"))
    )

get_authors()已添加以获得按姓氏排序的作者列表。该查询的结果是一个列表Author包含书籍集合的对象。这Author对象已经包含分层数据,因此结果不必重新格式化:

def get_authors(session):
    """Get a list of author objects sorted by last name"""
    return session.query(Author).order_by(Author.last_name).all()

和之前的版本一样,add_new_book()相对复杂但很容易理解。它确定数据库中是否已存在具有相同标题、作者和出版商的书籍。

如果搜索查询找到完全匹配,则该函数返回。如果没有书符合确切的搜索条件,则会搜索作者是否使用传入的标题写了一本书。此代码的存在是为了防止在数据库中创建重复的书籍。

如果不存在匹配的书,并且作者没有写过同名的书,则会创建一本新书。然后该函数检索或创建作者和出版商。一旦实例Book, AuthorPublisher存在,创建它们之间的关系,并将结果信息保存到数据库中:

 1def add_new_book(session, author_name, book_title, publisher_name):
 2    """Adds a new book to the system"""
 3    # Get the author's first and last names
 4    first_name, _, last_name = author_name.partition(" ")
 5
 6    # Check if book exists
 7    book = (
 8        session.query(Book)
 9        .join(Author)
10        .filter(Book.title == book_title)
11        .filter(
12            and_(
13                Author.first_name == first_name, Author.last_name == last_name
14            )
15        )
16        .filter(Book.publishers.any(Publisher.name == publisher_name))
17        .one_or_none()
18    )
19    # Does the book by the author and publisher already exist?
20    if book is not None:
21        return
22
23    # Get the book by the author
24    book = (
25        session.query(Book)
26        .join(Author)
27        .filter(Book.title == book_title)
28        .filter(
29            and_(
30                Author.first_name == first_name, Author.last_name == last_name
31            )
32        )
33        .one_or_none()
34    )
35    # Create the new book if needed
36    if book is None:
37        book = Book(title=book_title)
38
39    # Get the author
40    author = (
41        session.query(Author)
42        .filter(
43            and_(
44                Author.first_name == first_name, Author.last_name == last_name
45            )
46        )
47        .one_or_none()
48    )
49    # Do we need to create the author?
50    if author is None:
51        author = Author(first_name=first_name, last_name=last_name)
52        session.add(author)
53
54    # Get the publisher
55    publisher = (
56        session.query(Publisher)
57        .filter(Publisher.name == publisher_name)
58        .one_or_none()
59    )
60    # Do we need to create the publisher?
61    if publisher is None:
62        publisher = Publisher(name=publisher_name)
63        session.add(publisher)
64
65    # Initialize the book relationships
66    book.author = author
67    book.publishers.append(publisher)
68    session.add(book)
69
70    # Commit to the database
71    session.commit()

上面的代码比较长。让我们将功能分解为可管理的部分:

  • 7 至 18 号线设置book变量到 a 的实例Book如果找到具有相同标题、作者和出版商的书籍。否则,他们设置bookNone.

  • 20、21号线确定该书是否已存在,如果存在则返回。

  • 第 24 至 37 行设置book变量到 a 的实例Book如果找到具有相同标题和作者的书。否则,他们会创建一个新的Book实例。

  • 第 40 至 52 行设置author变量到现有作者(如果找到),或创建一个新的Author基于传入的作者姓名的实例。

  • 第 55 至 63 行设置publisher变量到现有的发布者(如果找到),或者创建一个新的Publisher基于传入的发布者名称的实例。

  • 66号线设置book.author实例到author实例。这将创建作者和书籍之间的关系,SQLAlchemy 将在提交会话时在数据库中创建该关系。

  • 67号线添加了publisher实例到book.publishers收藏。这在之间创建了多对多关系bookpublisher表。 SQLAlchemy 将在表和book_publisher连接两者的关联表。

  • 68号线添加了Book会话的实例,使其成为会话工作单元的一部分。

  • 71号线将所有创建和更新提交到数据库。

这里有几点需要注意。首先,文中没有提及author_publisher或者book_publisher查询或创建和更新中的关联表。因为你所做的工作models.py设置关系后,SQLAlchemy 可以处理将对象连接在一起并在创建和更新期间保持这些表同步。

其次,所有的创作和更新都发生在session目的。这些活动都没有触及数据库。仅当session.commit()语句执行后会话是否会遍历其工作单元并将该工作提交到数据库。

例如,如果一个新的Book创建实例(如上面第 37 行所示),然后书的属性被初始化,除了book_id主键和author_id外键。因为还没有发生任何数据库活动,book_id未知,并且在实例化时没有执行任何操作book给它一个author_id.

什么时候session.commit()被执行后,它要做的事情之一就是插入book到数据库中,此时数据库将创建book_id首要的关键。然后会话将初始化book.book_idvalue 与数据库引擎创建的主键值。

session.commit()也意识到插入Book实例中的author.books收藏。这author对象的author_id主键将被添加到Book实例附加到author.books集合作为author_id外键。

为多个用户提供访问权限

到目前为止,您已经了解了如何使用 pandas、SQLite 和 SQLAlchemy 以不同的方式访问相同的数据。对于作者、书籍和出版商数据的相对简单的用例,是否应该使用数据库仍然是一个犹豫不决的问题。

在使用平面文件还是数据库之间进行选择时的决定性因素之一是数据和关系的复杂性。如果每个实体的数据都很复杂并且包含实体之间的许多关系,那么在平面文件中创建和维护它可能会变得更加困难。

另一个需要考虑的因素是您是否要在多个用户之间共享数据。这个问题的解决方案可能很简单,只需使用运动鞋网在用户之间物理移动数据。以这种方式移动数据文件具有易于使用的优点,但在进行更改时数据可能很快就会失去同步。

如果用户位于远程并且希望通过网络访问数据,那么保持所有用户的数据一致的问题就变得更加困难。即使您仅限于 Python 等单一语言并使用 pandas 访问数据,网络文件锁定也不足以确保数据不会损坏。

通过服务器应用程序和用户界面提供数据可以缓解这个问题。服务器是唯一需要对数据库进行文件级访问的应用程序。通过使用数据库,无论服务器使用哪种编程语言,服务器都可以利用 SQL 通过一致的接口访问数据。

最后一个示例程序通过提供一个 Web 应用程序和用户界面来演示这一点奇努克示例 SQLite 数据库。彼得·斯塔克慷慨地维护奇努克数据库,作为其一部分SQLite 教程地点。如果您想了解有关 SQLite 和 SQL 的更多信息,那么该网站是一个很好的资源。

Chinook 数据库按照简化的方式提供艺术家、音乐和播放列表信息。Spotify。该数据库是示例代码项目的一部分project/data文件夹。

将 Flask 与 Python、SQLite 和 SQLAlchemy 结合使用

examples/example_3/chinook_server.py程序创建一个烧瓶您可以使用浏览器与之交互的应用程序。该应用程序利用以下技术:

  • 烧瓶蓝图是 Flask 的一部分,提供了一个很好的方法来遵循关注点分离设计原则并创建不同的模块来包含功能。

  • Flask SQLAlchemy是 Flask 的扩展,它在 Web 应用程序中添加了对 SQLAlchemy 的支持。

  • Flask_Bootstrap4封装了引导程序前端工具包,将其与 Flask Web 应用程序集成。

  • Flask_WTF扩展 Flask 为WT表格,为您的 Web 应用程序提供一种生成和验证 Web 表单的有用方法。

  • 蟒蛇 dotenv是一个 Python 模块,应用程序使用它从文件中读取环境变量并将敏感信息排除在程序代码之外。

尽管对于本示例来说不是必需的,但.env文件保存应用程序的环境变量。这.env文件的存在包含密码等敏感信息,您应该将其保留在代码文件之外。但该项目的内容.env文件如下所示,因为它不包含任何敏感数据:

SECRET_KEY = "you-will-never-guess"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLAlCHEMY_ECHO = False
DEBUG = True

该示例应用程序相当大,只有其中一部分与本教程相关。因此,检查和学习代码就留给读者作为练习。也就是说,您可以查看下面应用程序的动画屏幕截图,然后是呈现主页的 HTML 和提供动态数据的 Python Flask 路由。

这是正在运行的应用程序,浏览各种菜单和功能:

The chinook database web application in action as an animated GIF

动画屏幕截图从应用程序主页开始,样式使用引导程序4。该页面显示数据库中的艺术家,按升序排序。屏幕截图的其余部分显示了单击显示的链接或从顶级菜单浏览应用程序的结果。

这是金贾2生成应用程序主页的 HTML 模板:

 1{% extends "base.html" %}
 2
 3{% block content %}
 4<div class="container-fluid">
 5  <div class="m-4">
 6    <div class="card" style="width: 18rem;">
 7      <div class="card-header">Create New Artist</div>
 8      <div class="card-body">
 9        <form method="POST" action="{{url_for('artists_bp.artists')}}">
10          {{ form.csrf_token }}
11          {{ render_field(form.name, placeholder=form.name.label.text) }}
12          <button type="submit" class="btn btn-primary">Create</button>
13        </form>
14      </div>
15    </div>
16    <table class="table table-striped table-bordered table-hover table-sm">
17      <caption>List of Artists</caption>
18      <thead>
19        <tr>
20          <th>Artist Name</th>
21        </tr>
22      </thead>
23      <tbody>
24        {% for artist in artists %}
25        <tr>
26          <td>
27            <a href="{{url_for('albums_bp.albums', artist_id=artist.artist_id)}}">
28              {{ artist.name }}
29            </a>
30          </td>
31        </tr>
32        {% endfor %}
33      </tbody>
34    </table>
35  </div>
36</div>
37{% endblock %}

Jinja2 模板代码中发生的情况如下:

  • 1号线使用 Jinja2 模板继承来构建此模板base.html模板。这base.html模板包含所有 HTML5 样板代码以及在网站所有页面上一致的 Bootstrap 导航栏。

  • 第 3 行至第 37 行包含页面的块内容,该内容被合并到同名的 Jinja2 宏中base.html基础模板。

  • 第 9 至 13 行渲染形式以创建新的艺术家。这使用了以下功能Flask-WTF生成表格。

  • 第 24 至 32 行创建一个for渲染艺术家姓名表的循环。

  • 第 27 至 29 行将艺术家姓名呈现为艺术家专辑页面的链接,显示与特定艺术家相关的歌曲。

这是渲染页面的 Python 路由:

 1from flask import Blueprint, render_template, redirect, url_for
 2from flask_wtf import FlaskForm
 3from wtforms import StringField
 4from wtforms.validators import InputRequired, ValidationError
 5from app import db
 6from app.models import Artist
 7
 8# Set up the blueprint
 9artists_bp = Blueprint(
10    "artists_bp", __name__, template_folder="templates", static_folder="static"
11)
12
13def does_artist_exist(form, field):
14    artist = (
15        db.session.query(Artist)
16        .filter(Artist.name == field.data)
17        .one_or_none()
18    )
19    if artist is not None:
20        raise ValidationError("Artist already exists", field.data)
21
22class CreateArtistForm(FlaskForm):
23    name = StringField(
24        label="Artist's Name", validators=[InputRequired(), does_artist_exist]
25    )
26
27@artists_bp.route("/")
28@artists_bp.route("/artists", methods=["GET", "POST"])
29def artists():
30    form = CreateArtistForm()
31
32    # Is the form valid?
33    if form.validate_on_submit():
34        # Create new artist
35        artist = Artist(name=form.name.data)
36        db.session.add(artist)
37        db.session.commit()
38        return redirect(url_for("artists_bp.artists"))
39
40    artists = db.session.query(Artist).order_by(Artist.name).all()
41    return render_template("artists.html", artists=artists, form=form,)

让我们回顾一下上面的代码做了什么:

  • 第 1 至 6 行导入呈现页面所需的所有模块并使用数据库中的数据初始化表单。

  • 第 9 至 11 行创建艺术家页面的蓝图。

  • 第 13 至 20 行为 Flask-WTF 表单创建自定义验证器函数,以确保创建新艺术家的请求不会与现有艺术家发生冲突。

  • 第 22 至 25 行创建表单类来处理浏览器中呈现的艺术家表单并提供表单字段输入的验证。

  • 第 27 至 28 行将两条路线连接到artists()他们装饰的功能。

  • 30号线创建一个实例CreateArtistForm()班级。

  • 33号线确定页面是通过 HTTP 方法 GET 还是 POST(提交)请求的。如果它是 POST,那么它还会验证表单的字段并通知用户字段是否无效。

  • 第 35 至 37 行创建一个新的艺术家对象,将其添加到 SQLAlchemy 会话中,并将该艺术家对象提交到数据库中,并将其保留。

  • 38号线重定向回艺术家页面,该页面将使用新创建的艺术家重新呈现。

  • 40号线运行 SQLAlchemy 查询以获取数据库中的所有艺术家并对它们进行排序Artist.name.

  • 41号线如果 HTTP 请求方法是 GET,则呈现艺术家页面。

您可以看到,大量的功能是通过相当少量的代码创建的。

创建 REST API 服务器

您还可以创建一个 Web 服务器,提供休息API。这种服务器提供用数据响应的 URL 端点,通常在JSON格式。 JavaScript 单页 Web 应用程序可以通过 AJAX HTTP 请求来使用提供 REST API 端点的服务器。

Flask 是创建 REST 应用程序的优秀工具。有关使用 Flask、Connexion 和 SQLAlchemy 创建 REST 应用程序的多部分教程系列,请查看使用 Flask、Connexion 和 SQLAlchemy 的 Python REST API.

如果您是 Django 的粉丝并且对创建 REST API 感兴趣,那么请查看Django Rest 框架 – 简介使用 Django Tastypie 创建超级基本 REST API.

笔记:有理由询问 SQLite 是否是作为 Web 应用程序的数据库后端的正确选择。这SQLite 网站指出对于每天提供大约 100,000 个点击的网站来说,SQLite 是一个不错的选择。如果您的网站每日点击量更高,那么首先要恭喜您!

除此之外,如果您使用 SQLAlchemy 实现了您的网站,那么可以将数据从 SQLite 移动到另一个数据库,例如MySQL或 PostgreSQL。有关 SQLite、MySQL 和 PostgreSQL 的比较,这将帮助您决定哪一个最适合您的应用程序,请查看Python SQL 库简介.

无论它是什么,都值得考虑将 SQLite 用于您的 Python 应用程序。使用数据库可以为您的应用程序提供多功能性,并且可能会创造令人惊讶的机会来添加附加功能。

结论

您在本教程中已经涵盖了有关数据库、SQLite、SQL 和 SQLAlchemy 的大量内容!您已使用这些工具将平面文件中包含的数据移动到 SQLite 数据库,使用 SQL 和 SQLAlchemy 访问数据,并通过 Web 服务器提供该数据。

在本教程中,您学习了:

  • 为什么一个SQLite数据库可以成为平面文件数据存储的一个引人注目的替代方案
  • 如何标准化数据减少数据冗余并提高数据完整性
  • 如何使用SQL炼金术以面向对象的方式使用数据库
  • 如何建立一个Web应用程序为多个用户提供数据库服务

使用数据库是处理数据的强大抽象,它为您的 Python 程序添加了重要的功能,并允许您提出有关数据的有趣问题。

您可以通过以下链接获取在本教程中看到的所有代码和数据:

下载示例代码: 单击此处获取您将使用的代码在本教程中了解如何使用 SQLite 和 SQLAlchemy 进行数据管理。

进一步阅读

本教程介绍了如何使用数据库、SQL 和 SQLAlchemy,但关于这些主题还有更多内容需要了解。这些都是强大而复杂的工具,任何一个教程都无法充分涵盖。以下是一些资源,可提供额外信息以扩展您的技能:

  • 如果您的应用程序会将数据库暴露给用户,那么避免 SQL 注入攻击是一项重要技能。欲了解更多信息,请查看使用 Python 防止 SQL 注入攻击.

  • 提供对数据库的 Web 访问在基于 Web 的单页应用程序中很常见。要了解如何操作,请查看使用 Flask、Connexion 和 SQLAlchemy 的 Python REST API – 第 2 部分.

  • 准备数据工程工作面试可以帮助您在职业生涯中占据一席之地。要开始使用,请查看Python 数据工程师面试问题.

  • 使用 Flask 与 Postgres 和 SQLAlchemy 迁移数据并能够回滚是软件开发生命周期 (SDLC) 不可或缺的一部分。您可以通过查看了解更多信息Flask 示例 – 设置 Postgres、SQLAlchemy 和 Alembic.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Python、SQLite 和 SQLAlchemy 进行数据管理 的相关文章

  • ChatterBot:使用 Python 构建聊天机器人

    目录 演示 项目概况 先决条件 第 1 步 使用 Python ChatterBot 创建聊天机器人 第 2 步 开始训练您的聊天机器人 第 3 步 导出 WhatsApp 聊天记录 第 4 步 清理您的聊天导出 第 5 步 使用自定义数据
  • Django 测试(第 1 部分)——最佳实践和示例

    目录 Intro to Testing in Django 测试类型 最佳实践 结构 第三方包 Examples 设置 测试模型 测试视图 测试表格 测试 API 下次 测试至关重要 如果没有正确测试您的代码 您将永远不会知道代码现在或将来
  • 使用 Python、SQLite 和 SQLAlchemy 进行数据管理

    目录 Using Flat Files for Data Storage 平面文件的优点 平面文件的缺点 平面文件示例 Using SQLite to Persist Data 创建数据库结构 使用 SQL 与数据库交互 Structuri
  • 使用 Fabric 和 Ansible 自动化 Django 部署

    目录 设置和配置 Fabric Setup 设置 SSH 密钥 强化用户密码 安装 Ansible 依赖项 将 SELinux 设置为宽容模式 升级服务器 完整性检查 Ansible Primer 剧本 示例手册 Playbook Setu
  • NoSQL 数据库管理系统和模型的比较

    介绍 当大多数人想到数据库时 他们通常会想到传统的关系数据库模型 其中涉及由行和列组成的表 虽然关系数据库管理系统仍然处理互联网上的大部分数据 但近年来 随着开发人员寻求解决关系模型局限性的方法 替代数据模型变得更加普遍 这些非关系数据库模
  • 如何在 CentOS 7 上安装 MySQL

    介绍 MySQL是一个开源数据库管理系统 通常作为流行的一部分安装LEMP Linux Nginx MySQL MariaDB PHP Python Perl 堆栈 它使用关系数据库和 SQL 结构化查询语言 来管理其数据 CentOS 7
  • 如何在 Ubuntu 18.04 上安装 MySQL

    本教程的先前版本由以下人员编写榛子维尔多 介绍 MySQL是一个开源数据库管理系统 通常作为流行的一部分安装LAMP Linux Apache MySQL PHP Python Perl 堆栈 它使用关系数据库和 SQL 结构化查询语言 来
  • 如何在 Rocky Linux 9 上安装和使用 PostgreSQL

    介绍 关系数据库管理系统是许多网站和应用程序的关键组件 它们提供了一种结构化的方式来存储 组织和访问信息 PostgreSQL也称为 Postgres 是一个关系数据库管理系统 它提供结构化查询语言 更广为人知的名称为 SQL 的实现 许多
  • Python heapq 模块:使用堆和优先级队列

    目录 What Are Heaps 数据结构 堆和优先级队列 堆的实现 优先级队列的用途 Heaps as Lists in the Python heapq Module 基本操作 高级别操作 堆可以解决的问题 如何发现问题 Exampl
  • PyQt 布局:创建具有专业外观的 GUI 应用程序

    目录 在 GUI 上布置图形元素 储备 PyQt 布局库 Using General Purpose Layout Managers 构建水平布局 QHBoxLayout 构建垂直布局 QVBoxLayout 在网格中排列小部件 QGrid
  • Python pandas:你可能不知道的技巧和功能

    目录 1 在解释器启动时配置选项和设置 2 用pandas的测试模块制作玩具数据结构 3 利用访问器方法 4 从组件列创建 DatetimeIndex 5 使用分类数据节省时间和空间 6 通过迭代内省 Groupby 对象 7 使用此映射技
  • 使用 Python 发送电子邮件

    目录 Getting Started 选项 1 设置 Gmail 帐户进行开发 选项 2 设置本地 SMTP 服务器 Sending a Plain Text Email 启动安全 SMTP 连接 发送您的纯文本电子邮件 Sending F
  • 最好的 Python 书籍

    目录 Best Books for Learning Python Python 速成课程 Head First Python 第二版 用 Python 发明你自己的电脑游戏 第四版 思考 Python 如何像计算机科学家一样思考 第二版
  • 使用 PyQt 处理 SQL 数据库:基础知识

    目录 Connecting PyQt to an SQL Database 创建数据库连接 处理多个连接 使用不同的 SQL Divers 打开数据库连接 Running SQL Queries With PyQt 执行静态 SQL 查询
  • 在 Nitrous.IO 上使用 Django 和 Flask

    目录 入门 使用 Virtualenv 设置 Python 2 7 安装Django 安装烧瓶 这是我们的朋友 Greg McKeever 的客座帖子亚硝酸盐 Nitrous IO 是一个允许您在云中快速启动自己的开发环境的平台 以下是在上
  • Python 中的函数式编程:何时以及如何使用它

    目录 什么是函数式编程 Python 对函数式编程的支持程度如何 使用 lambda 定义匿名函数 Applying a Function to an Iterable With map 使用单个可迭代对象调用 map 使用多个可迭代对象调
  • Jupyter Notebook:简介

    目录 Getting Up and Running With Jupyter Notebook 安装 启动 Jupyter Notebook 服务器 Creating a Notebook 命名 运行细胞 菜单 启动终端和其他东西 查看正在
  • Python pickle 模块:如何在 Python 中持久化对象

    目录 Python 中的序列化 Python pickle 模块内部 Python pickle 模块的协议格式 可酸洗和不可酸洗类型 腌制对象的压缩 Python pickle 模块的安全问题 结论 作为开发人员 您有时可能需要通过网络发
  • 如何制作引人入胜的编程视频

    目录 Decide When a Programming Video Makes Sense 关注结果 绘制代码示例 做演讲者笔记 Audit Your Setup 准备你的房间 检查你的屏幕 选择录音软件 Clean Up Your Co
  • 使用 Python、PyQt 和 SQLite 构建联系簿

    目录 演示 Python 联系手册 项目概况 先决条件 Step 1 Creating the Contact Book s Skeleton App With PyQt 构建通讯录项目 创建应用程序的主窗口 编码和运行应用程序 第 2 步

随机推荐

  • 使用 Pdb 进行 Python 调试

    目录 入门 打印变量的值 打印表达式 Stepping Through Code 列出源代码 使用断点 继续执行 显示表达式 Python 来电显示 基本 pdb 命令 使用 pdb 进行 Python 调试 结论 调试应用程序有时可能是一
  • Python 社区采访迈克尔·肯尼迪

    本周 我们的 Python 社区采访对象正是迈克尔 肯尼迪的跟我聊Python名声 你可能听过他权威的声音 但你知道他的Python故事吗 请继续阅读 了解他使用 Python 的旅程 他遇到交通堵塞时的想法以及他对两个轮子的热爱 瑞奇 欢
  • 关于斯蒂芬·格鲁佩塔

    关于斯蒂芬 格鲁佩塔 推特 领英 个人网站 我当了很多年物理学家 好吧 我仍然是一名物理学家 但我不再以物理学家的身份工作 我在祖国马耳他学习物理和数学 然后在伦敦帝国学院获得光学和成像博士学位 出于必要 我在那里学习了编码 首先使用 MA
  • Django 入门:构建组合应用程序

    Django 是一个功能齐全的 Python Web 框架 可用于构建复杂的 Web 应用程序 在本课程中 您将立即投入并学习姜戈举例来说 您将按照步骤创建一个功能齐全的 Web 应用程序 并在此过程中了解该框架的一些最重要的功能以及它们如
  • 在您自己的 Python 对象中支持“with”语句

    在本课程中 您将学习如何为您自己的 Python 对象实现上下文管理器功能 在这种情况下 您将使用基于类的方法
  • 第 136 集:使用 pyproject.toml 打包 Python 代码并使用 pathlib 列出文件

    第 136 集 使用 pyproject toml 打包 Python 代码并使用 pathlib 列出文件 真正的 Python 播客 2022 年 12 月 9 日54m RSS Apple Podcasts Google Podcas
  • 了解并安装 Kivy

    要了解有关本课程中的概念的更多信息 请查看 Python 虚拟环境 入门 Kivy项目现场
  • 使用 Python、SQLite 和 SQLAlchemy 进行数据管理

    目录 Using Flat Files for Data Storage 平面文件的优点 平面文件的缺点 平面文件示例 Using SQLite to Persist Data 创建数据库结构 使用 SQL 与数据库交互 Structuri
  • NumPy arange():如何使用 np.arange()

    目录 np arange 的返回值和参数 Range Arguments of np arange 提供所有范围参数 提供两个范围参数 提供一个范围参数 提供否定论据 倒数 获取空数组 np arange 的数据类型 使用 np arang
  • 关于奥斯汀·塞帕利亚

    关于奥斯汀 塞帕利亚 推特 GitHub 领英 个人网站 大家好 我是 Austin Cepalia Real Python 的视频教程作者 我目前是罗切斯特理工学院计算机科学学位的一名学生 但当我能够按照自己的节奏完成项目并挑战我对所学内
  • 第 140 集:使用 Polars 加速数据帧

    第 140 集 使用 Polars 加速数据帧 真正的 Python 播客 2023 年 1 月 13 日57m RSS Apple Podcasts Google Podcasts Spotify More 播客瘾君子 灰蒙蒙 袖珍铸件
  • 2020 年 12 月 2 日

    继续上周会议的内容 在本集中我们将深入探讨使用 PyInstaller 创建可安装应用程序的现场演示
  • 第 72 集:从 FastAPI 开始并检查 Python 的导入系统

    第 72 集 从 FastAPI 开始并检查 Python 的导入系统 真正的 Python 播客 2021 年 8 月 6 日45m RSS Apple Podcasts Google Podcasts Spotify More 播客瘾君
  • 回复消息

    在本课程中 您将学习如何让您的机器人做出响应具体消息在一次聊天中 您将通过处理以下内容来添加机器人以前的功能on message 事件 因为一个Client无法区分机器人用户和普通用户帐户之间的区别 您的on message 处理程序应防止
  • 选择记录类型

    数据记录或普通数据对象用于将相关字段分组到一个位置 Python 提供了多种执行此分组的方法 包括使用dict类型 面向对象的机制 例如类和数据类 以及struct图书馆的Struct二进制记录的对象 以下是有关记录 数据类和结构的资源和附
  • Python 中使用 spaCy 进行自然语言处理

    目录 NLP 和 spaCy 简介 安装spaCy 已处理文本的 Doc 对象 句子检测 spaCy 中的令牌 停用词 词形还原 词频 词性标注 可视化 使用 displaCy 预处理函数 使用 spaCy 基于规则的匹配 使用 spaCy
  • 关于盖尔·阿恩·耶勒

    关于盖尔 阿恩 耶勒 推特 GitHub 经过多年思考这个问题我们在哪里 我现在与数据科学和机器学习在挪威奥斯陆 早些时候 我接触过从 Basic Awk Java 和 C 到 Assembly 的所有语言 现在 我很幸运能够用 Pytho
  • 计算机视觉技术

    人脸检测是一种计算机视觉技术 能够识别数字图像中的人脸 这对人类来说很容易 但计算机需要精确的指令 这些图像可能包含许多非人脸的物体 例如建筑物 汽车 动物等 它与其他涉及人脸的计算机视觉技术 例如面部识别 分析和跟踪 不同 面部识别涉及将
  • 2021 年 6 月 16 日

    主持人大卫 阿莫斯回答会员的问题 在这次会议上 我们讨论了 Python 新闻和更新 为什么不能在续行符后发表评论 如何使用括号隐式连接字符串 一些替代的 Python REPL 是什么 如何开始为开源做出贡献 在哪里可以找到 Python
  • 惯用的熊猫:你可能不知道的技巧和功能

    pandas 是一个用于分析 数据处理和数据科学的基础库 这是一个巨大的项目 具有大量的可选性和深度 在本课程中 您将了解如何使用一些较少使用但惯用的 pandas 功能 使您的代码具有更好的可读性 多功能性和速度 包含内容 11 Less