使用 INSERT 语句

Using INSERT Statements

使用 Core 以及在进行批量操作时使用 ORM 时,直接使用 insert() 函数生成 SQL INSERT 语句 - 该函数生成一个新的 Insert 实例,表示 SQL 中的 INSERT 语句,用于向表中添加新数据。

ORM 读者 -

本节详细介绍了 Core 生成单个 SQL INSERT 语句以向表中添加新行的方式。使用 ORM 时,我们通常使用另一个工具,即 unit of work,它将自动生成多个 INSERT 语句。然而,即使在 ORM 为我们运行时,了解 Core 如何处理数据创建和操作也非常有用。此外,ORM 支持直接使用 INSERT,使用称为 批量/多行 INSERT、upsert、UPDATE 和 DELETE 的功能。

要直接跳到如何使用正常的工作单元模式使用 ORM 插入行,请参阅 使用 ORM 工作单元模式插入行

When using Core as well as when using the ORM for bulk operations, a SQL INSERT statement is generated directly using the insert() function - this function generates a new instance of Insert which represents an INSERT statement in SQL, that adds new data into a table.

ORM Readers -

This section details the Core means of generating an individual SQL INSERT statement in order to add new rows to a table. When using the ORM, we normally use another tool that rides on top of this called the unit of work, which will automate the production of many INSERT statements at once. However, understanding how the Core handles data creation and manipulation is very useful even when the ORM is running it for us. Additionally, the ORM supports direct use of INSERT using a feature called 批量/多行 INSERT、upsert、UPDATE 和 DELETE.

To skip directly to how to INSERT rows with the ORM using normal unit of work patterns, see 使用 ORM 工作单元模式插入行.

insert() SQL 表达式构造

The insert() SQL Expression Construct

一个简单的 Insert 示例,展示目标表和 VALUES 子句:

>>> from sqlalchemy import insert
>>> stmt = insert(user_table).values(name="spongebob", fullname="Spongebob Squarepants")

上面的 stmt 变量是 Insert 的一个实例。大多数 SQL 表达式可以直接字符串化,以查看生成的内容的一般形式:

>>> print(stmt)
INSERT INTO user_account (name, fullname) VALUES (:name, :fullname)

字符串化形式是通过生成对象的 Compiled 形式创建的,其中包括语句的数据库特定字符串 SQL 表示;我们可以使用 ClauseElement.compile() 方法直接获取此对象:

>>> compiled = stmt.compile()

我们的 Insert 构造是一个“参数化”构造的示例,之前在 发送参数 中说明;要查看 namefullname bound parameters,这些也可以从 Compiled 构造中获取:

>>> compiled.params
{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}

A simple example of Insert illustrating the target table and the VALUES clause at once:

>>> from sqlalchemy import insert
>>> stmt = insert(user_table).values(name="spongebob", fullname="Spongebob Squarepants")

The above stmt variable is an instance of Insert. Most SQL expressions can be stringified in place as a means to see the general form of what’s being produced:

>>> print(stmt)
INSERT INTO user_account (name, fullname) VALUES (:name, :fullname)

The stringified form is created by producing a Compiled form of the object which includes a database-specific string SQL representation of the statement; we can acquire this object directly using the ClauseElement.compile() method:

>>> compiled = stmt.compile()

Our Insert construct is an example of a “parameterized” construct, illustrated previously at 发送参数; to view the name and fullname bound parameters, these are available from the Compiled construct as well:

>>> compiled.params
{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}

执行语句

Executing the Statement

通过执行该语句,我们可以在 user_table 中插入一行。 INSERT SQL 以及捆绑的参数可以在 SQL 日志中看到:

>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     conn.commit()
BEGIN (implicit) INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] ('spongebob', 'Spongebob Squarepants') COMMIT

在上面的简单形式中,INSERT 语句不返回任何行,如果仅插入一行,通常会包括返回在插入该行期间生成的列级默认值信息的功能,最常见的是整数主键值。在上述情况下,SQLite 数据库中的第一行通常会返回第一个整数主键值 1,我们可以使用 CursorResult.inserted_primary_key 访问器获取:

>>> result.inserted_primary_key
(1,)

小技巧

CursorResult.inserted_primary_key 返回一个元组,因为主键可能包含多个列。这被称为 composite primary keyCursorResult.inserted_primary_key 旨在始终包含刚插入记录的完整主键,而不仅仅是“cursor.lastrowid”类型的值,并且无论是否使用“自增”,都旨在填充,因此为了表达完整的主键,它是一个元组。

在 1.4.8 版本发生变更: CursorResult.inserted_primary_key 返回的元组现在是一个命名元组,通过将其返回为 Row 对象来实现。

Invoking the statement we can INSERT a row into user_table. The INSERT SQL as well as the bundled parameters can be seen in the SQL logging:

>>> with engine.connect() as conn:
...     result = conn.execute(stmt)
...     conn.commit()
BEGIN (implicit) INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] ('spongebob', 'Spongebob Squarepants') COMMIT

In its simple form above, the INSERT statement does not return any rows, and if only a single row is inserted, it will usually include the ability to return information about column-level default values that were generated during the INSERT of that row, most commonly an integer primary key value. In the above case the first row in a SQLite database will normally return 1 for the first integer primary key value, which we can acquire using the CursorResult.inserted_primary_key accessor:

>>> result.inserted_primary_key
(1,)

小技巧

CursorResult.inserted_primary_key returns a tuple

because a primary key may contain multiple columns. This is known as a composite primary key. The CursorResult.inserted_primary_key is intended to always contain the complete primary key of the record just inserted, not just a “cursor.lastrowid” kind of value, and is also intended to be populated regardless of whether or not “autoincrement” were used, hence to express a complete primary key it’s a tuple.

在 1.4.8 版本发生变更: the tuple returned by

CursorResult.inserted_primary_key is now a named tuple fulfilled by returning it as a Row object.

INSERT 通常会自动生成“values”子句

INSERT usually generates the “values” clause automatically

示例中使用了 Insert.values() 方法来显式创建 SQL INSERT 语句的 VALUES 子句。如果我们不实际使用 Insert.values() 并仅打印出一个“空”语句,我们会得到一个包含表中每一列的 INSERT:

>>> print(insert(user_table))
INSERT INTO user_account (id, name, fullname) VALUES (:id, :name, :fullname)

如果我们使用尚未调用 Insert.values()Insert 构造并执行它而不是打印它,该语句将基于我们传递给 Connection.execute() 方法的参数编译为字符串,并仅包括与传递的参数相关的列。这实际上是使用 Insert 插入行的常用方法,无需键入显式 VALUES 子句。下面的示例说明了一个带有一组参数的两列 INSERT 语句的执行:

>>> with engine.connect() as conn:
...     result = conn.execute(
...         insert(user_table),
...         [
...             {"name": "sandy", "fullname": "Sandy Cheeks"},
...             {"name": "patrick", "fullname": "Patrick Star"},
...         ],
...     )
...     conn.commit()
BEGIN (implicit) INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] [('sandy', 'Sandy Cheeks'), ('patrick', 'Patrick Star')] COMMIT

上面的执行采用了在 发送多个参数 中首次介绍的“executemany”形式,但与使用 text() 构造不同,我们不需要拼出任何 SQL。通过将字典或字典列表传递给与 Insert 构造结合使用的 Connection.execute() 方法,Connection 确保传递的列名将自动在 Insert 构造的 VALUES 子句中表示。

Deep Alchemy

嗨,欢迎来到 深度炼金术(Deep Alchemy) 的第一版。左边的人被称为 炼金术士(The Alchemist) ,你会注意到他们 不是 巫师,因为尖顶帽子没有向上翘。炼金术士来解释通常 更高级和/或复杂(more advanced and/or tricky)通常不需要(not usually needed) 的内容,但无论出于何种原因,他们认为你应该了解 SQLAlchemy 能做的这个事情。

