Unterminated dollar quote - sql

How to use IF statement in the PostgreSql (11 version)? I tried just raw IF usage but got problem (syntax error at or near “IF”). To resolve this problem people propose to use 'do &&' but it does not work as well (Unterminated dollar quote started at position 3 in SQL DO $$ BEGIN IF ......). Here is my SQL code:
DO $$
BEGIN
IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'categories')) THEN
CREATE TABLE IF NOT EXISTS categories
(
id SERIAL NOT NULL,
name character varying(40),
CONSTRAINT categories_pkey PRIMARY KEY (id)
);
INSERT INTO categories (name) VALUES ('Games');
INSERT INTO categories (name) VALUES ('Multimedia');
INSERT INTO categories (name) VALUES ('Productivity');
INSERT INTO categories (name) VALUES ('Tools');
INSERT INTO categories (name) VALUES ('Health');
INSERT INTO categories (name) VALUES ('Lifestyle');
INSERT INTO categories (name) VALUES ('Other');
END IF;
END
$$;
All I need is to create table and insert some init data to it if table does not exist.

Some platforms do not support dollar quoting. In your specific example you should have a semicolon after the last END. You may need to add a DECLARE statement also.
DO
$$
DECLARE
BEGIN
IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'categories'))
THEN
CREATE TABLE IF NOT EXISTS categories
(
id SERIAL NOT NULL,
name character varying(40),
CONSTRAINT categories_pkey PRIMARY KEY (id)
);
INSERT INTO categories (name) VALUES ('Games');
INSERT INTO categories (name) VALUES ('Multimedia');
INSERT INTO categories (name) VALUES ('Productivity');
INSERT INTO categories (name) VALUES ('Tools');
INSERT INTO categories (name) VALUES ('Health');
INSERT INTO categories (name) VALUES ('Lifestyle');
INSERT INTO categories (name) VALUES ('Other');
END IF;
END;
$$ LANGUAGE PLPGSQL;
For platforms that don't recognize dollar quoting you can use ' instead. You'll need to escape any ' in the body of the anonymous function though.
Like so:
DO
'
DECLARE
BEGIN
IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''categories''))
THEN
CREATE TABLE IF NOT EXISTS categories
(
id SERIAL NOT NULL,
name character varying(40),
CONSTRAINT categories_pkey PRIMARY KEY (id)
);
INSERT INTO categories (name) VALUES (''Games'');
INSERT INTO categories (name) VALUES (''Multimedia'');
INSERT INTO categories (name) VALUES (''Productivity'');
INSERT INTO categories (name) VALUES (''Tools'');
INSERT INTO categories (name) VALUES (''Health'');
INSERT INTO categories (name) VALUES (''Lifestyle'');
INSERT INTO categories (name) VALUES (''Other'');
END IF;
END;
' LANGUAGE PLPGSQL;
DBFiddle to view a working example.

I have the same issue in play framework evolution, fixed by adding additional semicolon
$BODY$
BEGIN
if NEW.year_after IS NULL THEN
NEW.year_after := 100;;
NEW.after := 200;;
end if;;
RETURN NEW;;
END;;
$BODY$ language plpgsql

I'm posting this here as this is the first result on Google when searching for this problem on Delphi's FireDAC.
My issue was that the function had comments with accented characters, and after removing those comments, the "Unterminated dollar quote" error went away. So be careful with UTF8 characters in comments.
I would also like to note that I've had similar problems with operators that use special characters, like the !~ (NOT SIMILAR TO) operator, which was wrongfully sent to PostgreSQL as the ~ (SIMILAR TO) operator.

Related

postgres updatable view and unique constraints

