38

我最近将大量数据从旧数据库导入到新的 Postgresql 数据库中,作为新 Django 站点中模型的基础。

我使用了旧数据库中的 ID(因为各个表中的行相互引用),但它们并不都是连续的——通常存在很大的差距。

我注意到,当我通过 Django 应用程序添加一个新对象时,它一直使用从 1 开始的 ID,这没有问题,因为没有 ID 非常低的行。

但是一旦它到达第一行遗留数据,那么 postgres 显然会抱怨:

ERROR:  duplicate key value violates unique constraint "django_comments_pkey"
DETAIL:  Key (id)=(25) already exists.

查看表格描述,我猜我需要在每个表格上重置某种序列:

                                      Table "public.django_comments"
     Column      |           Type           |                          Modifiers                           
-----------------+--------------------------+--------------------------------------------------------------
 id              | integer                  | not null default nextval('django_comments_id_seq'::regclass)
...

我需要做什么来重置该序列,以便添加 ID 高于当前最大 ID 的新行?

4

9 回答 9

61

运行sqlsequencereset它将打印您需要的所有重置命令。

于 2013-01-29T18:27:55.750 回答
20

正如“Dmitry Shevchenko”所建议的那样,您可以运行sqlsequencereset来解决您的问题。

或者

sqlsequencereset您可以通过这种方式执行从 python 中生成的 SQL 查询(使用默认数据库):

from django.core.management.color import no_style
from django.db import connection

from myapps.models import MyModel1, MyModel2


sequence_sql = connection.ops.sequence_reset_sql(no_style(), [MyModel1, MyModel2])
with connection.cursor() as cursor:
    for sql in sequence_sql:
        cursor.execute(sql)

我用Python3.6Django 2.0PostgreSQL 10测试了这段代码。

于 2018-05-10T15:03:18.667 回答
9

