plpgsql: change order of update - postgresql

I've the following table:
CREATE TABLE api.capabilities (
id uuid NOT NULL,
historycounter int8 NOT NULL DEFAULT 0,
url jsonb NOT NULL,
checkservice bool NOT NULL DEFAULT false,
added timestamptz NOT NULL,
modified timestamptz NULL,
deleted timestamptz NULL,
adduser varchar(40) NOT NULL DEFAULT 'pgsql'::character varying,
lastuser varchar(40) NULL,
CONSTRAINT id_histcount PRIMARY KEY (id, historycounter)
);
With the following content:
|80af3ff3-2dc1-434b-ad3c-490d8b4a7949|1|{"host": "dev.33wessling824.net", "protocol": "http"}|false|2022-07-12 18:35:17.465 +0200|||pgsql||
|80af3ff3-2dc1-434b-ad3c-490d8b4a7949|0|{"host": "dev.33wessling824.net", "protocol": "http"}|true|2022-07-12 18:35:17.465 +0200|2022-07-14 11:46:50.073 +0200||pgsql||
I'd like to run this update statement:
Update api.capabilities set historycounter = historycounter + 1 where id = '80af3ff3-2dc1-434b-ad3c-490d8b4a7949';
Trying this results in an error duplicate key value violates unique constraint "id_histcount"
Because first the dataset with historycounter = 0 is updated, I do get this error. How to change my code to first update the dataset with the biggest historycounter?
Thanks in advance! Achim

If you are totally sure that your data will not violate primary key after your transaction. First, alter table constraint. This is safe:
ALTER TABLE api.capabilities
ALTER CONSTRAINT id_histcount DEFERRABLE INITIALLY IMMEDIATE;
Then in your transaction:
begin transaction;
set constraints all deferred;
Update api.capabilities set historycounter = historycounter + 1 where id = '80af3ff3-2dc1-434b-ad3c-490d8b4a7949';
--your other operations
commit transaction;
Source: https://www.postgresql.org/docs/current/sql-set-constraints.html

Related

How to reference hypertables properly using foreign key constraints in PostgreSQL?