in my simple application I would like to create a view in order to allow users filling data of my db.
Here a little example of my data
CREATE TABLE specie
(
specie_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_comune TEXT UNIQUE,
nome_scientifico TEXT UNIQUE
);
CREATE TABLE rilevatore
(
rilevatore_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
nome_cognome TEXT UNIQUE,
telefono INTEGER,
email TEXT,
ente_appartenenza TEXT
);
CREATE TABLE evento_investimento
(
evento_id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
data DATE,
ora TIME WITHOUT TIME ZONE,
rilevatore_id INT REFERENCES rilevatore (rilevatore_id),
specie_id INT REFERENCES specie(specie_id),
);
This is the VIEW I created
CREATE VIEW investimenti_vista AS
SELECT
evento_investimento.evento_id,
evento_investimento.ora,
evento_investimento.data,
rilevatore.nome_cognome,
rilevatore.telefono,
rilevatore.email,
rilevatore.ente_appartenenza,
specie.nome_comune,
specie.nome_scientifico
from
evento_investimento
JOIN specie ON evento_investimento.specie_id = specie.specie_id
JOIN rilevatore ON evento_investimento.rilevatore_id = rilevatore.rilevatore_id;
When I attempt to fill the data I receive an error from postgres since view generated from different tables aren't updatable by default.
Thus, I implemetend the following trigger to overcome this issue.
CREATE OR REPLACE FUNCTION inserimento_vista() RETURNS trigger AS $$
BEGIN
INSERT INTO evento_investimento (data,ora)
VALUES (NEW.data,NEW.ora);
INSERT INTO rilevatore (nome_cognome, telefono, email, ente_appartenenza)
VALUES (NEW.nome_cognome, NEW.telefono, NEW.email, NEW.ente_appartenenza);
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
create trigger inserimento_vista_trg
instead of insert on investimenti_vista for each row EXECUTE procedure inserimento_vista();
However this is not working due to unique contraints I have in the rilevatore and specie tables. How I can solve this?
Thanks
You might try to check for the existence of the conflicting values like this:
CREATE OR REPLACE FUNCTION inserimento_vista() RETURNS trigger AS $$
BEGIN
INSERT INTO evento_investimento (data,ora)
VALUES (NEW.data,NEW.ora);
if not exists(select * from rilevatore where rilevatore.nome_cognome=new.nome_cognome) then
INSERT INTO rilevatore (nome_cognome, telefono, email, ente_appartenenza)
VALUES (NEW.nome_cognome, NEW.telefono, NEW.email, NEW.ente_appartenenza);
end if;
if not exists(select * from specie where specie.nome_comune=new.nome_comune) then
INSERT INTO specie (nome_comune, nome_scientifico)
VALUES (NEW.nome_comune, NEW.nome_scientifico);
end if;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
You might want to add to this an update the specie and/or rilevatore tables with the non-conflicting values but that's up to you :-)

Insert Into Oracle Table with single, autoincrement Column

Imagine the following (fictional) situation:
You have a table with only one column id that is the primary key, autoincremented by using a typical sequence + trigger combination.
How would you create a new row there as you have to specify the values keyword for the insert query?
INSERT INTO table () VALUES () is not valid as far as I understood.
ATTENTION:
This is not a discussion about the sense of such a table! It is out of pure technical interest.
In any current Oracle version (12.1, 12.2, 18) I would not use a trigger but an identity column - then use the default keyword during insert:
create table x (id integer generated by default as identity);
insert into x (id) values (default);
How about
INSERT INTO theTable (id) VALUES (null);
and your before insert trigger would be like:
if :NEW.id is NULL Then
SELECT id_sequence.NEXTVAL INTO :NEW.id FROM dual;
end if;

PostgreSQL constraint using prefixes

