Use DEFAULT value if empty string - sql

How can I tell Postgres that a column with an empty string should use the DEFAULT value?
Using CHECK doesn't seem to work here.

A check constraint will only prevent putting such a value into the column. It will not magically replace the supplied value with the default value.
In order to silently replace an empty string (or null) with the value 'active' you will need a trigger:
create or replace function check_default()
returns trigger
as
$body$
begin
if (new.status is null or new.status = '') then
new.status = 'active';
end if;
return new;
end;
$body$
language plpgsql;
The above trigger would catch stuff like this:
insert into foo (id, active)
values (42,'');
update foo
set active = ''
where id = 42;
You could even extend that logic to also replace whitespace only values (' ') with the desired value.
Although it is possible to retrieve the default value dynamically in the trigger (to avoid having the same constant in two places) I would not do that for performance reasons.

Related

Restrict insert into column with default value

Taking the following schema as an example:
CREATE TABLE public."RiskHeaders"
(
"UID" uuid NOT NULL DEFAULT uuid_generate_v4(),
"Name" character varying(35) NOT NULL
)
Is it possible to prevent INSERT statements including values for the UID column?
For avoidance of doubt, I wish for all values within the UID column to be randomly generated via the default value function.
I had hoped that I could use an INSERT trigger to see whether UID had been populated but that doesn't appear to be possible:
BEGIN
IF TG_OP = 'INSERT' THEN
IF TG_WHEN = 'BEFORE' THEN
RAISE NOTICE 'Before: Old = % New = %', OLD."UID", NEW."UID";
ELSIF TG_WHEN = 'AFTER' THEN
RAISE NOTICE 'After: Old = % New = %', OLD."UID", NEW."UID";
END IF;
END IF;
RETURN NEW;
END;
Running INSERT INTO "RiskHeaders"("Name") VALUES ('Test'); gives the following console output:
NOTICE: Before: Old = <NULL> New = ccc6757f-d1a2-4849-828d-017d2b738c9d
NOTICE: After: Old = <NULL> New = ccc6757f-d1a2-4849-828d-017d2b738c9d
Running INSERT INTO "RiskHeaders"("UID", "Name") VALUES (uuid_generate_v4(), 'Test1'); gives the same output (albeit with a different UUID).
Thank you in advance for any suggestions and hopefully this isn't too daft a question!
Many thanks for all the suggestions!
Given my (reckless?) determination to keep the UID column as a uuid type and prevent the user from specifying any values on INSERT, I have come up with the following work-around(/bodge):
I am now using the following schema:
CREATE TABLE public."RiskHeaders"
(
"UID" uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid,
"Name" character varying(35) NOT NULL
}
Taking #stickybit's suggestion to use a trigger... I have come up with a trigger to the effect of:
IF TG_OP = 'INSERT' THEN
IF TG_WHEN = 'BEFORE' THEN
IF NEW."UID" <> '00000000-0000-0000-0000-000000000000'::uuid THEN
RAISE EXCEPTION 'A UID cannot be specified when creating a risk.';
ELSE
NEW."UID" := uuid_generate_v4();
RETURN NEW;
END IF;
...
END IF;
END IF;
...
The drawbacks from this are:
The schema doesn't make it clear that UIDs are actually being randomly assigned.
The user could specify a zero UID which would then get silently replaced.

Postgresql create stored procedure with update a row

I have a table names Locations. It has columns [ID, GUID_ID, NAME, GEOMETRY]. I want to update GUID_ID column after a row inserted. So I will create a trigger to do this.
CREATE TRIGGER update_id
AFTER INSERT ON Locations
?? (How can Update GUID_ID column as uuid_generate_v4())
I set a default value for this column. But third party applications like QGIS inserts thousands of geometry records at same time. So the default value does not fill all columns. They are null. So I need trigger for solution.
I think it dosnt matter how much records QGIS enters, if you have defined a default value for field. May be QGIS is entering empty space instead. You can also add "NOT NULL" constraint in the field definition.
However trigger can be used like this. I am setting the GUID_ID value BEFORE INSERTING though.
CREATE TRIGGER update_id
BEFORE INSERT ON Locations
FOR EACH ROW EXECUTE PROCEDURE fn_trgr();
CREATE OR REPLACE FUNCTION fn_trgr() RETURNS TRIGGER AS $$
BEGIN
IF pg_trigger_depth() <> 1 THEN
RETURN NEW;
END IF;
NEW.GUID_ID = uuid_generate_v4(); -- or whatever value you wants to set
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

PostgreSQL Update trigger

