如何用Django搞定SQL的“脏活累活”

如果要用更少的代码构建更高效的应用程序,并与高度可扩展的数据库建立连接,Python,尤其是Django往往是必不可少的。本文将简要谈谈在使用SQL和Python构建和支持现代应用的过程中,该如何减小日常工作中的摩擦,并将应用中蕴含的复杂性抽象出来,从而简化开发者的日常工作。

过于深入的技术细节先抛开不谈,不妨就让我们假设:

  • SQL是专为SQL数据库优化的
  • Python并没有为SQL数据库进行优化

下文将介绍如何在无需实际编写SQL语句的情况下,通过Python和相关工具来执行原始SQL命令。为此我们将使用Django的数据建模能力,但具体语法其实与Python中的SQLAlchemy包非常类似。

这就开始吧!

这是一个Django数据模型范例:

class BlogArticle(models.Model):
    user = models.ForeignKey(User, default=1, on_delete=models.SET_DEFAULT)
    title = models.CharField(max_length=120)
    slug = models.SlugField(blank=True, null=True)
    content = models.TextField(blank=True, null=True)
    publish_timestamp = models.DateTimeField(
        auto_now_add=False,
        auto_now=False,
        blank=True,
        null=True,
    )

假设该模型位于一个名为Articles的Django应用中(Django应用本质上是指构成整个Django项目的不同组件)。

那么我们就有两个名称需要处理:

  • Articles(应用名)
  • BlogArticle(模型名)

结合在一起,就可以形成如下的SQL表名称:

articles_blog_article

接下来就可以看着Django为我们变魔术了。

如果使用MySQL shell,将能看到这样的结果:

mysql> SHOW TABLES;
+------------------------------+
| Tables_in_cfe_django_blog_db |
+------------------------------+
| articles_blog_article        |
| auth_group                   |
| auth_group_permissions       |
| auth_permission              |
| auth_user                    |
| auth_user_groups             |
| auth_user_user_permissions   |
| django_admin_log             |
| django_content_type          |
| django_migrations            |
| django_session               |
+------------------------------+
11 rows in set (0.01 sec)

接下来一起看看Django模型中的列:

mysql> DESCRIBE articles_blog_article;
+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| id                     | bigint       | NO   | PRI | NULL    | auto_increment |
| title                  | varchar(120) | NO   |     | NULL    |                |
| slug                   | varchar(50)  | YES  | MUL | NULL    |                |
| content                | longtext     | YES  |     | NULL    |                |
| publish_timestamp      | datetime(6)  | YES  |     | NULL    |                |
| user_id                | int          | NO   | MUL | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+

12 rows in set (0.00 sec)

除了编写一个最基本的配置外,Django在这个MySQL数据库中帮助我们完成了所有与SQL有关的工作。其实到这里,Django并没有表现出什么让人印象深刻的作用。实际上,本文中涉及到的大部分内容并不是为了凸显可将Django或Python作为SQL的替代品,而是为了向大家展示它在“抽象”方面的作用。

Python的崛起和“摩擦”的代价

一起来看看阻力最小的路径是什么样的,以及为什么说Python是利用SQL的最佳方式。

Python代码的撰写、阅读、运行以及发布都很容易。但如果将“Python”换成其他任何一种编程语言,这个结论几乎就可以肯定无法成立了。JavaScript虽然也是Python的“竞争者”,但不少人至今依然会把它和Java混淆。这些观点都是概括性的,并非总是成立,但却是很多开发者经常会遇到的难题。

但实际上这种概括性的观点大部分时候都能成立,因为Python的语法实际上和英语语法几乎没有差别!

不信就一起比一比SQL语句以及Python和Django的语句吧:

  • SQL:SELECT * from articles_blog_article;
  • Python和Django:items = BlogArticle.objects.all()

这两条语句可以产生相同的数据,不过Python语句会返回一个Python对象(项)的列表,几乎任何有经验的Python开发者都可以直接使用该列表。而SQL语句产生的结果必须首先进行转换,随后才能供Python应用使用。

字段描述

再来仔细看看SQL的字段描述:

+------------------------+--------------+------+-----+---------+----------------+
| Field                  | Type         | Null | Key | Default | Extra          |
+------------------------+--------------+------+-----+---------+----------------+
| title                  | varchar(120) | NO   |     | NULL    |                |
+------------------------+--------------+------+-----+---------+----------------+

将上述内容与Django的字段描述对比一下:

title = models.CharField(max_length=120)
  • 哪个结果更直观?
  • 哪个结果更容易让人理解发生了什么?
  • 哪个结果能够提供“刚好够用”的信息?