Let's say I have the following PostgreSQL table:
id | key
---+--------
1 | 'a.b.c'
I need to prevent inserting records with a key that is a prefix of another key. For example, I should be able to insert:
'a.b.b'
But the following keys should not be accepted:
'a.b'
'a.b.c'
'a.b.c.d'
Is there a way to achieve this - either by a constraint or by a locking mechanism (check the existance before inserting)?
This solution is based on PostgreSQL user-defined operators and exclusion constraints (base syntax, more details).
NOTE: more testing shows this solution does not work (yet). See bottom.
Create a function has_common_prefix(text,text) which will calculate logically what you need. Mark the function as IMMUTABLE.
CREATE OR REPLACE FUNCTION
has_common_prefix(text,text)
RETURNS boolean
IMMUTABLE STRICT
LANGUAGE SQL AS $$
SELECT position ($1 in $2) = 1 OR position ($2 in $1) = 1
$$;
Create an operator for the index
CREATE OPERATOR <~> (
PROCEDURE = has_common_prefix,
LEFTARG = text,
RIGHTARG = text,
COMMUTATOR = <~>
);
Create exclusion constraint
CREATE TABLE keys ( key text );
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
EXCLUDE ( key WITH <~> );
However, the last point produces this error:
ERROR: operator <~>(text,text) is not a member of operator family "text_ops"
DETAIL: The exclusion operator must be related to the index operator class for the constraint.
This is because to create an index PostgreSQL needs logical operators to be bound with physical indexing methods, via entities calles "operator classes". So we need to provide that logic:
CREATE OR REPLACE FUNCTION keycmp(text,text)
RETURNS integer IMMUTABLE STRICT
LANGUAGE SQL AS $$
SELECT CASE
WHEN $1 = $2 OR position ($1 in $2) = 1 OR position ($2 in $1) = 1 THEN 0
WHEN $1 < $2 THEN -1
ELSE 1
END
$$;
CREATE OPERATOR CLASS key_ops FOR TYPE text USING btree AS
OPERATOR 3 <~> (text, text),
FUNCTION 1 keycmp (text, text)
;
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
EXCLUDE ( key key_ops WITH <~> );
Now, it works:
INSERT INTO keys SELECT 'ara';
INSERT 0 1
INSERT INTO keys SELECT 'arka';
INSERT 0 1
INSERT INTO keys SELECT 'barka';
INSERT 0 1
INSERT INTO keys SELECT 'arak';
psql:test.sql:44: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(arak) conflicts with existing key (key)=(ara).
INSERT INTO keys SELECT 'bark';
psql:test.sql:45: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(bark) conflicts with existing key (key)=(barka).
NOTE: more testing shows this solution does not work yet: The last INSERT should fail.
INSERT INTO keys SELECT 'a';
INSERT 0 1
INSERT INTO keys SELECT 'ac';
ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(ac) conflicts with existing key (key)=(a).
INSERT INTO keys SELECT 'ab';
INSERT 0 1
You can use ltree module to achieve this, it will let you to create hierarchical tree-like structures. Also will help you to prevent from reinventing the wheel, creating complicated regular expressions and so on. You just need to have postgresql-contrib package installed. Take a look:
--Enabling extension
CREATE EXTENSION ltree;
--Creating our test table with a pre-loaded data
CREATE TABLE test_keys AS
SELECT
1 AS id,
'a.b.c'::ltree AS key_path;
--Now we'll do the trick with a before trigger
CREATE FUNCTION validate_key_path() RETURNS trigger AS $$
BEGIN
--This query will do our validation.
--It'll search if a key already exists in 'both' directions
--LIMIT 1 because one match is enough for our validation :)
PERFORM * FROM test_keys WHERE key_path #> NEW.key_path OR key_path <# NEW.key_path LIMIT 1;
--If found a match then raise a error
IF FOUND THEN
RAISE 'Duplicate key detected: %', NEW.key_path USING ERRCODE = 'unique_violation';
END IF;
--Great! Our new row is able to be inserted
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_keys_validator BEFORE INSERT OR UPDATE ON test_keys
FOR EACH ROW EXECUTE PROCEDURE validate_key_path();
--Creating a index to speed up our validation...
CREATE INDEX idx_test_keys_key_path ON test_keys USING GIST (key_path);
--The command below will work
INSERT INTO test_keys VALUES (2, 'a.b.b');
--And the commands below will fail
INSERT INTO test_keys VALUES (3, 'a.b');
INSERT INTO test_keys VALUES (4, 'a.b.c');
INSERT INTO test_keys VALUES (5, 'a.b.c.d');
Of course I did not bother creating primary key and other constraints for this test. But do not forget to do so. Also, there is much more on ltree module than I'm showing, if you need something different take a look on its docs, perhaps you'll find the answer there.
You can try below trigger. Please note that key is sql reserve word. So I would suggest you avoid using that as column name in your table.
I have added my create table syntax also for testing purpose:
CREATE TABLE my_table
(myid INTEGER, mykey VARCHAR(50));
CREATE FUNCTION check_key_prefix() RETURNS TRIGGER AS $check_key_prefix$
DECLARE
v_match_keys INTEGER;
BEGIN
v_match_keys = 0;
SELECT COUNT(t.mykey) INTO v_match_keys
FROM my_table t
WHERE t.mykey LIKE CONCAT(NEW.mykey, '%')
OR NEW.mykey LIKE CONCAT(t.mykey, '%');
IF v_match_keys > 0 THEN
RAISE EXCEPTION 'Prefix Key Error occured.';
END IF;
RETURN NEW;
END;
$check_key_prefix$ LANGUAGE plpgsql;
CREATE TRIGGER check_key_prefix
BEFORE INSERT OR UPDATE ON my_table
FOR EACH ROW
EXECUTE PROCEDURE check_key_prefix();
Here is a CHECK - based solution - it may satisfy your needs.
CREATE TABLE keys ( id serial primary key, key text );
CREATE OR REPLACE FUNCTION key_check(text)
RETURNS boolean
STABLE STRICT
LANGUAGE SQL AS $$
SELECT NOT EXISTS (
SELECT 1 FROM keys
WHERE key ~ ( '^' || $1 )
OR $1 ~ ( '^' || key )
);
$$;
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
CHECK ( key_check(key) );
PS. Unfortunately, it fails in one point (multi - row inserts).
SQL is a very powerful language. Usually you can do most of the things by plain select statements. I.e. if you do not like triggers, you can use a this method for your inserts.
The only assumption is there exists at least 1 row in the table. (*)
The table:
create table my_table
(
id integer primary key,
key varchar(100)
);
Because of the assumption, we'll have at least 1 row.(*)
insert into my_table (id, key) values (1, 'a.b.c');
Now the magic sql. The trick is replace the p_key value by your key value to insert. I have, intentionally, not put that statement into a stored procedure. Because I want it to be straight forward if you want to carry it to your application side. But usually putting sql into stored procedure is better.
insert into my_table (id, key)
select (select max(id) + 1 from my_table), p_key
from my_table
where not exists (select 'p' from my_table where key like p_key || '%' or p_key like key || '%')
limit 1;
Now the tests:
-- 'a.b.b' => Inserts
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b.b'
from my_table
where not exists (select 'p' from my_table where key like 'a.b.b' || '%' or 'a.b.b' like key || '%')
limit 1;
-- 'a.b' => does not insert
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b'
from my_table
where not exists (select 'p' from my_table where key like 'a.b' || '%' or 'a.b' like key || '%')
limit 1;
-- 'a.b.c' => does not insert
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b.c'
from my_table
where not exists (select 'p' from my_table where key like 'a.b.c' || '%' or 'a.b.c' like key || '%')
limit 1;
-- 'a.b.c.d' does not insert
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b.c.d'
from my_table
where not exists (select 'p' from my_table where key like 'a.b.c.d' || '%' or 'a.b.c.d' like key || '%')
limit 1;
(*) If you wish you can get rid of this existence of the single row by introducing an Oracle like dual table. If you wish modifying the insert statement is straight forward. Let me know if you wish to do so.
One possible solution is to create a secondary table that holds the prefixes of your keys, and then use a combination of unique and exclusion constraints with an insert trigger to enforce the uniqueness semantics you want.
At a high level, this approach breaks each key down into a list of prefixes and applies something similar to readers-writer lock semantics: any number of keys may share a prefix as long as none of the keys equals the prefix. To accomplish that, the list of prefixes includes the key itself with a flag that marks it as a terminal prefix.
The secondary table looks like this. We use a CHAR rather than a BOOLEAN for the flag because later on we’ll be adding a constraint that doesn’t work on boolean columns.
CREATE TABLE prefixes (
id INTEGER NOT NULL,
prefix TEXT NOT NULL,
is_terminal CHAR NOT NULL,
CONSTRAINT prefixes_id_fk
FOREIGN KEY (id)
REFERENCES your_table (id)
ON DELETE CASCADE,
CONSTRAINT prefixes_is_terminal
CHECK (is_terminal IN ('t', 'f'))
);
Now we’ll need to define a trigger on insert into your_table to also insert rows into prefixes, such that
INSERT INTO your_table (id, key) VALUES (1, ‘abc');
causes
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'a', ‘f’);
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'ab', ‘f’);
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'abc', ’t’);
The trigger function might look like this. I’m only covering the INSERT case here, but the function could be made to handle UPDATE as well by deleting the old prefixes and then inserting the new ones. The DELETE case is covered by the cascading foreign-key constraint on prefixes.
CREATE OR REPLACE FUNCTION insert_prefixes() RETURNS TRIGGER AS $$
DECLARE
is_terminal CHAR := 't';
remaining_text TEXT := NEW.key;
BEGIN
LOOP
IF LENGTH(remaining_text) <= 0 THEN
EXIT;
END IF;
INSERT INTO prefixes (id, prefix, is_terminal)
VALUES (NEW.id, remaining_text, is_terminal);
is_terminal := 'f';
remaining_text := LEFT(remaining_text, -1);
END LOOP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
We add this function to the table as a trigger in the usual way.
CREATE TRIGGER insert_prefixes
AFTER INSERT ON your_table
FOR EACH ROW
EXECUTE PROCEDURE insert_prefixes();
An exclusion constraint and a partial unique index will enforce that a row where is_terminal = ’t’ can't collide with another row of the same prefix regardless of its is_terminal value, and that there's only one row with is_terminal = ’t’:
ALTER TABLE prefixes ADD CONSTRAINT prefixes_forbid_conflicts
EXCLUDE USING gist (prefix WITH =, is_terminal WITH <>);
CREATE UNIQUE INDEX ON prefixes (prefix) WHERE is_terminal = 't';
This allows new rows that don’t conflict but prevents ones that do conflict, including in multi-row INSERTs.
db=# INSERT INTO your_table (id, key) VALUES (1, 'a.b.c');
INSERT 0 1
db=# INSERT INTO your_table (id, key) VALUES (2, 'a.b.b');
INSERT 0 1
db=# INSERT INTO your_table (id, key) VALUES (3, 'a.b');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"
db=# INSERT INTO your_table (id, key) VALUES (4, 'a.b.c');
ERROR: duplicate key value violates unique constraint "prefixes_prefix_idx"
db=# INSERT INTO your_table (id, key) VALUES (5, 'a.b.c.d');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"
db=# INSERT INTO your_table (id, key) VALUES (6, 'a.b.d'), (7, 'a');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"

