130

For development I'm using SQLite database with production in PostgreSQL. I updated my local database with data and need to transfer a specific table to the production database.

Running sqlite database .dump > /the/path/to/sqlite-dumpfile.sql, SQLite outputs a table dump in the following format:

BEGIN TRANSACTION;
CREATE TABLE "courses_school" ("id" integer PRIMARY KEY, "department_count" integer NOT NULL DEFAULT 0, "the_id" integer UNIQUE, "school_name" varchar(150), "slug" varchar(50));
INSERT INTO "courses_school" VALUES(1,168,213,'TEST Name A',NULL);
INSERT INTO "courses_school" VALUES(2,0,656,'TEST Name B',NULL);
....
COMMIT;

How do I convert this into a PostgreSQL compatible dump file I can import into my production server?

user4157124
  • 2,809
  • 13
  • 27
  • 42
DevX
  • 1,714
  • 2
  • 14
  • 17

8 Answers8

129

You should be able to feed that dump file straight into psql:

/path/to/psql -d database -U username -W < /the/path/to/sqlite-dumpfile.sql

If you want the id column to "auto increment" then change its type from "int" to "serial" in the table creation line. PostgreSQL will then attach a sequence to that column so that INSERTs with NULL ids will be automatically assigned the next available value. PostgreSQL will also not recognize AUTOINCREMENT commands, so these need to be removed.

You'll also want to check for datetime columns in the SQLite schema and change them to timestamp for PostgreSQL. (Thanks to Clay for pointing this out.)

If you have booleans in your SQLite then you could convert 1 and 0 to 1::boolean and 0::boolean (respectively) or you could change the boolean column to an integer in the schema section of the dump and then fix them up by hand inside PostgreSQL after the import.

If you have BLOBs in your SQLite then you'll want to adjust the schema to use bytea. You'll probably need to mix in some decode calls as well. Writing a quick'n'dirty copier in your favorite language might be easier than mangling the SQL if you a lot of BLOBs to deal with though.

As usual, if you have foreign keys then you'll probably want to look into set constraints all deferred to avoid insert ordering problems, placing the command inside the BEGIN/COMMIT pair.

Thanks to Nicolas Riley for the boolean, blob, and constraints notes.

