Want Not Null and Default to False if Supplied value is NULL - postgresql

I want to achieve this:
column bool is not null
when supplied value is null it will fill in with default value false
thought this will make it:
create table public.testnull
(
xid integer not null, bool boolean default false
)
test got error
insert into public.testnotnull values(2, null)
ERROR: null value in column "bool" violates not-null constraint
DETAIL: Failing row contains (2, null).
SQL state: 23502
this will run but won't use default. Please don't tell me to use trigger.
CREATE TABLE public.testnull
(
xid integer NOT NULL, bool boolean DEFAULT false
)

You need to use the DEFAULT keyword instead of NULL in your INSERT statement.
From the docs:
DEFAULT: The corresponding column will be filled with its default value. An identity column will be filled with a new value generated by the associated sequence. For a generated column, specifying this is permitted but merely specifies the normal behavior of computing the column from its generation expression.
Also, always explicitly specify column names when using INSERT.
Speaking from decades of experience: unless you're using an ORM it's impossible to keep your CREATE TABLE definitions and INSERT statements in-sync, and eventually you'll add a new column or alter an existing column somewhere that the INSERT statements aren't expecting and everything will break.
INSERT INTO table ( xid, bool ) VALUES ( 2, DEFAULT )
Please don't tell me to use trigger.
However, if you want to change the NULL into DEFAULT or FALSE in a statement like this: INSERT INTO table ( xid, bool ) VALUES ( 2, NULL ) then you have to use a TRIGGER. There's no real way around that.
(You could use a VIEW with a custom INSERT handler, of course, but that's the same thing as creating a trigger).

Related

Get row number of row to be inserted in Postgres trigger that gives no collisions when inserting multiple rows

Given the following (simplified) schema:
CREATE TABLE period (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
name TEXT
);
CREATE TABLE course (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
name TEXT
);
CREATE TABLE registration (
id UUID NOT NULL DEFAULT uuid_generate_v4(),
period_id UUID NOT NULL REFERENCES period(id),
course_id UUID NOT NULL REFERENCES course(id),
inserted_at timestamptz NOT NULL DEFAULT now()
);
I now want to add a new column client_ref, which identifies a registration unique within a period, but consists of only a 4-character string. I want to use pg_hashids - which requires a unique integer input - to base the column value on.
I was thinking of setting up a trigger on the registration table that runs on inserting a new row. I came up with the following:
CREATE OR REPLACE FUNCTION set_client_ref()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
next_row_number integer;
BEGIN
WITH rank AS (
SELECT
period.id AS period_id,
row_number() OVER (PARTITION BY period.id ORDER BY registration.inserted_at)
FROM
registration
JOIN period ON registration.period_id = period.id ORDER BY
period.id,
row_number
)
SELECT
COALESCE(rank.row_number, 0) + 1 INTO next_row_number
FROM
period
LEFT JOIN rank ON (rank.period_id = period.id)
WHERE
period.id = NEW.period_id
ORDER BY
rank.row_number DESC
LIMIT 1;
NEW.client_ref = id_encode (next_row_number);
RETURN NEW;
END
$function$
;
The trigger is set-up like: CREATE TRIGGER set_client_ref BEFORE INSERT ON registration FOR EACH ROW EXECUTE FUNCTION set_client_ref();
This works as expected when inserting a single row to registration, but if I insert multiple within one statement, they end up having the same client_ref. I can reason about why this happens (the rows don't know about each other's existence, so they assume they're all just next in line when retrieving their row_order), but I am not sure what a way is to prevent this. I tried setting up the trigger as an AFTER trigger, but it resulted in the same (duplicated) behaviour.
What would be a better way to get the lowest possible, unique integer for the rows to be inserted (to base the hash function on) that also works when inserting multiple rows?

Why default is not used when I set not null contratint?

I have table in postgresql database.
For given column I set default value, then I want it to be NOT NULL:
ALTER TABLE "order" ALTER COLUMN last_bill_date SET DEFAULT '-Infinity';
ALTER TABLE "order" ALTER COLUMN last_bill_date SET NOT NULL;
But second statement fails:
ERROR: column "last_bill_date" contains null values
Why DEFAULT value is not used when NOT NULL is applied for this column?
Per the documentation:
DEFAULT default_expr
(...)
The default expression will be used in any insert operation that does not specify a value for the column. If there is no default for a column, then the default is null.
The altered default expression cannot modify rows already existing in the table, you should do it before setting the not null constraint:
update "order"
set last_bill_date = '-Infinity'
where last_bill_date is null

Unable to change field type to not null with default value

I have a simple SQL statement, which looks like so:
alter table my_table alter column my_field set data type numeric(12,4) not null default 0;
But I get an error message, that points to not. What is wrong with that?
Use separate ALTER COLUMN clauses for the type, null behavior, and default value:
ALTER TABLE my_table
ALTER COLUMN my_field TYPE numeric(12,4),
ALTER COLUMN my_field SET DEFAULT 0,
ALTER COLUMN my_field SET NOT NULL;

How can I set the next value of a serial for the serial used by the primary key of a table in postgres?

I have Table A. Table A owns a sequence.
I create Table B, inheriting from Table A.
Table A and B now use the same default value for their primary key column.
For a simplified example, Table A is "person", and B is "bulk_upload_person".
CREATE TABLE "testing"."person" (
"person_id" serial, --Resulting DDL: int4 NOT NULL DEFAULT nextval('person_person_id_seq'::regclass)
"public" bool NOT NULL DEFAULT false
);
--SQL Ran
CREATE TABLE "testing"."bulk_upload_person" (
"upload_id" int4 NOT NULL
)
INHERITS ("testing"."person");
--Resulting DDL
CREATE TABLE "testing"."bulk_upload_person" (
"person_id" int4 NOT NULL DEFAULT nextval('person_person_id_seq'::regclass),
"public" bool NOT NULL DEFAULT false,
"upload_id" int4 NOT NULL
)
INHERITS ("testing"."person");
For table A, I can get the sequence by using pg_get_table_serial_seqence.
How can I get and then set the next value of the sequence if I only know about Table B? I want to add n to the value.
I need to do this in order to populate multiple related objects at once, while being able to know what primary IDs they will have, rather than having to query the tables I've just populated to determine the IDs.
By populate, I mean inserting multiple rows in one statement.
insert into "testing"."bulk_upload_person" ( "person_id", "public", "upload_id") values ( '1', 'f', '1'), ( '2', 't', '1'); --etc
I think our situation is similar to https://stackoverflow.com/a/8007835/89211 but we don't want to keep the lock on the table beyond getting and setting the next value of the serial for each table.
Currently we are doing this by getting the name of the sequence by regexing the default value of the primary key for Table B, but it feels like there's probably a better way to do this that we don't realise.

Conditional unique constraint not updating correctly

I need to enforce uniqueness on a column but only when other column is true. For example:
create temporary table test(id serial primary key, property character varying(50), value boolean);
insert into test(property, value) values ('a', false);
insert into test(property, value) values ('a', true);
insert into test(property, value) values ('a', false);
And I enforce the uniqueness with a conditional index:
create unique index on test(property) where value = true;
So far so good, the problem arises when I try to change the row that has the value set to true. It works if I do:
update test set value = new_value from (select id, id=3 as new_value from test where property = 'a')new_test where test.id = new_test.id
But it doesn't when I do:
update test set value = new_value from (select id, id=1 as new_value from test where property = 'a')new_test where test.id = new_test.id
And I get:
ERROR: duplicate key value violates unique constraint "test_property_idx"
DETAIL: Key (property)=(a) already exists.
********** Error **********
ERROR: duplicate key value violates unique constraint "test_property_idx"
SQL state: 23505
Detail: Key (property)=(a) already exists.
Basically it works if the row with value true has a primary key with a bigger value than the current row which is truthy. Any idea on how to circumvent it?
Of course I could do:
update test set value = false where property='a';
update test set value = true where property = 'a' and id = 1;
However, I'm running these queries from node and it is preferable to run only one query.
I'm using Postgres 9.5
Your problem is that UPDATE statements cannot have an ORDER BY clause in SQL (it can have in some RDBMS, but not in PostgreSQL).
The usual solution to this is to make the constraint deferrable. But you use a partial unique index & indexes cannot be declared as deferrable.
Use an exclusion constraint instead: they are the generalization of unique constraints & can be partial too.
ALTER TABLE test
ADD EXCLUDE (property WITH =) WHERE (value = true)
DEFERRABLE INITIALLY DEFERRED;