Default ID with Korma and Postgresql? - postgresql

I have the following schema:
CREATE TABLE IF NOT EXISTS art_pieces
(
-- Art Data
ID SERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
price INT NULL,
-- Relations
artists_id INT NULL
);
--;;
CREATE TABLE IF NOT EXISTS artists
(
-- Art Data
ID SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
This is the corresponding art-piece entity:
(defentity art-pieces
(table :art_pieces)
(entity-fields
:id
:title
:description
:price
:artists_id)
(belongs-to artists))
I'm wondering why the following returns PSQLException ERROR: null value in column "id" violates not-null constraint:
(create-piece {:title "The Silence of the Lambda"
:description "Something something java beans and a nice chianti"
:price 5000})
Shouldn't the ID SERIAL PRIMARY KEY field populate automatically? Is this something to do with Korma's interaction with PSQL?

INSERT INTO "art_pieces" ("description", "id", "price", "title") VALUES (?, NULL, ?, ?)
The problem here is that you try to insert NULL value into id column. Default value is inserted only if you omit the column or use DEFAULT keyword (instead of NULL).
To insert the next value of the sequence into the serial column, specify that the serial column should be assigned its default value. This can be done either by excluding the column from the list of columns in the INSERT statement, or through the use of the DEFAULT key word
PostgreSQL Serial Types
So you have to change the query to:
INSERT INTO "art_pieces" ("description", "id", "price", "title") VALUES (?, DEFAULT, ?, ?)
-- or
INSERT INTO "art_pieces" ("description", "price", "title") VALUES (?, ?, ?)
Another workaround (in case you don't have permissions to change the query) would be to add a trigger function that will replace NULL value in id column automatically:
CREATE OR REPLACE FUNCTION tf_art_pieces_bi() RETURNS trigger AS
$BODY$
BEGIN
-- if insert NULL value into "id" column
IF TG_OP = 'INSERT' AND new.id IS NULL THEN
-- set "id" to the next sequence value
new.id = nextval('art_pieces_id_seq');
END IF;
RETURN new;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER art_pieces_bi
BEFORE INSERT
ON art_pieces
FOR EACH ROW EXECUTE PROCEDURE tf_art_pieces_bi();

Related

Inserting row types with a serial primary key column in PostgreSQL

I want a simple function that takes in all record columns and creates them if it does not exist. The table here is just a dummy, my point is more about how the id (SERIAL) column must be handled when inserting data using row types.
CREATE TABLE
some_table (
"id" SERIAL PRIMARY KEY,
"some_value" VARCHAR NOT NULL
);
declare
r_some_table_rt some_table%rowtype;
begin
select *
into r_some_table_rt
from some_table
where some_column = 'Some value';
if r_some_table_rt.id is null then
-- Tried without assigning r_some_table_rt.id
-- r_some_table_rt.id := default; -- Tried this
-- r_some_table_rt.id := currval(pg_get_serial_sequence('some_table', 'id')); -- And this
r_some_table_rt.some_column := 'Some other value';
insert into some_table
values (r_some_table_rt.*)
returning id into r_some_table_rt.id;
end if;
return r_some_table_rt.id;
end;
PostgresError: null value in column "id" of relation "some_table" violates not-null constraint
I tried the following
Using a normal insert works perfectly fine: insert into some_table (some_column) values ('Some value');.
r_some_table.id := default; -> PostgresError: DEFAULT is not allowed in this context
r_some_table.id := currval(pg_get_serial_sequence('some_table', 'id')); -> PostgresError: currval of sequence "some_table_id_seq" is not yet defined in this session
This worked for me:
r_some_table_rt.id := nextval(pg_get_serial_sequence('some_table', 'id'));

POSTGRESQL: Insert into 2 tables in the same query, using ID generated from first insert in the second insert

Tables
CREATE TABLE "assets"
(
"uuid" uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
"symbol" text NOT NULL,
"name" text NOT NULL,
"decimal" numeric DEFAULT 18,
"img_small" text DEFAULT '',
"img_large" text DEFAULT '',
"gecko_id" text
)
CREATE TABLE "chain_asset"
(
"uuid" uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
"chain_uuid" uuid NOT NULL,
"asset_uuid" uuid NOT NULL,
"contract" text NOT NULL,
"chain_contract" text NOT NULL,
)
My Query
`WITH x AS (
INSERT INTO assets (symbol, name, decimal) VALUES ($1, $2, $3) RETURNING uuid
)
INSERT INTO chain_asset (chain_uuid, asset_uuid, contract, chain_contract) VALUES ($4, x.uuid ,$5, $6)
`,
I'm using pg promise. I would like to insert into 2 tables using the same query. I need the uuid from the insert into the asset table in order to insert into the chain_asset table. using x.uuid is not working, I'm getting the following error
Error: missing FROM-clause entry for table "x"
You don't need the VALUES(), Just SELECT the literals in a select-clause:
WITH x AS (
INSERT INTO assets(symbol,name,decimal) VALUES ($1, $2, $3)
RETURNING uuid
)
INSERT INTO chain_asset(chain_uuid,asset_uuid,contract,chain_contract)
SELECT $4,x.uuid,$5,$6
FROM x
;

Running A FOREACH after a CTE in a PostgreSQL procedure?

I have a PLPGSQL procedure that:
A) Inserts an employee into an Employee table.
B) Also insert the generated serial eid into another Manager table.
C) Finally, the procedure also dictates an array of course_areas that I would like to insert in a specialize table.
Procedure where C and D array are the course_areas
CALL add_employee('Athena', '22222222', 'athena#outlook.com', '2012-12-12', '111', 'instructor', 300.0, NULL,
'{C,D}'::text[]);
Employees
CREATE TABLE Employees (
eid SERIAL PRIMARY KEY,
name TEXT NOT NULL,
phone TEXT NOT NULL,
email TEXT NOT NULL,
join_date DATE NOT NULL,
address TEXT NOT NULL,
depart_date DATE
);
Managers
CREATE TABLE Managers (
eid INT PRIMARY KEY,
monthly_salary DECIMAL(10,2) NOT NULL,
FOREIGN KEY (eid) REFERENCES Employees(eid)
ON DELETE CASCADE
);
Specializes
CREATE TABLE Specializes (
eid INT NOT NULL,
name TEXT NOT NULL,
PRIMARY KEY (eid, name),
FOREIGN KEY (eid) REFERENCES Employees(eid)
on DELETE CASCADE,
FOREIGN KEY (name) REFERENCES Course_areas(name)
on DELETE CASCADE
);
procedure.sql
CREATE OR REPLACE PROCEDURE add_employee (name TEXT, phone TEXT, email TEXT, joinDate DATE, address TEXT, category TEXT, monthlySalary DECIMAL(10,2) default NULL, hourlySalary DECIMAL(10,2) default NULL, courseAreas TEXT[] default NULL)
...
WITH FIRST AS (
INSERT INTO Employees(
name, phone, email, join_date, address
)
VALUES
(
name, phone, email, joinDate, address
) RETURNING eid
),
SECOND AS (
INSERT INTO Full_time_instructors(eid, monthly_salary)
SELECT
eid,
monthlySalary
FROM
ROWS RETURNING eid
)
FOREACH area IN ARRAY courseAreas
LOOP
RAISE NOTICE '%', area; -- should print "C" and "D"
END LOOP;
Error
ERROR: syntax error at or near "FOREACH"
LINE 27: FOREACH area IN ARRAY courseAreas
I can get A) and B) to work.
How can I use FOREACH to iterate through my courseAreas that I pass to the procedure such that I can insert each course area and the eid into a Specialize table?
The FOREACH is a PLPGSQL control structure whereas the CTE is a SQL feature. You can't mix these. Instead of using CTE you can simply perform the first statement, using the RETURNING clause to retrieve the eid to a local variable. You can then use this variable with the subsequent statement and inside your FOREACH loop.
This question gives an overview of getting the new serial value using returning: Get default serial value after INSERT inside PL/pgSQL.