If you have ` on your code, as generated by some SQLite3 clients, you need to remove them.

PostGRESQL also doesn't recognize unsigned columns, so you might want to drop that or add a custom-made constraint such as this:

CREATE TABLE tablename (
    ...
    unsigned_column_name integer CHECK (unsigned_column_name > 0)
);

While SQLite defaults null values to '', PostgreSQL requires them to be set as NULL.

The syntax in the SQLite dump file appears to be mostly compatible with PostgreSQL so you can patch a few things and feed it to psql. Importing a big pile of data through SQL INSERTs might take a while but it'll work.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Thank you! Thank you! This worked great for me. I had a program doing this migration at first but it was way too slow and causing other problems. Correction to your command. It should be -d, not -D. – DevX Jan 03 '11 at 06:54
  • One note if anyone else has this problem, SQLITE had my null values as ''. POSTGRESQL requires NULL. So fire up vim and do the command: %s/''/NULL/g to fix the dump file. – DevX Jan 03 '11 at 07:00
  • 4
    No, you want to keep the transaction to avoid some overhead. – Peter Eisentraut Jan 03 '11 at 07:07
  • I think Peter may be right. Although mu's answer is working right now (and is how I got the migration working), according to this FAQ it is even better to keep the BEGIN/COMMIT: http://www.sqlite.org/faq.html#q19 – DevX Jan 03 '11 at 07:18
  • Transactions: Presumably PostgreSQL will have to commit each INSERT to disk without the big transaction wrapping the whole thing but, with the transaction wrapper, it'll just queue up a big blob of stuff and do one big write at the end. Right? And thanks for the correction on the `psql` switches. – mu is too short Jan 03 '11 at 18:58
  • 3
    This works great. I would also note that if you need to migrate sqlite `datetime` columns, that you have to change them to `timestamp` for postgres. – Clay Feb 06 '12 at 15:56
  • @Clay: Thanks for that, I included that in the answer for posterity. – mu is too short Feb 06 '12 at 18:41
  • 4
    A few more issues I ran into: changing `BLOB` into `BYTEA` (http://stackoverflow.com/questions/3103242/), changing 0/1 for `BOOLEAN` columns to '0'/'1', and deferring constraints (`DEFERRABLE` / `SET CONSTRAINTS ALL DEFERRED`). – Nicholas Riley Feb 11 '12 at 00:47
  • 2
    @NicholasRiley: Thanks for that. I gave this up to a community wiki since it has turned into a group effort, fair is fair. – mu is too short Feb 11 '12 at 05:10
  • Also ``unsigned`` should be dropped. – Vladimir Nani Mar 19 '13 at 23:22
  • 2
    You can use to_timestamp() in the postgreSQL to convert a timestamp to a progreSQL timestamp – roeland Mar 23 '13 at 10:51
  • 1
    I'd note that the `integer` type in Sqlite defaults to 8 bytes, while the `integer` type in PostgreSQL is 4 bytes. To be safe, you may want to convert your `integer`-typed columns in Sqlite to the `bigint` type in PostgreSQL. – Jeff Hammerbacher Aug 30 '14 at 00:30
  • Also be aware that SQLite stores timestamps with zone by default, PostgreSQL does not. If you need to preserve time zone information, you can use `timestamptz` as column type. For more details see http://www.postgresql.org/docs/9.2/static/datatype-datetime.html – Sebastian Apr 16 '15 at 16:06
  • `ERROR: relation "sqlite_sequence" does not exist` at `DELETE FROM sqlite_sequence;` how to solve that? – Saravanabalagi Ramachandran Nov 02 '16 at 11:32
  • @ZekeDran Remove it from the SQL dump or use one of the solutions? I'm not even sure where that's coming from. – mu is too short Nov 02 '16 at 17:01
  • 1
    Should remove `DELETE FROM sqlite_sequence;` and replace `INSERT INTO "sqlite_sequence" VALUES('locations',7);` with `ALTER SEQUENCE locations_id_seq RESTART WITH 8;` and it works...! – Saravanabalagi Ramachandran Nov 03 '16 at 13:50
  • 2
    "You should be able to feed that dump file straight into psql" This doesn't work at all. The dumped sqlite sql file needs significant conversion, which your comments allude to but don't completely explain. Were you expecting OP to manually edit an gigabyte sized sql file? – Cerin Feb 01 '19 at 22:34
  • @Cerin The file as posted eight years ago (!) was pretty much a simple "clean up and dump into `psql`" job. In the general case that's not true so I gave up the answer as a community wiki (i.e. I get nothing for this and it is easy for the community as a whole to update and maintain and complain about and whatever). – mu is too short Feb 01 '19 at 23:04
  • As @SaravanabalagiRamachandran mentioned, Postgres sequences must be restarted, which can be quite tedious (I just did it :-) ). Plus a very interesting thing: I had a REAL column in my Sqlite DB containing percentages with 1 digit after the decimal point, e.g. `34.5`. Interestingly after migrating to Postgres these values became like `34.499999` or similar such things. Had to run the `ROUND(value, prec)` function on them to clean them up. – András Aszódi Jun 01 '20 at 15:51
  • 1
    the sqlite3 output has table names with capitals which are quoted in the create table statement, but not in the insert into statements, so inserts fail. Another little gotcha – Jan Feb 08 '22 at 14:17
85

pgloader

I came across this post when searching for a way to convert an SQLite dump to PostgreSQL. Even though this post has an accepted answer (and a good one at that +1), I think adding this is important.

I started looking into the solutions here and realized that I was looking for a more automated method. I looked up the wiki docs:

https://wiki.postgresql.org/wiki/Converting_from_other_Databases_to_PostgreSQL

and discovered pgloader. Pretty cool application and it's relatively easy to use. You can convert the flat SQLite file into a usable PostgreSQL database. I installed from the *.deb and created a command file like this in a test directory:

load database  
    from 'db.sqlite3'  
    into postgresql:///testdb 
       
with include drop, create tables, create indexes, reset sequences  
         
set work_mem to '16MB', maintenance_work_mem to '512 MB';

like the docs state. I then created a testdb with createdb:

createdb testdb

I ran the pgloader command like this:

pgloader command

and then connected to the new database:

psql testdb

After some queries to check the data, it appears it worked quite well. I know if I had tried to run one of these scripts or do the stepwise conversion mentioned herein, I would have spent much more time.

To prove the concept I dumped this testdb and imported into a development environment on a production server and the data transferred over nicely.

Community
  • 1
  • 1
nicorellius
  • 3,715
  • 4
  • 48
  • 79
  • 3
    Beware that (still supported) Ubuntu distributions might have outdated version - v2.x.y are already deprecated and don't actually work. v3.2.x might work but v3.2.3 is recommended. I have fetched v3.2.3 from bleeding edge and installed with _sudo dpkg -i <.deb file name>_, it had no problem with dependencies. – silpol May 21 '16 at 17:03
  • 1
    I concur with @silpol - be sure to download the latest stable release and install using your fav package manager; for the "command" file this is just a text file called 'command' with no extension name (i.e. no need for .txt at the end of the file name) you don't need to put the file name in angular brackets; i had to change the search_parth of the psql database in order to see my data; pgloader works well and saved me a great deal of hassle – BenKoshy Sep 29 '16 at 20:51
  • 1
    Yeah, I was struggling when I encountered this issue, and that tool made it so easy... Sometimes things just work out nicely, don't they? – nicorellius Apr 29 '17 at 15:04
  • Unfortunately it does not work on windows. – sveri Feb 17 '21 at 09:12
28

The sequel gem (a Ruby library) offers data copying across different databases: http://sequel.jeremyevans.net/rdoc/files/doc/bin_sequel_rdoc.html#label-Copy+Databases

First install Ruby, then install the gem by running gem install sequel.

In case of sqlite, it would be like this: sequel -C sqlite://db/production.sqlite3 postgres://user@localhost/db

lulalala
  • 17,572
  • 15
  • 110
  • 169
  • 4
    Awesome solution. Much easier than fiddling around with `pgloader`. – michaeldever Oct 26 '19 at 12:10
  • 2
    Absolutely, pgloader is messy, the GC seems to crash on huge databases: https://github.com/dimitri/pgloader/issues/962 – hasufell Oct 31 '19 at 08:39
  • Feel free to post your answer at https://stackoverflow.com/questions/6148421/how-to-convert-a-postgres-database-to-sqlite/64222183#64222183 where I copied your answer. Then ping me and I will revoke my answer if you want the reps for it. – Felix Oct 06 '20 at 08:38
  • @Felix thanks! You can take the credit. Could you swap the order of DB references around (since it wants PG to SQLite), oh and add one more "la" to my id. The answer may also be less helpful though since it requires them installing PG on dev machine, and at that point they'd just use PG for development. – lulalala Oct 07 '20 at 01:39
  • @lulalala Thanks. Did that. But about the reasoning I disagree. They could e.g. convert the db on the linux machine and then copy it over to the dev machine (as sqlite db file). But anyway all in all its a bad idea :) But sequel saved my ass here in a nasty situation. – Felix Oct 07 '20 at 07:25
  • sequel gem worked like a charm. – rahul Jul 03 '23 at 11:40
18

You can use a one liner, here is an example with the help of sed command:

sqlite3 mjsqlite.db .dump | sed -e 's/INTEGER PRIMARY KEY AUTOINCREMENT/SERIAL PRIMARY KEY/g;s/PRAGMA foreign_keys=OFF;//;s/unsigned big int/BIGINT/g;s/UNSIGNED BIG INT/BIGINT/g;s/BIG INT/BIGINT/g;s/UNSIGNED INT(10)/BIGINT/g;s/BOOLEAN/SMALLINT/g;s/boolean/SMALLINT/g;s/UNSIGNED BIG INT/INTEGER/g;s/INT(3)/INT2/g;s/DATETIME/TIMESTAMP/g' | psql mypqdb mypguser
develCuy
  • 576
  • 5
  • 14
  • there is no replace for LONG type, e.g. – yetanothercoder Jul 11 '15 at 23:12
  • 2
    one more item could be added `sed -e 's/DATETIME/TIMESTAMP/g'` – silpol May 17 '16 at 23:53
  • 1
    `sed -e 's/TINYINT(1)/SMALLINT/g'` -- and for a comparison of all the data types see https://stackoverflow.com/questions/1942586/comparison-of-database-column-types-in-mysql-postgresql-and-sqlite-cross-map – Purplejacket Jun 28 '17 at 22:07
  • I also had a problem with a SMALLINT that defaulted to 't' or 'f' in the sqlite. Obviously a boolean, but not familiar enough with either db system to recommend a safe fix. – labyrinth Apr 03 '18 at 20:44
  • This doesn't work. I get the error `ERROR: syntax error at or near "AUTOINCREMENT"` – Cerin Feb 01 '19 at 22:41
  • 2
    Replace `' | sed -e '` with `; ` :) – AstraSerg Dec 03 '19 at 08:32
  • get this error ERROR: column "time" is of type timestamp without time zone but expression is of type bigint LINE 1: ...o":null,"notNullable":false,"unsigned":true}]}]}',1673471190.. – dasfacc Jan 12 '23 at 21:13
  • My dump had a `"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,` at some point, so the casing was different to your `sed`s. Maybe all of them should be case insensitive by changing `/g` to `/gi`? Not sure about the implications – phil294 Jun 25 '23 at 20:09