#Error description:
It's possible to create a table that has a foreign key into a hypertable provided the foreign key is defined when the table is created
#To Reproduce, there are next tables:
CREATE TABLE ids (
measurement_id int DEFAULT 0,
description text DEFAULT 0,
m_id bigserial NOT NULL,
service_id int DEFAULT NULL,
time bigint NOT NULL DEFAULT cast((EXTRACT(EPOCH FROM now() AT TIME ZONE 'UTC') * 1000) as bigint),
user_id int DEFAULT NULL,
end_time DOUBLE PRECISION DEFAULT 0,
start_time int NOT NULL DEFAULT 0
);
CREATE INDEX ON ids (time DESC, user_id);
CREATE INDEX ON ids (time DESC, service_id);
SELECT create_hypertable('ids', 'start_time', chunk_time_interval => 604800016);
---------
CREATE TABLE IF NOT EXISTS metrics (
id bigserial NOT NULL,
duration real DEFAULT NULL,
metric integer DEFAULT 0,
m_id bigint NOT NULL,
time bigint NOT NULL DEFAULT 0
);
ALTER TABLE metrics ADD PRIMARY KEY (time, m_id);
CREATE INDEX ON metrics (time DESC);
CREATE INDEX ON metrics (time DESC, measurement );
CREATE INDEX ON metrics (time DESC, m_id );
grant all privileges on ids, metrics to your_db_user;
SELECT create_hypertable('metrics', 'time' , chunk_time_interval => 604800016);
SELECT table_catalog, table_schema, table_name, privilege_type FROM information_schema.table_privileges WHERE grantee = 'your_db_user';
---------
DROP TABLE IF EXISTS resource;
CREATE TABLE resource(
id int NOT NULL,
cpu text DEFAULT 0,
storing text DEFAULT 0,
memory text DEFAULT 0
);
ALTER TABLE resource ADD PRIMARY KEY (id);
CREATE SEQUENCE resource_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 2147483647
START 1
CACHE 1;
ALTER TABLE resource_id_seq
OWNER TO your_db_user;
ALTER TABLE resource ALTER COLUMN id SET DEFAULT nextval('resource_id_seq'::regclass);
---------
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
DROP TABLE IF EXISTS ns;
CREATE TABLE ns(
id bigint NOT NULL,
uuid uuid NOT NULL DEFAULT uuid_generate_v4 (),
availability double precision,
faultTolerance boolean,
activated boolean,
UNIQUE (id, uuid),
PRIMARY KEY(id),
CONSTRAINT fk_resource
FOREIGN KEY(id)
REFERENCES resource(id)
ON DELETE CASCADE
);
CREATE SEQUENCE ns_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1;
ALTER TABLE ns_id_seq
OWNER TO your_db_user;
ALTER TABLE ns ALTER COLUMN id SET DEFAULT nextval('ns_id_seq'::regclass);
---------
DROP TABLE IF EXISTS authentication;
CREATE TABLE authentication(
id integer NOT NULL,
username character varying(255) NOT NULL,
password character varying(255) NOT NULL,
host character varying(255) NOT NULL,
port character varying(10) NOT NULL,
PRIMARY KEY(id)
);
CREATE SEQUENCE auth_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 2147483647
START 1
CACHE 1;
ALTER TABLE auth_id_seq
OWNER TO your_db_user;
ALTER TABLE authentication ALTER COLUMN id SET DEFAULT nextval('auth_id_seq'::regclass);
---------
DROP TABLE IF EXISTS job;
CREATE TABLE job(
id int NOT NULL,
interval integer NOT NULL,
auth_id integer REFERENCES authentication (id),
ns_id integer REFERENCES ns (id),
UNIQUE (auth_id, ns_id),
PRIMARY KEY(id)
);
ALTER TABLE job
ADD CONSTRAINT fk_auth_id
FOREIGN KEY (id) REFERENCES authentication (id)
ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE job
ADD CONSTRAINT fk_ns_id
FOREIGN KEY (id) REFERENCES ns (id)
ON DELETE CASCADE
DEFERRABLE INITIALLY DEFERRED;
CREATE SEQUENCE job_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 2147483647
START 1
CACHE 1;
ALTER TABLE job_id_seq
OWNER TO your_db_user;
ALTER TABLE job ALTER COLUMN id SET DEFAULT nextval('job_id_seq'::regclass);
---------
DROP TABLE IF EXISTS job_metric;
CREATE TABLE job_metric (
id int NOT NULL,
j_id int NOT NULL REFERENCES job (id),
mj_id bigint NOT NULL,
jm_time bigint NOT NULL
);
CREATE INDEX ON job_metric (jm_time DESC);
CREATE INDEX ON job_metric (jm_time DESC, id);
CREATE INDEX ON job_metric (jm_time DESC, mj_id);
ALTER TABLE job_metric ADD PRIMARY KEY (jm_time, id);
grant all privileges on job_metric to your_db_user;
SELECT create_hypertable('job_metric', 'jm_time' , chunk_time_interval => 604800016);
CREATE SEQUENCE mjob_metric_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 2147483647
START 1
CACHE 1;
ALTER TABLE mjob_metric_id_seq
OWNER TO your_db_user;
ALTER TABLE job_metric ALTER COLUMN id SET DEFAULT nextval('mjob_metric_id_seq'::regclass);
---------
After creating the tables, I have used the solution proposed by #Laurenz in a database with PostgreSQL 12.6 using the extension of timescaledb 1.7.5 as follows:
#To fill the table with the appropriate values:
UPDATE job_metric AS jm_point
SET jm_time = qm.time
FROM metrics AS qm
WHERE qm.m_id = jm_point.mj_id;
#Then set it NOT NULL:
ALTER TABLE job_metric ALTER jm_time SET NOT NULL;
#To define your foreign key:
ALTER TABLE job_metric
ADD FOREIGN KEY (mj_id, jm_time)
REFERENCES metrics (time, m_id) MATCH FULL;
#Response of the last reference table to enable foreign key: Query returned successfully in 40 msec.
Expected behavior:
The idea is to use the table job_metric in an even many-to-many relationship to access the information of job and metrics tables.
Actual behavior and error:
Tables are created and FKs were created but cannot be used when data is inserted at job_metric as is detailed in the following:
INSERT INTO job_metric (j_id, mj_id, jm_time)
VALUES(13, 185063, 1621957192266);
ERROR: foreign keys to hypertables are not supported CONTEXT: SQL
statement " ALTER TABLE _timescaledb_internal._hyper_5_5_chunk ADD
CONSTRAINT "5_13_job_metric_j_id_mj_id_jm_time_fkey" FOREIGN KEY
(j_id, mj_id, jm_time) REFERENCES qmetrics("time", m_id) MATCH FULL "
PL/pgSQL function
_timescaledb_internal.chunk_constraint_add_table_constraint(_timescaledb_catalog.chunk_constraint)
line 42 at EXECUTE SQL state: 0A000
***According to https://docs.timescale.com/timescaledb/latest/overview/limitations/##distributed-hypertable-limitations, it looks like the above error is part of the hypertable limitations:
Foreign key constraints referencing a hypertable are not supported.
#Request:
Given the above information and errors, does anyone know any solution at the DB level to establish the relationships (many-to-many or one-to-many) using timescaledb extension and mainly hypertables?
Actually, I have obtained similar of above error when I had attempted to create many-to-many relation among the tables metrics and job_metric using the Django Rest Framework:
class Job_Metrics(models.Model):
job = models.OneToOneField(Job, on_delete=models.CASCADE)
metrics = models.ManyToManyField(Metrics)
time = models.IntegerField(default=0)
Running the application metrics pointing out directly metrics_db:
$ python3 manage.py migrate metrics --database=metrics_db
Operations to perform: Apply all migrations: metrics Running migrations: Applying
metrics.0002_job...Traceback (most recent call last): File
"/var/myproject/myprojectenv/lib/python3.8/site-packages/django/db/backends/utils.py",
line 84, in _execute return self.cursor.execute(sql, params)
psycopg2.errors.FeatureNotSupported: foreign keys to hypertables are
not supported
If someone knows a solution or has an idea to deal with the above error at the REST API level, please could you share your idea with the aim to access data associated tables (metrics and jobs) and modify them together when is required to delete e.g., a job_metric. So far, using hypertables amendments of timescaledb extension seems to be not a viable solution.