I'm having an issue with this code when I try to input values into the transactions table

So I'm setting up a schema in which I can input transactions of a journal entry independent of each other but also that rely on each other (mainly to ensure that debits = credits). I set up the tables, function, and trigger. Then, when I try to input values into the transactions table, I get the error below. I'm doing all of this in pgAdmin4.
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
id UUID PRIMARY KEY,
test_date DATE NOT NULL,
balance NUMERIC(12,2)
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_biut()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
EXECUTE 'INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS ''balance''
FROM
transactions
GROUP BY
entry_id;';
END;
$$;
CREATE TRIGGER transactions_biut
BEFORE INSERT OR UPDATE ON transactions
FOR EACH ROW EXECUTE PROCEDURE transactions_biut();
INSERT INTO transactions (
entry_id,
post_date,
account_id,
description,
amount
)
VALUES
(
'1',
'2019-10-01',
'101',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
-200.00
),
(
'1',
'2019-10-01',
'505',
'MISC DEBIT: PAID FOR FACEBOOK ADS',
200.00
);
After I execute this input, I get the following error:
ERROR: column "id" of relation "entries" does not exist
LINE 1: INSERT INTO entries (id,test_date,balance)
^
QUERY: INSERT INTO entries (id,test_date,balance)
SELECT
entry_id,
post_date,
SUM(amount) AS "balance"
FROM
transactions
GROUP BY
entry_id;
CONTEXT: PL/pgSQL function transactions_biut() line 2 at EXECUTE
SQL state: 42703
There are a few problems here:
You're not returning anything from the trigger function => should probably be return NEW or return OLD since you're not modifying anything
Since you're executing the trigger before each row, it's bound to fail for any transaction that isn't 0 => maybe you want a deferred constraint trigger?
You're not grouping by post_date, so your select should fail
You've defined entry_id as INTEGER, but entries.id is of type UUID
Also note that this isn't really going to scale (you're summing up all transactions of all days, so this will get slower and slower...)
#chirs I was able to figure out how to create a functioning solution using statement-level triggers:
CREATE TABLE transactions (
transactions_id UUID PRIMARY KEY DEFAULT uuid_generate_v1(),
entry_id INTEGER NOT NULL,
post_date DATE NOT NULL,
account_id INTEGER NOT NULL,
contact_id INTEGER NULL,
description TEXT NOT NULL,
reference_id UUID NULL,
document_id UUID NULL,
amount NUMERIC(12,2) NOT NULL
);
CREATE TABLE entries (
entry_id INTEGER PRIMARY KEY,
post_date DATE NOT NULL,
balance NUMERIC(12,2),
CHECK (balance = 0.00)
);
CREATE OR REPLACE FUNCTION transactions_entries() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO entries
SELECT o.entry_id, o.post_date, SUM(o.amount) FROM old_table o GROUP BY o.entry_id, o.post_date;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO entries
SELECT o.entry_id, n.post_date, SUM(n.amount) FROM new_table n, old_table o GROUP BY o.entry_id, n.post_date;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO entries
SELECT n.entry_id,n.post_date, SUM(n.amount) FROM new_table n GROUP BY n.entry_id, n.post_date;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER transactions_ins
AFTER INSERT ON transactions
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_upd
AFTER UPDATE ON transactions
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
CREATE TRIGGER transactions_del
AFTER DELETE ON transactions
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT EXECUTE PROCEDURE transactions_entries();
Any thoughts on optimization?