这是一个简短的片段,用于重置 Django 1.9+ 中的所有序列(基于http://djangosnippets.org/snippets/2774/)并与 Python 3 兼容:

import os
from io import StringIO

os.environ['DJANGO_COLORS'] = 'nocolor'

from django.core.management import call_command
from django.apps import apps
from django.db import connection

commands = StringIO()
cursor = connection.cursor()

for app in apps.get_app_configs():
    label = app.label
    call_command('sqlsequencereset', label, stdout=commands)

cursor.execute(commands.getvalue())
于 2016-08-17T06:03:50.147 回答
7

所以在我看来,最快、最简单、最“Django”的方法是使用以下管理命令:

python manage.py sqlsequencereset app_name

在此之后,您将获得以下内容:

BEGIN;
SELECT setval(pg_get_serial_sequence('"measurements_quantity"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "measurements.Quantities";
SELECT setval(pg_get_serial_sequence('"measurements.Prefixes"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "measurements.Prefixes";
COMMIT;

下一步是在python manage.py dbshell管理命令中运行它,所以运行它,然后您将在终端中看到交互数据库 shell:

psql (11.7 (Debian 11.7-0+deb10u1), server 11.5 (Debian 11.5-1.pgdg90+1))
Type "help" for help.

postgres=# BEGIN;
BEGIN
postgres=# SELECT setval(pg_get_serial_sequence('"measurements.Quantities"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "measurements.Quantities";
 setval 
--------
      1
(1 row)

postgres=# SELECT setval(pg_get_serial_sequence('"measurements.Prefixes"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "measurements.Prefixes";
 setval 
--------
      1
(1 row)

postgres=# COMMIT;
COMMIT
postgres=# exit

就那么简单。该python manage.py sqlsequencereset app_name命令将为您提供您需要运行的 SQL,并在dbshell.

无需编写您自己的自定义 SQL 或自定义代码,它会以正确的格式和选择的数据库引擎为您提供所需的内容。

于 2020-09-04T09:56:37.517 回答
4

PostgreSQL 命令: ALTER SEQUENCE app_model_id_seq RESTART WITH 1

于 2019-03-16T13:16:18.933 回答
1
select setval('django_comments_id_seq', 12345);
于 2013-01-29T18:26:22.973 回答
1

此片段在所有应用程序上运行 sqlsequencereset重置所有空模型的所有 ID

于 2013-05-14T15:06:49.273 回答
1

这是我刚刚在一个管理命令中实现的或多或少完全动态的解决方案,它对您尝试重置的主键的名称没有限制,因为它根据您在settings中的连接参数收集它。

我无法重置的唯一排序包括不是整数的 PK,这在django.contrib.sessions的 PK 中很明显,但我再次从未遇到过排序错误,所以我怀疑这是一个问题。

这是命令,使用运行python manage.py reset_sequences(显然只要您的文件/命令名为 reset_sequences.py)

import psycopg2
from django.conf import settings
from django.core.management.base import BaseCommand
from django.db import connections


def dictfetchall(cursor):
    """Return all rows from a cursor as a dict"""
    columns = [col[0] for col in cursor.description]
    return [
        dict(zip(columns, row))
        for row in cursor.fetchall()
    ]


class Command(BaseCommand):
    help = "Resets sequencing errors in Postgres which normally occur due to importing/restoring a DB"

    def handle(self, *args, **options):
        # loop over all databases in system to figure out the tables that need to be reset
        for name_to_use_for_connection, connection_settings in settings.DATABASES.items():
            db_name = connection_settings['NAME']
            host = connection_settings['HOST']
            user = connection_settings['USER']
            port = connection_settings['PORT']
            password = connection_settings['PASSWORD']

            # connect to this specific DB
            conn_str = f"host={host} port={port} user={user} password={password}"

            conn = psycopg2.connect(conn_str)
            conn.autocommit = True

            select_all_table_statement = f"""SELECT *
                                    FROM information_schema.tables
                                    WHERE table_schema = 'public'
                                    ORDER BY table_name;
                                """
            # just a visual representation of where we are
            print('-' * 20, db_name)
            try:
                not_reset_tables = list()
                # use the specific name for the DB
                with connections[name_to_use_for_connection].cursor() as cursor:
                    # using the current db as the cursor connection
                    cursor.execute(select_all_table_statement)
                    rows = dictfetchall(cursor)
                    # will loop over table names in the connected DB
                    for row in rows:
                        find_pk_statement = f"""
                            SELECT k.COLUMN_NAME
                            FROM information_schema.table_constraints t
                            LEFT JOIN information_schema.key_column_usage k
                            USING(constraint_name,table_schema,table_name)
                            WHERE t.constraint_type='PRIMARY KEY'
                                AND t.table_name='{row['table_name']}';
                        """
                        cursor.execute(find_pk_statement)
                        pk_column_names = dictfetchall(cursor)
                        for pk_dict in pk_column_names:
                            column_name = pk_dict['column_name']

                        # time to build the reset sequence command for each table
                        # taken from django: https://docs.djangoproject.com/en/3.0/ref/django-admin/#sqlsequencereset
                        # example: SELECT setval(pg_get_serial_sequence('"[TABLE]"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "[TABLE]";
                        try:
                            reset_statement = f"""SELECT setval(pg_get_serial_sequence('"{row['table_name']}"','{column_name}'), 
                                                    coalesce(max("{column_name}"), 1), max("{column_name}") IS NOT null) FROM "{row['table_name']}" """
                            cursor.execute(reset_statement)
                            return_values = dictfetchall(cursor)
                            # will be 1 row
                            for value in return_values:
                                print(f"Sequence reset to {value['setval']} for {row['table_name']}")
                        except Exception as ex:
                            # will only fail if PK is not an integer...
                            # currently in my system this is from django.contrib.sessions
                            not_reset_tables.append(f"{row['table_name']} not reset")

            except psycopg2.Error as ex:
                raise SystemExit(f'Error: {ex}')

            conn.close()
            print('-' * 5, ' ALL ERRORS ', '-' * 5)
            for item_statement in not_reset_tables:
                # shows which tables produced errors, so far I have only
                # seen this with PK's that are not integers because of the MAX() method
                print(item_statement)

            # just a visual representation of where we are
            print('-' * 20, db_name)

于 2020-08-11T18:31:42.000 回答
0

基于@Paolo Melchiorre,我创建了一个自定义管理命令,用于填充所选应用程序的所有模型。

from django.core.management.base import BaseCommand
from django.apps import apps
from django.core.management.color import no_style
from django.db import connection

class Command(BaseCommand):
    def handle(self, *args, **kwargs):
        self.stdout.write('Reset AutoFields ...')
        APPS = ['app1', 'app2']

        APPS = [apps.get_app_config(app) for app in APPS]
        models = []
        for app in APPS:
            models.extend(list(app.get_models()))

        sequence_sql = connection.ops.sequence_reset_sql(no_style(), models)
        with connection.cursor() as cursor:
            for sql in sequence_sql:
                self.stdout.write(sql)
                cursor.execute(sql)
        self.stdout.write(self.style.SUCCESS('Reset AutoField complete.'))

使用python 3.7和进行测试django 2.2

于 2019-08-21T14:31:53.123 回答