How to create a QGIS processing script that adds a sequence to a unique identifier column in PostGIS?
Provided your SQL statement produces valid results, the scripts below should do what you are after. Unfortunately I have nothing at hand to test this, but you could try and give feedback.
I tried to comment it for convenience, basically the script carries out three steps:
- obtain database connection parameters for the selected layer (should be postgres)
- fill the connection parameters in the sql statement string
- execute the sql statement
Note the protocol output of the script.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#--------- define Interface
##[my_scripts]=group
##Add Serial to PostgreSQL Table=name
##Postgres_Table=vector
##Unique_identifier_column=string replace_this_with_your_uic
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import *
from qgis.core import *
from qgis.gui import *
from qgis.utils import *
import psycopg2
#get the parameters for the tpg table into a directory
#get the table
pg_table = processing.getObject(Postgres_Table)
#create empty dictionary for key/value pairs of the tables connection parameters
db_params = {}
db_params['uic'] = Unique_identifier_column
#iterate over connection string
progress.setInfo(20*'-' + ' Connection parameters')
for param in pg_table.dataProvider().dataSourceUri().split(' '):
key_val = param.split('=')
progress.setInfo(str(key_val))
try:
#set key/value pair
db_params[key_val[0]] = key_val[1]
except:
pass
#generate the sql statement string
#the text in round brackets are the keys from the db_params dictionary created above
#the values belonging to the keys are inserted into the string
progress.setInfo(20*'-' + ' SQL statement')
sql = """CREATE SEQUENCE %(table)s_%(uic)s_seq OWNED BY %(table)s.%(uic)s;
SELECT SETVAL(%(table)s_%(uic)s_seq, (SELECT MAX(%(uic)s) FROM %(table)s));
ALTER TABLE %(table)s
ALTER COLUMN %(uic)s SET DEFAULT nextval(%(table)s_%(uic)s_seq::regclass);""" % db_params
#remove double quotes
sql = sql.replace('"','')
progress.setInfo(sql)
#make connection string
constr = """dbname=%(dbname)s host=%(host)s port=%(port)s user=%(user)s password=%(password)s""" % db_params
progress.setInfo(20*'-' + ' DB Connection string')
progress.setInfo(constr)
#make db connection
con = psycopg2.connect(constr)
cur = con.cursor()
#execute the above created sql statement
progress.setInfo(20*'-' + ' Executing SQL statement ...')
cur.execute(sql)
progress.setInfo(20*'-' + ' ... done.')
There seems to be a similar plugin already (although it creates a new unique ID field for you, rather than creating a sequence.)
This assumes you already have a unique id field (this doesn't need to be numeric), but want a simple numeric ID instead (1,2,3..)
In Processing toolbox, go to Scripts > Tools > Get Scripts from Online...
Expand "Not installed" and choose "EquivalentNumField" . Remember to click the checkbox before clicking on OK. That caught me out... ;-)
To find it quickly, type "Equiv" in the processing search bar, and you should be able to double-click it from there.
Here's an example. These woods had a unique field (osm_id) but the plugin has added a NUM_FIELD with simple numeric values instead
It is worth noting, that python module psycopg2
doesn't seem to automatically COMMIT
a transaction (as other clients like QGIS DB Manager or pgAdmin do), therefore the COMMIT
statement has to be part of the sql
string in the script.
This does not matter with SELECT
statements because in theses cases a COMMIT
is obviously carried out when getting results via cur.fetchall()
.
This is a reworked version of the script from my answer above:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#--------- define Interface
##[my_scripts]=group
##Add Serial to PostgreSQL Table=name
##Postgres_Table=vector
##Unique_identifier_column=field Postgres_Table
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import *
from qgis.core import *
from qgis.gui import *
from qgis.utils import *
import psycopg2
#get the parameters for the tpg table into a directory
#get the table
pg_table = processing.getObject(Postgres_Table)
#create empty dictionary for key/value pairs of the tables connection parameters
db_params = {}
db_params['uic'] = Unique_identifier_column
#iterate over connection string
progress.setInfo(20*'-' + ' Connection parameters')
for param in pg_table.dataProvider().dataSourceUri().split(' '):
key_val = param.split('=')
progress.setInfo(str(key_val))
try:
#set key/value pair
db_params[key_val[0]] = key_val[1]
except:
pass
#generate the sql statement string
#the text in round brackets are the keys from the db_params dictionary created above
#the values belonging to the keys are inserted into the string
progress.setInfo(20*'-' + ' SQL statement')
sql = """CREATE SEQUENCE %(table)s_%(uic)s_seq OWNED BY %(table)s.%(uic)s;
SELECT SETVAL('%(table)s_%(uic)s_seq', (SELECT MAX(%(uic)s) FROM %(table)s));
ALTER TABLE %(table)s ALTER COLUMN %(uic)s SET DEFAULT nextval('%(table)s_%(uic)s_seq'::regclass);
COMMIT;""" % db_params
#remove double quotes
sql = sql.replace('"','')
progress.setInfo(sql)
#make connection string
constr = """dbname=%(dbname)s host=%(host)s port=%(port)s user=%(user)s password=%(password)s""" % db_params
progress.setInfo(20*'-' + ' DB Connection string')
progress.setInfo(constr)
#make db connection
con = psycopg2.connect(constr)
cur = con.cursor()
#execute the above created sql statement
progress.setInfo(20*'-' + ' Executing SQL statement ...')
cur.execute(sql)
progress.setInfo(20*'-' + ' ... done.')