Need foreign key as array

CREATE TABLE test ( id int PRIMARY KEY , name );
CREATE TABLE test1 ( id integer[] REFERENCES test , rollid int );
ERROR: foreign key constraint "test3_id_fkey" cannot be implemented
DETAIL: Key columns "id" and "id" are of incompatible types: integer[] and integer.
after that I try to another way also
CREATE TABLE test1 ( id integer[] , rollid int);
ALTER TABLE test1 ADD CONSTRAINT foreignkeyarray FOREIGN KEY (id) REFERENCES test;
ERROR: foreign key constraint "fkarray" cannot be implemented
DETAIL: Key columns "id" and "id" are of incompatible types: integer[] and integer.
so I try create a foreign key array means it say error. please tell me anyone?
postgresql version is 9.1.
What you're trying to do simply can't be done. At all. No ifs, no buts.
Create a new table, test1_test, containing two fields, test1_id, test_id. Put the foreign keys as needed on that one, and make test1's id an integer.
Using arrays with foreign element keys is usually a sign of incorrect design. You need to do separate table with one to many relationship.
But technically it is possible. Example of checking array values without triggers. One reusable function with paramethers and dynamic sql. Tested on PostgreSQL 10.5
create schema if not exists test;
CREATE OR REPLACE FUNCTION test.check_foreign_key_array(data anyarray, ref_schema text, ref_table text, ref_column text)
RETURNS BOOL
RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql
AS
$body$
DECLARE
fake_id text;
sql text default format($$
select id::text
from unnest($1) as x(id)
where id is not null
and id not in (select %3$I
from %1$I.%2$I
where %3$I = any($1))
limit 1;
$$, ref_schema, ref_table, ref_column);
BEGIN
EXECUTE sql USING data INTO fake_id;
IF (fake_id IS NOT NULL) THEN
RAISE NOTICE 'Array element value % does not exist in column %.%.%', fake_id, ref_schema, ref_table, ref_column;
RETURN false;
END IF;
RETURN true;
END
$body$;
drop table if exists test.t1, test.t2;
create table test.t1 (
id integer generated by default as identity primary key
);
create table test.t2 (
id integer generated by default as identity primary key,
t1_ids integer[] not null check (test.check_foreign_key_array(t1_ids, 'test', 't1', 'id'))
);
insert into test.t1 (id) values (default), (default), (default); --ok
insert into test.t2 (id, t1_ids) values (default, array[1,2,3]); --ok
insert into test.t2 (id, t1_ids) values (default, array[1,2,3,555]); --error
If you are able to put there just values from test.id, then you can try this:
CREATE OR REPLACE FUNCTION test_trigger() RETURNS trigger
LANGUAGE plpgsql AS $BODY$
DECLARE
val integer;
BEGIN
SELECT id INTO val
FROM (
SELECT UNNEST(id) AS id
FROM test1
) AS q
WHERE id = OLD.id;
IF val IS NULL THEN RETURN OLD;
ELSE
RAISE 'Integrity Constraint Violation: ID "%" in Test1', val USING ERRCODE = '23000';
RETURN NULL;
END IF;
END; $BODY$;
-- DROP TRIGGER test_delete_trigger ON test;
CREATE TRIGGER test_delete_trigger BEFORE DELETE OR UPDATE OF id ON test
FOR EACH ROW EXECUTE PROCEDURE test_trigger();