Variables in PostgreSQL with UUID

All my tables use UUID primary keys. I want to fill tables with test data in one sql file, but can't use UUID PK as FK in other tables, because default function uuid_generate_v4() generates a random value during execution.
Example what I want:
SET Uservar = uuid_generate_v4();
SET Postvar = uuid_generate_v4();
INSERT INTO public."User" (id, name) VALUES (Uservar, 'Foo Bar');
INSERT INTO public."Post" (id, content, userId) VALUES (Postvar, 'Test message', Uservar)
How to do this? Or how to select already created UUID and store for next insert?
E.g. say you had table like this:
create table my_table(uuid_column uuid PRIMARY KEY NOT NULL);
You can insert a variablized uuid like so:
DO $$
DECLARE
my_uuid uuid = uuid_generate_v4();
BEGIN
insert into my_table (uuid_column) values (my_uuid);
select * from my_table where uuid_column = my_uuid;
END $$;
Check this documentation.
N.B. To have uuid_generate_v4() available, make sure you have the below snipped ran before your use it:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
When you combine both INSERTs into a single statement you can re-use the uuid values quite easily:
WITH newUser(id uuid) AS (
INSERT INTO public."User" (id, name)
VALUES (uuid_generate_v4(), 'Foo Bar')
RETURNING id
)
INSERT INTO public."Post" (id, content, userId)
SELECT uuid_generate_v4(), 'Test message', id
FROM newUser;
When you want to add a post for an existing user, you can use a very similar approach:
INSERT INTO public."Post" (id, content, userId)
SELECT uuid_generate_v4(), 'Test message', id
FROM public."User"
WHERE name = 'Foo Bar';
This would also work when the PK's are auto-generated (i.e. id uuid PRIMARY KEY DEFAULT uuid_generate_v4()) but then you would not explicitly include the PK columns in the INSERT statements.
As I cannot comment
it should be
DO $$
DECLARE
my_uuid uuid := uuid_generate_v4();
BEGIN
insert into my_table (uuid_column) values (my_uuid);
select * from my_table where uuid_column = my_uuid;
END $$;

