如何使用Django的ORM截断表格?

67

为了清空一个数据库表,我使用以下的SQL查询:

TRUNCATE TABLE `books`

如何使用Django的模型和ORM截断表格?

我尝试过这个,但它并不起作用:

Book.objects.truncate()

虽然下面有几个很好的答案,但你应该意识到,在你的 SQL 数据库中,表名将会在模型中加上 appname_ 前缀。 - joel goldstick
使用任何SQL,而不仅仅是TRUNCATE:https://dev59.com/questions/sW855IYBdhLWcg3wGQXY - Ciro Santilli OurBigBook.com
9个回答

91

使用 ORM,你最接近的方法是 Book.objects.all().delete()

但是有一些区别:truncate 可能更快,但 ORM 也会追踪外键引用并删除其他表中的对象。


谢谢。这就行了。在我的情况下,速度不重要。 - Silver Light
4
似乎并非适用于所有数据库后端-当我在sqlite3上尝试对大约3000条记录的表执行上述操作时,出现了"太多SQL变量"的DatabaseError错误。也许我只是缺少批处理/ sqlite的配置值。 - Bernhard Kircher
@BernhardKircher:请查看我作为单独答案的补充。 - michel.iamit
4
在MySQL中,也是使用DELETE而不是TRUNCATE。DELETE不会重置索引。 - Master Bee
Sqlite3 也有 delete,但没有 truncate。 - Legolas Bloom
大多数数据库在DB级别上实现了此命令,称之为截断(truncate)。因此根据此链接:https://code.djangoproject.com/ticket/16427,似乎不应该存在性能问题。 - Reza Abbasi

44

您可以以快速且轻量的方式完成此操作,但不使用Django的ORM。您可以使用Django连接游标执行原始SQL:

from django.db import connection
cursor = connection.cursor()
cursor.execute("TRUNCATE TABLE `books`")

谢谢,但我更喜欢让我的应用程序与尽可能多的数据库引擎配合使用,而不是使用原始的SQL。 - Silver Light
8
TRUNCATE TABLE是“典型”的SQL语法的一部分,虽然只在官方SQL:2008规范中被正式支持。Django支持Postgres、MySQL、SQLite*和Oracle。http://www.postgresql.org/docs/8.1/interactive/sql-truncate.html http://dev.mysql.com/doc/refman/4.1/en/truncate-table.html http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_10006.htm*SQLite不支持TRUNCATE [TABLE],需要使用DELETE FROM来代替。当然,这仅适用于需要更高性能的情况。 - Aea
还有可能存在截断问题(取决于所使用的数据库系统)。例如,Microsoft SQL Server不允许对被外键引用的表进行截断。我只是想提一下这个问题,也许其他数据库系统有不同的行为。 - Bernhard Kircher
在大多数数据库中,您需要在执行execute()调用后调用django.db.transaction.commit_unless_managed()。 - tobych
1
请注意,许多关系型数据库(如Oracle和MySQL)将TRUNCATE视为DDL而非DML,并且它不是事务处理的一部分! - Vajk Hermecz
从django.db中导入connection; 从psycopg2中导入sql; connection.cursor().execute(sql.SQL("TRUNCATE TABLE {} RESTART IDENTITY").format(sql.Identifier(Model.objects.model._meta.db_table))) 如果存在引用给定表的表,则可能需要添加“CASCADE”,这将删除相关记录。 - x-yuri

30
您可以使用模型的_meta属性来填充数据库表名称:
from django.db import connection
cursor = connection.cursor()
cursor.execute('TRUNCATE TABLE "{0}"'.format(MyModel._meta.db_table))

重要提示:这个方法对于涉及多个表的继承模型不适用!


10

除了Ned Batchelder的回答,参考Bernhard Kircher的评论:

在我的情况下,我需要使用Web应用程序清空一个非常大的数据库:

Book.objects.all().delete()

在开发SQLlite环境中,返回:

too many SQL variables

所以我添加了一个小的解决方法。它可能不是最整洁的,但至少在 Django 的 ORM 构建截断表选项之前可以使用:

countdata = Book.objects.all().count()
logger.debug("Before deleting: %s data records" % countdata)
while countdata > 0:
    if countdata > 999:
        objects_to_keep = Book.objects.all()[999:]
        Book.objects.all().exclude(pk__in=objects_to_keep).delete()
        countdata = Book.objects.all().count()
    else:
        Book.objects.all().delete()
        countdata = Book.objects.all().count()

顺便提一下,我的一些代码是基于"Django Delete all but last five of queryset"。

我在意识到该答案已经被回答的情况下添加了这个,但希望这个补充能帮助其他人。


不错,但在我的MySQL中出现了“NotSupportedError:(1235,“此版本的MySQL尚不支持'LIMIT&IN / ALL / ANY / SOME子查询'”)”错误。 - user1472229
那是哪个版本?(MySQL和Django都是)我在谷歌上搜索,得到的是MySQL 5.0(正确吗?)但对于一个非常旧且不再受支持的Django版本...(https://code.djangoproject.com/ticket/10099) - michel.iamit
再次强调,Book.objects.all().delete() 不是一个 truncate 操作。 - undefined

8

我知道这是一个很老的问题,并且这里有一些正确的答案,但我忍不住要分享最优雅和最快速的方法来完成这个问题的目的。

class Book(models.Model):
    # Your Model Declaration

    @classmethod
    def truncate(cls):
        with connection.cursor() as cursor:
            cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table))

现在,如果要从Book表中截断所有数据,只需调用以下命令:

Book.truncate()

由于它直接与数据库交互,因此执行速度比其他方法更快。

Book.objects.all().delete()

0

这段代码使用的是PosgreSQL方言。如果要使用标准SQL,请省略级联部分。


跟进Shubho Shaha的回答,你也可以为此创建一个模型管理器。
class TruncateManager(models.Manager):
    def truncate(self, cascade=False):
        appendix = " CASCADE;" if cascade else ";"
        raw_sql = f"TRUNCATE TABLE {self.model._meta.db_table}{appendix}"
        cursor = connection.cursor()
        cursor.execute(raw_sql)

class Truncatable(models.Model):
    class Meta:
        abstract = True

    objects = TruncateManager()

然后,您可以扩展Truncatable来创建可截断的对象:

class Book(Truncatable):
    ...

这将允许您在所有继承自Truncatable的模型上调用truncate。

Book.objects.truncate()

我添加了一个标记来使用级联,其中(危险区域)也将:"自动截断所有具有对命名表或由于CASCADE而添加到组中的任何表的外键引用的表。",这显然更具破坏性,但将允许代码在原子事务中运行。

0

4
这个管理命令似乎没有实现对表进行截断。它的实现似乎是调用了_model.objects.all().delete(),这绝对不是截断操作。https://github.com/KhaledElAnsari/django-truncate/blob/master/django_truncate/management/commands/truncate.py - BrennanR

0

对于我来说,要截断我的本地sqllite数据库,我最终使用python manage.py flush

我最初尝试的是迭代模型并逐个删除所有行:

models = [m for c in apps.get_app_configs() for m in c.get_models(include_auto_created=False)]

        for m in models:
            m.objects.all().delete()

但是由于我有保护外键,操作的成功取决于模型的顺序。

因此,我使用flush命令来截断我的本地测试数据库,并且它对我有效。 https://docs.djangoproject.com/en/3.0/ref/django-admin/#django-admin-flush


此解决方案不会截断。 - undefined

-1

这并不是直接回答楼主的问题,但仍然是一个可能用来实现相同目的的解决方案 - 不同的方法。


好吧,由于某些奇怪的原因(在尝试使用其他答案中建议的RAW方法时),我无法截断我的Django数据库缓存表,直到我做了类似这样的事情:

import commands
cmd = ['psql', DATABASE, 'postgres', '-c', '"TRUNCATE %s;"' % TABLE]
commands.getstatusoutput(' '.join(cmd))

基本上,我不得不通过数据库的实用命令 - 在这种情况下是 psql,因为我使用的是Postgres - 发出truncate命令。因此,自动化命令行可以处理这些边角情况。

可能会节省其他人一些时间...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接