如果一个并非开发者的人看到了varchar(120),会怎样理解这个内容?相信大家至少可以猜到max_length=120是什么意思。最酷的地方来了:完全正确!就是“将字段限制在120个字符以内”的意思。

向数据库添加数据

如果使用Django:

BlogArticle.objects.create(
    title="Hello World",
    content="Coming Soon",
    slug="hello-world",
    publish_timestamp=None,
)

如果使用SQL:

INSERT INTO `articles_blog_article` (`user_id`, `title`, `slug`, `content`, `publish_timestamp`) 
VALUES (1, 'Hello World', 'hello-world', 'Coming Soon', NULL);

说到简洁明了,这方面的冠军无疑就是Django和Python了。title = "Hello World"无疑要比弄清楚SQL中的等效列(字段)值是怎么回事更容易。没错,只有当你明确知道自己在所什么时,SQL中的这种写法才会足够高效。

添加多个行

如果使用Django:

items = [
    BlogArticle(title='Hello Again 0', slug='hello-again-0', content="Coming Soon"),
    BlogArticle(title='Hello Again 1', slug='hello-again-1', content="Coming Soon"),
    BlogArticle(title='Hello Again 2', slug='hello-again-2', content="Coming Soon"),
    BlogArticle(title='Hello Again 3', slug='hello-again-3', content="Coming Soon"),
    BlogArticle(title='Hello Again 4', slug='hello-again-4', content="Coming Soon"),
]
BlogArticle.objects.bulk_create(items)

如果使用SQL:

INSERT INTO `articles_blog_article` (`user_id`, `title`, `slug`, `content`, `publish_timestamp`) 
VALUES (1, 'Hello Again 0', 'hello-again-0', 'Coming Soon', NULL),
    (1, 'Hello Again 1', 'hello-again-1', 'Coming Soon', NULL),
    (1, 'Hello Again 2', 'hello-again-2', 'Coming Soon', NULL),
    (1, 'Hello Again 3', 'hello-again-3', 'Coming Soon', NULL),
    (1, 'Hello Again 4', 'hello-again-4', 'Coming Soon', NULL);

这再次证明了Python代码有着更高可读性,而SQL代码能针对实际数据提供更深入的见解。不过上述SQL代码依然是通过Python使用Django写出来的。很棒对吧!

请注意:上述对比并不是为了确定哪种技术才是利用SQL数据库的最佳方式,而是为了凸显Python在帮助开发者减轻学习和直接编写SQL语句的负担方面所蕴含的潜力。

有很多Python包可以帮助开发者编写原始SQL语句,包括但不限于:

  • Django
  • Pandas
  • SQLAlchemy
  • Polars
  • Dask
  • Vaex
  • Python’s built-in CSV Module
  • Tortoise ORM
  • Pony ORM
  • SQLObject

Django替你负重前行

Python这样利用SQL数据库的秘诀在于对象关系映射包(通常也被称之为ORM)。我们可以将ORM看作一种“中间人”,它可以帮助我们在任何特定语言的原生语法中移动数据。

上文我们展示了如何将SQL转换为Django,接下来不放再发散思考一下还能怎样进行扩展。

假设数据库中有大量数据,并且我们编写了这样一条命令:

my_post = BlogArticle.objects.first()

该语句可以查询数据库,提取数据,将数据载入Python Class实例,并将其分配给变量my_post。

这样我们就可以执行类似下面这样的操作:

# using a django-managed python shell
# via python manage.py shell
>>> print(my_post.title)
Hello World

本例中我们使用“dot”符号来访问上文提到的BlogPost这个Django模型的title字段。该字段对应于SQL数据库表articles_blog_article中的一个列。

借助ORM,我们将能做到:
>>> my_post.title = "some other title"

在这个Python Shell会话的例子中,my_post.title将始终保持为"some other title"。然而底层的SQL数据库依然能将这个数据识别为Hello World。数据库将保持原始数据,直到Python最终将数据的变化提交(即.save())到数据库。如果Python从不提交该数据,那么数据库的内容将永不更新。这也恰恰是ORM的神奇之处。我们可以在不影响实际已存储数据的前提下使用和更改数据。当需要对数据库中的内容进行实际修改时,可以运行:

>>> my_post.title = "some other title again"
>>> my_post.save()

在针对数据库运行.save()后,数据库中特定的行将被更新为新的标题,进而与上述Python代码中给出的字符串保持一致。别忘了,.save()方法是专门用于向Django模型提交数据库改动的,.save()对Python的Class没有任何实际意义,除非首先继承了形式的Django模型类。

你可能感兴趣的:(如何用Django搞定SQL的“脏活累活”)