33

I have been working on an application in Django. To begin with, for simplicity, I had been using sqlite3 for the database.

However, once I moved to PostgreSQL, I've run into a bit of a problem: the primary key does not reset once I clear out a table.

This app is a game that is played over a long time period (weeks). As such, every time a new game starts, all of the data is cleared out of the database and then new, randomized data is added.

I'd like to be able to "start over" with primary keys starting at 1 each time I clean/rebuild the game.

The code still works as-is, but integers are a pretty natural way for describing the objects in my game. I'd like to have each new game start at 1 rather than wherever the last game left off.

How can I reset the primary key counter in PostgreSQL? Keep in mind that I don't need to preserve the data in the table since I am wiping it out anyway.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
TM.
  • 108,298
  • 33
  • 122
  • 127
  • I suppose I could resort to just dropping the table and then recreating it, but I'd rather not if it could be avoided... – TM. Feb 13 '09 at 05:59
  • i dropped the table myself ... was taking me too much time trying to figure out – fuentesjr Feb 13 '09 at 07:12

6 Answers6

39

In your app directory try this:

python manage.py help sqlsequencereset

Pipe it into psql like this to actually run the reset:

python manage.py sqlsequencereset myapp1 myapp2 | psql

Edit: here's an example of the output from this command on one of my tables:

BEGIN;
SELECT setval('"project_row_id_seq"', coalesce(max("id"), 1), max("id") IS NOT null) FROM "project_row";
COMMIT;
Van Gale
  • 43,536
  • 9
  • 71
  • 81
  • The sqlsequencereset commmand generates the SQL needed to reset the primary key counter, so on windows, instead of piping the output to psql, I would copy+paste it into whatever admin app you use. For example, if pgAdminIII is installed, paste into "execute arbitrary SQL queries". – Van Gale Feb 13 '09 at 07:21
  • I will check this out when I get home from work, thanks! Is there way to do this programmatically? I'd like to be able to easily stick it in a django view if possible. – TM. Feb 13 '09 at 13:37
  • That seems a bit dangerous, but to each his own! Use this method: http://docs.djangoproject.com/en/dev/topics/db/sql/ – Van Gale Feb 13 '09 at 14:01
  • Yes, I know it seems dangerous, but I'd like people using this game to be able to restart it without having to know the table structure or django commands. Really the only data "at risk" is randomized game state data for their own install of the game. – TM. Feb 13 '09 at 14:44
  • You can call the sqlsequencereset management command from code. It's a messy business, and this code does it: http://djangosnippets.org/snippets/2774/ Do note Django ticket 18387 though that means if it fails, call_command() will call sys.exit, which ain't gonna make you happy. If that matters and you're not using Django 1.6, you'll need to knock up your own code. – tobych Jun 14 '13 at 20:27
  • 1
    This is the recommended method here also https://code.djangoproject.com/ticket/11423. You could simplify by piping it to dbshell `python manage.py sqlsequencereset | python managy.py dbshell` – user May 23 '14 at 18:01
  • I'm using django 1.7 and get an error: App 'myapp' has migrations. Only the sqlmigrate and sqlflush commands can be used when an app has migrations. I have run manage.py makemigrations and migrate... – Chase Roberts Jan 03 '15 at 20:45
21

As suggested by "Van Gale" you can get the commands to solve your problem running sqlsequencereset.

or

You can execute the SQL query generated by sqlsequencereset from within python in this way (using the default database):

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)

I tested this code with Python3.6, Django 2.0 and PostgreSQL 10.

Paolo Melchiorre
  • 5,716
  • 1
  • 33
  • 52
6

If you perform a raw sql, can do this:

ALTER SEQUENCE youApp_id_seq RESTART WITH 1;

docs: http://www.postgresql.org/docs/8.2/static/sql-altersequence.html

Lauro Oliveira
  • 2,362
  • 1
  • 18
  • 12
1

I view auto-increment primary keys as purely internal identifiers for database records, and I don't like exposing them to users. Granted, it's a common design to use them as part of URLs, but even there slugs or other identifiers feel more appropriate.

akaihola
  • 26,309
  • 7
  • 59
  • 69
  • I normally would too, but the objects in this case are already going to be numbered, and displayed as 1, 2, 3... etc. – TM. Feb 14 '09 at 14:25
1

If you do not want to have to manually grab the apps you need, or if you have a series of different databases, this command will dynamically gather all connections from settings.py and reset the sequence.

To run use: python manage.py reset_sequences

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)

ViaTech
  • 2,143
  • 1
  • 16
  • 51
0

You need to truncate the table. See http://www.postgresql.org/docs/8.1/static/sql-truncate.html

superUntitled
  • 22,351
  • 30
  • 83
  • 110
  • Doesn't seem to work... deletes all the data, but the primary keys still start where they left off before. For example, if I insert 500 rows, then truncate the table, then insert another 500, the primary keys will be from 501 to 1000, not from 1-500. – TM. Feb 13 '09 at 05:58
  • 1
    Hmmm, try this: ALTER TABLE theTableInQuestion AUTO_INCREMENT=0 – superUntitled Feb 13 '09 at 06:35
  • Never mind, that is for mySql, sorry I cannot not be of any assistance. – superUntitled Feb 13 '09 at 06:57