在本版中,为了在 address_table 中也有一些有趣的数据,下面是一个更高级的示例,说明如何在同时包括从参数生成的附加 VALUES 的情况下显式使用 Insert.values() 方法。构造了一个 scalar subquery,使用了在下一节中介绍的 select() 构造,子查询中使用的参数使用 bindparam() 构造显式设置绑定参数名称。

这是一些稍微 更深(deeper) 的炼金术,我们可以添加相关的行而不需要将 user_table 操作的主键标识符获取到应用程序中。大多数炼金术士将简单地使用 ORM,它会为我们处理类似的事情。

>>> 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()
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

有了这些,我们的表中有了一些更有趣的数据,我们将在接下来的部分中使用这些数据。

小技巧

如果我们指示 Insert.values() 不带任何参数,则生成一个真正的“空” INSERT,它仅插入表的“默认值”而不包含任何显式值;并非每个数据库后端都支持此功能,但这是 SQLite 生成的内容:

>>> print(insert(user_table).values().compile(engine))

{printsql}INSERT INTO user_account DEFAULT VALUES

The example above made use of the Insert.values() method to explicitly create the VALUES clause of the SQL INSERT statement. If we don’t actually use Insert.values() and just print out an “empty” statement, we get an INSERT for every column in the table:

>>> print(insert(user_table))
INSERT INTO user_account (id, name, fullname) VALUES (:id, :name, :fullname)

If we take an Insert construct that has not had Insert.values() called upon it and execute it rather than print it, the statement will be compiled to a string based on the parameters that we passed to the Connection.execute() method, and only include columns relevant to the parameters that were passed. This is actually the usual way that Insert is used to insert rows without having to type out an explicit VALUES clause. The example below illustrates a two-column INSERT statement being executed with a list of parameters at once:

>>> with engine.connect() as conn:
...     result = conn.execute(
...         insert(user_table),
...         [
...             {"name": "sandy", "fullname": "Sandy Cheeks"},
...             {"name": "patrick", "fullname": "Patrick Star"},
...         ],
...     )
...     conn.commit()
BEGIN (implicit) INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] [('sandy', 'Sandy Cheeks'), ('patrick', 'Patrick Star')] COMMIT

The execution above features “executemany” form first illustrated at 发送多个参数, however unlike when using the text() construct, we didn’t have to spell out any SQL. By passing a dictionary or list of dictionaries to the Connection.execute() method in conjunction with the Insert construct, the Connection ensures that the column names which are passed will be expressed in the VALUES clause of the Insert construct automatically.

Deep Alchemy

Hi, welcome to the first edition of Deep Alchemy. The person on the left is known as The Alchemist, and you’ll note they are not a wizard, as the pointy hat is not sticking upwards. The Alchemist comes around to describe things that are generally more advanced and/or tricky and additionally not usually needed, but for whatever reason they feel you should know about this thing that SQLAlchemy can do.

In this edition, towards the goal of having some interesting data in the address_table as well, below is a more advanced example illustrating how the Insert.values() method may be used explicitly while at the same time including for additional VALUES generated from the parameters. A scalar subquery is constructed, making use of the select() construct introduced in the next section, and the parameters used in the subquery are set up using an explicit bound parameter name, established using the bindparam() construct.

This is some slightly deeper alchemy just so that we can add related rows without fetching the primary key identifiers from the user_table operation into the application. Most Alchemists will simply use the ORM which takes care of things like this for us.

>>> 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()
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

With that, we have some more interesting data in our tables that we will make use of in the upcoming sections.

小技巧

A true “empty” INSERT that inserts only the “defaults” for a table without including any explicit values at all is generated if we indicate Insert.values() with no arguments; not every database backend supports this, but here’s what SQLite produces:

>>> print(insert(user_table).values().compile(engine))

{printsql}INSERT INTO user_account DEFAULT VALUES