17

I wrote a script to do the sqlite3 to postgres migration. It doesn't handle all the schema/data translations mentioned in https://stackoverflow.com/a/4581921/1303625, but it does what I needed it to do. Hopefully it will be a good starting point for others.

https://gist.github.com/2253099

Community
  • 1
  • 1
Earle Clubb
  • 451
  • 5
  • 7
  • 2
    This works well! I've forked the Gist and added some insights as a comment: https://gist.github.com/bittner/7368128 – Peterino Nov 15 '13 at 00:39
3

Try these steps...

Step 01: Dump sqlite db to json

python3 manage.py dumpdata > data.json

Step 02: Create tables without migration

python3 manage.py migrate --run-syncdb

Step 03: Open django shell. Then exclude contentype data

python3 manage.py shell
from django.contrib.contenttypes.models import ContentType
ContentType.objects.all().delete()
quit()

Step 04: Load Data

python3 manage.py loaddata data.json
Kusal Thiwanka
  • 151
  • 1
  • 4
2

pgloader work wonders on converting database in sqlite to postgresql.

Here's an example on converting a local sqlitedb to a remote PostgreSQL db:

pgloader sqlite.db postgresql://username:password@hostname/dbname

kouichi
  • 106
  • 8
  • 2
    Pgloader is terribly buggy and unreliable. It immediately crashes with the error `KABOOM! Control stack exhausted (no more space for function call frames).` – Cerin Feb 01 '19 at 22:43