I have a table:
CREATE TABLE annotations
(
gid serial NOT NULL,
annotation character varying(250),
the_geom geometry,
"rotationAngle" character varying(3) DEFAULT 0,
CONSTRAINT annotations_pkey PRIMARY KEY (gid),
CONSTRAINT enforce_dims_the_geom CHECK (st_ndims(the_geom) = 2),
CONSTRAINT enforce_srid_the_geom CHECK (st_srid(the_geom) = 4326)
)
And trigger:
CREATE TRIGGER set_angle
AFTER INSERT OR UPDATE
ON annotations
FOR EACH ROW
EXECUTE PROCEDURE setangle();
And function:
CREATE OR REPLACE FUNCTION setAngle() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
And when new row inserted in table or row edited i want to field rotationAngle setted with function result.
But when i inserting a new row in table function not work. I mean thath rotationAngle value not changed.
What can be wrong?
You are triggering an endless loop. Simplify the trigger function:
CREATE OR REPLACE FUNCTION set_angle()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
NEW."rotationAngle" := degrees(
ST_Azimuth(
ST_StartPoint(NEW.the_geom)
, ST_EndPoint(NEW.the_geom)
)
) - 90;
RETURN NEW;
END
$func$;
Assign to NEW directly. No WHERE in this case.
You must double-quote illegal column names. Better not to use such names to begin with.
Recent related answer.
Code for insert & upgrade is the same. I folded into one code path.
Use a BEFORE trigger. This way you can edit columns of the triggering row directly before they are saved:
CREATE TRIGGER set_angle
BEFORE INSERT OR UPDATE ON annotations
FOR EACH ROW EXECUTE PROCEDURE set_angle();
However
If you are just trying to persist a functionally dependent value in the table (and there are no other considerations): Don't. Use a view or a generated column instead:
Store common query as column?
Then you don't need any of this.
There are multiple things wrong here.
1) When you insert a row 'A' the function setAngle() is called. But in the function you are calling another update within the function which will trigger the function again, and again, and so on...To fix this don't issue a update! Just update the NEW records value independently and return it.

Functions with variable number of input parameters

I'm creating a stored procedure (function) in a PostgreSQL DB, which updates a table depending on its input. In order to create a variable number of parameter function, I'm creating an extra input parameter called mode, which I use to control which parameters I use on the update query.
CREATE OR REPLACE FUNCTION update_site(
mode integer,
name character varying,
city character varying,
telephone integer,
)
RETURNS integer AS
$$
BEGIN
IF mode = 0 THEN
BEGIN
UPDATE "Sites" SET
("City","Telephone") = (city,telephone)
WHERE "SiteName" = name;
RETURN 1;
EXCEPTION WHEN others THEN
RAISE NOTICE 'Error on site update: %, %',SQLERRM,SQLSTATE;
RETURN 0;
END;
ELSIF mode = 1 THEN
BEGIN
UPDATE "Sites" SET "City" = city
WHERE "SiteName" = name;
RETURN 1;
EXCEPTION WHEN others THEN
RAISE NOTICE 'Error on site update: %, %',SQLERRM,SQLSTATE;
RETURN 0;
END;
ELSIF mode = 2 THEN
BEGIN
UPDATE "Sites" SET "Telephone" = telephone
WHERE "SiteName" = name;
RETURN 1;
EXCEPTION WHEN others THEN
RAISE NOTICE 'Error on site update: %, %',SQLERRM,SQLSTATE;
RETURN 0;
END;
ELSE
RAISE NOTICE 'Error on site update: %, %',SQLERRM,SQLSTATE;
RETURN 0;
END IF;
END;
$$ LANGUAGE plpgsql;
What would be best? To create a function update_site(<all the columns of table>) and a separate function update_site(id integer, <varchar column to update>), or use the mode in one function to define the difference? Which option is more efficient? One unique function or different ones for each purpose?
Advanced features like VARIADIC or even polymorphic input types and dynamic SQL are very powerful. The last chapter in this answer provides an advanced example:
Refactor a PL/pgSQL function to return the output of various SELECT queries
But for a simple case like yours, you can just use default values for function parameters. It all depends on exact requirements.
If the columns in question are all defined NOT NULL, this would probably be simpler and faster:
CREATE OR REPLACE FUNCTION update_site(_name text -- always required
, _city text DEFAULT NULL
, _telephone integer DEFAULT NULL)
RETURNS integer AS
$func$
BEGIN
IF _city IS NULL AND _telephone IS NULL THEN
RAISE WARNING 'At least one value to update required!';
RETURN; -- nothing to update
END IF;
UPDATE "Sites"
SET "City" = COALESCE(_city, "City")
, "Telephone" = COALESCE(_telephone, "Telephone")
WHERE "SiteName" = _name;
END
$func$ LANGUAGE plpgsql;
Read about default values in the manual!
To avoid naming conflicts between parameters and column names I make it a habit to prefix input parameters with _. That's a matter of taste and style.
The first parameter name has no default, since it is required at all times.
Other parameters can be omitted.
At least one is required, or a WARNING is raised and nothing else happens.
The UPDATE will only change columns for given parameters.
Can easily be expanded for N parameters.
Function call
Since Postgres 9.5:
The simple way is with positional notation for parameters. This only allows to omit the rightmost parameter(s):
SELECT update_site('foo', 'New York'); -- no telephone
Named notation allows to omit any parameter that has a default value:
SELECT update_site(name => 'foo', _telephone => 123); -- no city
Both can be combined in mixed notation:
SELECT update_site('foo', _telephone => 123); -- still no city
In Postgres 9.4 or older, := was used for assignment in the call:
SELECT update_site(name := 'foo', _telephone := 123);
SELECT update_site('foo', _telephone := 123);
Still valid in Postgres 12 for backward compatibility, but rather use the modern notation.
There are a few things you'll want to look into:
Dynamically building the SQL using the format function and its %I and %L specifiers, then executing it with EXECUTE ... USING; and
Using VARIADIC parameters to take variable numbers of arguments to the function, with the caveat that they must all be the same data type.