Alter table to add 'partition by range' in postgres

I want to alter table with adding partition by range with archivedate.
CREATE TABLE transactions(
id UUID not null ,
txn_id UUID NOT NULL,
orderId UUID NOT NULL,
inserttstmp timestamp not NULL
archivedate timestamp NULL
)
usually, whenever i'm creating new table with partition with below script. But now want to do same for existing tables.
CREATE TABLE attachment(
id UUID not null,
txn_id UUID NOT NULL,
attachment_id UUID NOT NULL,
inserttstmp timestamp not NULL
archivedate timestamp NULL
)PARTITION BY RANGE (archivedate);
CREATE TABLE ins_txn_attachment_live PARTITION OF ins_txn_attachment DEFAULT;
ALTER TABLE ins_txn_attachment_live ADD CHECK (archivedate is null);
ALTER TABLE ins_txn_attachment_live ADD CONSTRAINT PK_INS_TXN_ATTACHMENT_EVENT_Live unique (id);
CREATE UNIQUE NONCLUSTERED INDEX PK_Ins_TXN_Attachment_ID_ArchiveData ON ins_txn_attachment (id,archivedate);
Thanks,
Jagadeesh

with migration to fill slug with default value

In my Laravel 5.6/PostgreSQL 10.5 application
I have 2 tables :
CREATE TABLE public.rt_genres (
id serial NOT NULL,
published bool NULL DEFAULT false,
created_at timestamp NOT NULL DEFAULT now(),
updated_at timestamp NULL,
CONSTRAINT rt_genres_pkey PRIMARY KEY (id)
)...
CREATE TABLE public.rt_genre_translations (
id serial NOT NULL,
genre_id int4 NOT NULL,
"name" varchar(100) NOT NULL,
description text NOT NULL,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
locale varchar(2) NOT NULL,
CONSTRAINT genre_translations_genre_id_locale_unique UNIQUE (genre_id, locale),
CONSTRAINT rt_genre_translations_pkey PRIMARY KEY (id),
CONSTRAINT genre_translations_genre_id_foreign FOREIGN KEY (genre_id) REFERENCES rt_genres(id) ON DELETE CASCADE
)
I need to add slug field in first rt_genres table
with migration rule:
$table->string('slug', 105)->unique();
and got error :
: Unique violation: 7 ERROR: could not create unique index "genres_slug_unique"
DETAIL: Key (slug)=(id) is duplicated.")
1)If there is a way to assign in migration some unique default value, like = id
->default('rt_genres.id')
?
2) That would be cool to assign to slug value from public.rt_genre_translations.name as I use
"cviebrock/eloquent-sluggable": "^4.5" plugin in my app ? Can I do it ?
Thank you!
You can only get the default value from a different column with a trigger (SO answer).
You can make the column nullable:
$table->string('slug', 105)->nullable()->unique();
Or you create the column, insert unique values and then create the index:
Schema::table('rt_genres', function($table) {
$table->string('slug', 105);
});
DB::table('rt_genres')->update(['slug' => DB::raw('"id"')]);
Schema::table('rt_genres', function($table) {
$table->unique('slug');
});

I want to insert rows into history tables from actual tables (parent and child) on insertion of row into actual tables using postgresql trigger