1

I have tried editing/regexping the sqlite dump so PostgreSQL accepts it, it is tedious and prone to error.

What I got to work really fast:

First recreate the schema on PostgreSQL without any data, either editing the dump or if you were using an ORM you may be lucky and it talks to both back-ends (sqlalchemy, peewee, ...).

Then migrate the data using pandas. Suppose you have a table with a bool field (which is 0/1 in sqlite, but must be t/f in PostgreSQL)

def int_to_strbool(df, column):
    df = df.replace({column: 0}, 'f')
    df = df.replace({column: 1}, 't')
    return df

#def other_transform(df, column):
#...

conn = sqlite3.connect(db)
df = pd.read_sql(f'select * from {table_name}', conn)

df = int_to_strbool(df, bool_column_name)
#df = other_transform(df, other_column_name)

df.to_csv(table_name + '.csv'), sep=',', header=False, index=False)

This works like a charm, is easy to write, read and debug each function, unlike (for me) the regular expressions.

Now you can try to load the resulting csv with PostgreSQL (even graphically with the admin tool), with the only caveat that you must load the tables with foreign keys after you have loaded the tables with the corresponding source keys. I did not have the case of a circular dependency, I guess you can suspend temporarily the key checking if that is the case.

agomcas
  • 695
  • 5
  • 12