INSERT…RETURNING

对于支持的后端,RETURNING 子句会自动使用以检索最后插入的主键值以及服务器默认值。然而,也可以使用 Insert.returning() 方法显式指定 RETURNING 子句;在这种情况下,执行语句时返回的 Result 对象包含可以获取的行:

>>> insert_stmt = insert(address_table).returning(
...     address_table.c.id, address_table.c.email_address
... )
>>> print(insert_stmt)
INSERT INTO address (id, user_id, email_address) VALUES (:id, :user_id, :email_address) RETURNING address.id, address.email_address

它还可以与 Insert.from_select() 结合使用,如下面的示例所示,构建在 INSERT…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))
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

小技巧

RETURNING 功能还支持 UPDATE 和 DELETE 语句,这将在本教程的后面介绍。

对于 INSERT 语句,RETURNING 功能可用于单行语句以及一次插入多行的语句。支持 RETURNING 的多行 INSERT 是特定于方言的,但对于支持 RETURNING 的所有 SQLAlchemy 包含的方言均支持此功能。有关此功能的背景信息,请参阅部分 INSERT 语句的“插入多个值”行为

参见

ORM 也支持带或不带 RETURNING 的批量 INSERT。参考文档请参阅 ORM 批量 INSERT 语句

The RETURNING clause for supported backends is used automatically in order to retrieve the last inserted primary key value as well as the values for server defaults. However the RETURNING clause may also be specified explicitly using the Insert.returning() method; in this case, the Result object that’s returned when the statement is executed has rows which can be fetched:

>>> insert_stmt = insert(address_table).returning(
...     address_table.c.id, address_table.c.email_address
... )
>>> print(insert_stmt)
INSERT INTO address (id, user_id, email_address) VALUES (:id, :user_id, :email_address) RETURNING address.id, address.email_address

It can also be combined with Insert.from_select(), as in the example below that builds upon the example stated in INSERT…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))
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

小技巧

The RETURNING feature is also supported by UPDATE and DELETE statements, which will be introduced later in this tutorial.

For INSERT statements, the RETURNING feature may be used both for single-row statements as well as for statements that INSERT multiple rows at once. Support for multiple-row INSERT with RETURNING is dialect specific, however is supported for all the dialects that are included in SQLAlchemy which support RETURNING. See the section INSERT 语句的“插入多个值”行为 for background on this feature.

参见

Bulk INSERT with or without RETURNING is also supported by the ORM. See ORM 批量 INSERT 语句 for reference documentation.

INSERT…FROM SELECT

一个较少使用但为完整性考虑而存在的 Insert 特性是,Insert 构造可以通过 Insert.from_select() 方法直接从 SELECT 获取行来组成 INSERT。这种方法接受一个 select() 构造和一列列名列表,目标是实际 INSERT 中的列。在下面的示例中,行被添加到 address 表中,这些行是从 user_account 表中派生的,给每个用户一个免费的 aol.com 电子邮件地址:

>>> 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)
INSERT INTO address (user_id, email_address) SELECT user_account.id, user_account.name || :name_1 AS anon_1 FROM user_account

这种构造用于当希望将数据从数据库的某些其他部分直接复制到一组新行中,而无需实际从客户端获取和重新发送数据时。

参见

Insert - 在 SQL 表达式 API 文档中

A less used feature of Insert, but here for completeness, the Insert construct can compose an INSERT that gets rows directly from a SELECT using the Insert.from_select() method. This method accepts a select() construct, which is discussed in the next section, along with a list of column names to be targeted in the actual INSERT. In the example below, rows are added to the address table which are derived from rows in the user_account table, giving each user a free email address at aol.com:

>>> 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)
INSERT INTO address (user_id, email_address) SELECT user_account.id, user_account.name || :name_1 AS anon_1 FROM user_account

This construct is used when one wants to copy data from some other part of the database directly into a new set of rows, without actually fetching and re-sending the data from the client.

参见

Insert - in the SQL Expression API documentation