For Example:
Consider my original tables are:
CREATE TABLE employees(
id serial primary key,
first_name varchar(40) NOT NULL,
last_name varchar(40) NOT NULL
);
CREATE TABLE employees_detail
(
eid integer NOT NULL,
id integer,
first_name character varying(40),
last_name character varying(40),
CONSTRAINT employees_detail_pkey PRIMARY KEY (eid),
CONSTRAINT employees_detail_id_fkey FOREIGN KEY (id)
REFERENCES employees (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
The above tables have foreign key relation. So when I insert a row into both the tables, at the same time I want to insert them into history tables.
consider history tables are:
CREATE TABLE employee_audits (
id serial primary key,
employee_id int4 NOT NULL,
last_name varchar(40) NOT NULL,
changed_on timestamp(6) NOT NULL
)
CREATE TABLE employee_audits_detail
(
eid integer NOT NULL,
id integer,
last_name character varying(40),
changed_on timestamp(6) NOT NULL,
CONSTRAINT employee_audits_detail_pkey PRIMARY KEY (eid),
CONSTRAINT employee_audits_detail_fkey FOREIGN KEY (id)
REFERENCES employee_audits (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
I have created below trigger to insert into original parent table(employees) to history parent table(employee_audits). My trigger is as follows:
CREATE OR REPLACE FUNCTION log_last_name_changes()
RETURNS trigger AS
$BODY$
BEGIN
IF NEW.last_name <> OLD.last_name THEN
INSERT INTO employee_audits(employee_id,last_name,changed_on)
VALUES(OLD.id,OLD.last_name,now());
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql
CREATE TRIGGER last_name_changes
BEFORE UPDATE
ON employees
FOR EACH ROW
EXECUTE PROCEDURE log_last_name_changes();
Same way I want to insert original child table rows into history child table.

Postgres before insert trigger using sequence from another table

Using Postgres, what I would like to achieve is to be able to have many different instrument types, with corresponding [TYPE].instrument tables, which all have a unique ID in the table, but also reference a unique ID in the instrument.master table. I have the following:
create schema instrument
CREATE TABLE instrument.type (
id smallserial NOT NULL,
name text not null,
code text not null,
CONSTRAINT pk_instrument_type PRIMARY KEY (id)
);
ALTER TABLE instrument.type ADD CONSTRAINT unq_instrument_type_code UNIQUE(code);
ALTER TABLE instrument.type ADD CONSTRAINT unq_instrument_type_name UNIQUE(name);
insert into instrument.type (name, code) values ('futures', 'f');
CREATE TABLE instrument.master (
id serial NOT NULL,
type smallint not null references instrument.type (id),
timestamp timestamp with time zone not null,
CONSTRAINT pk_instrument_master PRIMARY KEY (id)
);
CREATE TABLE futures.definition (
id smallserial NOT NULL,
code text not null,
CONSTRAINT pk_futures_definition PRIMARY KEY (id)
);
ALTER TABLE futures.definition ADD CONSTRAINT unq_futures_definition_code UNIQUE(code);
insert into futures.definition (code) values ('ED');
CREATE TABLE futures.instrument (
id smallserial NOT NULL,
master serial not null references instrument.master (id),
definition smallint not null references futures.definition (id),
month smallint not null,
year smallint not null,
CONSTRAINT pk_futures_instrument PRIMARY KEY (id),
check (month >= 1),
check (month <= 12),
check (year >= 1900)
);
ALTER TABLE futures.instrument ADD CONSTRAINT unq_futures_instrument UNIQUE(definition, month, year);
CREATE OR REPLACE FUNCTION trigger_master_futures()
RETURNS trigger AS
$BODY$
BEGIN
insert into instrument.master (type, timestamp)
select id, current_timestamp from instrument.type where code = 'f';
NEW.master := currval('instrument.master_id_seq');
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
create trigger trg_futures_instrument before insert on futures.instrument
for each row
execute procedure trigger_master_futures();
I then test with:
insert into futures.instrument (definition, month, year)
select id, 3, 2015 from futures.definition where code = 'ED';
Everything works almost as I would like it to. The only issue is that somehow, instrument.master.id ends up being one more than futures.instrument.master. I am not sure what I need to do to achieve the behavior I want, which is that whenever an entry is inserted into futures.instrument, an entry should be inserted into instrument.master, and the id entry of the latter should be inserted into the master entry of the former. I actually think it should have failed since the foreign key relationship is violated somehow.
As it turns out, everything was correct. The issue was that in futures.instrument, the type of the master column is serial, and it should have been int.