How to use default column value for the rowtype variable in PostgreSQL?

I have a table and a procedure like this:
CREATE TABLE test_table (
id SERIAL PRIMARY KEY,
info TEXT);
create or replace function test() returns void as
$$
declare
v_row test_table%ROWTYPE;
begin
v_row.info := 'test';
insert into test_table values (v_row.*);
end;
$$ language plpgsql;
select test();
ERROR: null value in column "id" violates not-null constraint
How to use default value for the v_row.id field? I know I could write
insert into test_table (info) values (v_row.info);
But in my real case I have a lot of such tables with many columns and I really want to avoid enumerating all the columns in the insert statement.
By writing insert into test_table values (v_row.*); you actually force postgres to insert NULL value into the id column.
You will need to run such code - either in application
v_row.id := nextval( 'mysequence' );
.. or in trigger
IF NEW.id IS NULL THEN
NEW.id := nextval( 'mysequence' );
END IF;
You can check, if Postgresql have a SEQUENCE for this column and then, if this column have a DEFAULT value set. In psql try:
\d+ test_table
You have to see somethink like this:
id | integer | default nextval('test_table_id_seq'::regclass) |
If there is not a default nextval('somethink'), then you have to check, if there is sequnence for this column:
\ds+
You have to see somethink like this:
public | test_table_id_seq | sequence
If you will not have a sequence, you have a CREATE it:
CREATE SEQUENCE test_table_id_seq;
And if you will have not a `default nextval('somethink'), you have use a ALTER TABLE:
ALTER TABLE test_table ALTER COLUMN id SET DEFAULT nextval('test_table_id_seq');
You can find about it some informations here: http://www.postgresql.org/docs/8.1/static/datatype.html#DATATYPE-SERIAL
Perhaps you can understand it, although English is not my native language...