Postgresql regex in tsvector update

I have the following update trigger for a tsvector column
CREATE TRIGGER tsvector_user_update
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE
tsvector_update_trigger(user_tsv, 'pg_catalog.english', firstname, surname, email, card_id);
This works fine, however my card_id column (text) contains a pre-amble that the user is not aware of (it is added after the card is scanned), so I would like to strip out the pre-amble when the tsvector value is generated, I have tried the trigger function as a start
CREATE FUNCTION user_change_trigger() RETURNS trigger AS $$
BEGIN
NEW.user_tsv = setweight(to_tsvector('pg_catalog.english', coalesce(NEW.firstname,'')), 'A') ||
setweight(to_tsvector('pg_catalog.english', coalesce(NEW.surname,'')), 'A') ||
setweight(to_tsvector('pg_catalog.english', coalesce(REGEXP_REPLACE(NEW.card_id, '^\d+PRE', ''),'')), 'B') ||
setweight(to_tsvector('pg_catalog.english', coalesce(NEW.email,'')), 'C');
return new;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON users FOR EACH ROW EXECUTE PROCEDURE user_change_trigger();
Which executes, but I get the following:
WARNING: nonstandard use of escape in a string literal
And no updated tsvector
The pre-amble is an integer followed by 'PRE'.
(PostgreSQL 9.0)
Basic trigger design
The problem is of principal nature. In PostgreSQL you create a trigger function that does the work. I don't see your trigger function in the question.
Then you create a trigger which makes use of this function. You can only pass constants to a trigger function. Consider this quote from the manual about CREATE TRIGGER
function_name
A user-supplied function that is declared as taking no arguments and
returning type trigger, which is executed when the trigger fires.
arguments
An optional comma-separated list of arguments to be provided to the
function when the trigger is executed. The arguments are literal
string constants. Simple names and numeric constants can be written
here, too, but they will all be converted to strings. Please check the
description of the implementation language of the trigger function to
find out how these arguments can be accessed within the function; it
might be different from normal function arguments.
Bold emphasis mine.
Use NEW to access the column values inside the trigger function. You don't need to pass them as arguments. Get a grip on the basic concept first. Start here.
regexp_replace()
Use:
regexp_replace(card_id, '^\d+PRE', '')
.. since the leading characters are supposed to be digits only (and at least one of them).
Proper trigger & function
The following test case works for me on PostgreSQL 9.1.6. Your version looks basically good to me, I only made minor changes. But keep reading ...
Create test environment (will be rolled back at the end):
BEGIN;
CREATE SCHEMA test;
SET search_path = test;
CREATE TABLE users (
users_id serial primary key
,firstname text
,surname text
,card_id text
,email text
,user_tsv tsvector
);
Trigger function:
CREATE FUNCTION user_change_trigger()
RETURNS trigger AS
$func$
BEGIN
NEW.user_tsv :=
setweight(to_tsvector('pg_catalog.english', coalesce(NEW.firstname,'')), 'A')
|| setweight(to_tsvector('pg_catalog.english', coalesce(NEW.surname,'')), 'A')
|| setweight(to_tsvector('pg_catalog.english', coalesce(regexp_replace(NEW.card_id, '^\d+PRE', ''),'')), 'B')
|| setweight(to_tsvector('pg_catalog.english', coalesce(NEW.email,'')), 'C');
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
The assignment operator of plpgsql is := - unlike SQL where = is used.
Trigger:
CREATE TRIGGER tsvectorupdate
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE user_change_trigger();
Tests:
INSERT INTO users (firstname, surname, card_id, email)
VALUES ('Erwin', 'Brandstetter', '123PRE456', 'foo#dummy.org')
RETURNING *;
-- looks good!
UPDATE users SET firstname = 'Walter' WHERE TRUE
RETURNING *;
-- looks good, too!
Clean up:
ROLLBACK;
standard_conforming_strings
Explore your setting of standard_conforming_strings. The WARNING suggests that you don't have this setting on, which would require that you double the backslash in:
'^\\d+PRE'