Insert into postgres view using defaults based on view definition - postgresql

Нello! Say that a vehicle can be of type "car", "truck", or "motorcycle". Each vehicle has a top_speed (in km/h) and a license_plate string.
E.g.
CREATE TABLE vehicle (
type VARCHAR(20) NOT NULL,
top_speed INTEGER NOT NULL,
license_plate VARCHAR(10) NOT NULL
);
INSERT INTO vehicle (type, top_speed, license_plate)
VALUES
('car', 120, 'abc123'),
('truck', 110, 'def456'),
('motorcycle', 140, 'ghi789');
Now add views for each type of vehicle:
CREATE VIEW car AS (SELECT * FROM vehicle WHERE type='car');
CREATE VIEW truck AS (SELECT * FROM vehicle WHERE type='truck');
CREATE VIEW motorcycle AS (SELECT * FROM vehicle WHERE type='motorcycle');
All this is fine and dandy. But I run into an uncomfortable situation when I try to insert into these views:
INSERT INTO car (type, top_speed, license_plate)
VALUES
('car', 160, 'v4n1ty');
My issue is that I'm already inserting into a view called "car"... why should I have to bother to specify that type = 'car'?
If I omit the type column from this insert query I'll get an error that the type column isn't allowed to contain NULL. It seems like postgres won't default omitted values even when they could be gleaned from the view's definition.
Is there a way I can get postgres to look to the view's definition in order to provide defaults for omitted columns in INSERT queries?

There is very powerful rule system in PostgreSQL.
Additionally to your code:
create rule car_insert as on insert to car do instead
insert into vehicle(type, top_speed, license_plate)
values('car', new.top_speed, new.license_plate);
insert into car(top_speed, license_plate) values(160,'v4n1ty');
table car;
┌──────┬───────────┬───────────────┐
│ type │ top_speed │ license_plate │
├──────┼───────────┼───────────────┤
│ car │ 120 │ abc123 │
│ car │ 160 │ v4n1ty │
└──────┴───────────┴───────────────┘

Postgres can prevent you from inserting rows into a view that would not be visible in the view. The syntax is WITH CHECK OPTION at the end of CREATE VIEW.
Inferring column values from a view's where clause is not supported. You could simulate it with an instead of insert trigger:
CREATE FUNCTION insertCar() RETURNS trigger AS $$
BEGIN
INSERT INTO vehicle
(type, top_speed, license_plate)
VALUES
('car', new.top_speed, new.license_plate);
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER insertCarTrigger INSTEAD OF INSERT ON car
FOR EACH ROW EXECUTE PROCEDURE insertCar();

Related

Copy data from one table to another with different column names and column ordering in timescaledb

I have 2 timescaledb tables with different order of columns and even different column names for for some. Is it possible for me to copy data from one table to another table ?
Essentially the new table has hyper table on it, but the old one does not have hyper table on it.
I have looked at https://docs.timescale.com/timescaledb/latest/how-to-guides/migrate-data/same-db/
However it only seems to tell i need to have same column names and even the same column order in the create table syntax. Can you assist, am new to timescaledb
eg:
**table1**
id
price
datetime_string
**table2**
id
time
price
I created a minimal example here:
CREATE TABLE old_table ( id bigserial, time_string text NOT NULL, price decimal);
CREATE TABLE new_table ( time TIMESTAMP NOT NULL, price decimal);
SELECT create_hypertable('new_table', 'time');
INSERT INTO old_table (time_string, price) VALUES
('2021-08-26 10:09:00.01', 10.1),
('2021-08-26 10:09:00.08', 10.0),
('2021-08-26 10:09:00.23', 10.2),
('2021-08-26 10:09:00.40', 10.3);
INSERT INTO new_table SELECT time_string::timestamp as time, price from old_table;
Results:
playground=# \i move_data.sql
CREATE TABLE
CREATE TABLE
┌─────────────────────────┐
│ create_hypertable │
├─────────────────────────┤
│ (19,public,new_table,t) │
└─────────────────────────┘
(1 row)
INSERT 0 4
INSERT 0 4
playground=# table new_table;
┌────────────────────────┬───────┐
│ time │ price │
├────────────────────────┼───────┤
│ 2021-08-26 10:09:00.01 │ 10.1 │
│ 2021-08-26 10:09:00.08 │ 10.0 │
│ 2021-08-26 10:09:00.23 │ 10.2 │
│ 2021-08-26 10:09:00.4 │ 10.3 │
└────────────────────────┴───────┘
(4 rows)
Can you try to execute your select first and see if the relation matches the table structure?

Elegant way to check item in an array of ranges

About array of ranges (ex. int4range[]), and range functions.
Suppose table t as
CREATE TABLE t (id serial, r int4range[]);
INSERT INTO t (r) VALUES
('{"[2,5]","[100,200]"}'::int4range[]),
('{"[6,9]","[201,300]"}'::int4range[]);
So, to check if 7 or 70 are there, need a query like
SELECT * FROM (SELECT *, unnest(r) as ur FROM t) t2 WHERE 7<#ur; -- row2
SELECT * FROM (SELECT *, unnest(r) as ur FROM t) t2 WHERE 70<#ur; -- empty
There are a less ugly way to do this kind of queries?
NOTE: need a generic "template" to build functions that returns the table with simple * query,
CREATE FUNCTION t_where(int4) RETURNS t AS $f$
SELECT id,r -- but need *
FROM ( SELECT *, unnest(r) as ur FROM t) t2
WHERE $1 <# ur
$f$ LANGUAGE SQL IMMUTABLE;
So the same query is SELECT * FROM t_where(7), but I need (an elegant way) to build using *, not listing fields (id,r).
You don't need a subquery, you can use an implicit LATERAL JOIN, giving you access to t.* in the main SELECT:
#= SELECT t.*
FROM t, unnest(r) AS ur
WHERE 7<#ur;
┌────┬────────────────────────┐
│ id │ r │
├────┼────────────────────────┤
│ 2 │ {"[6,10)","[201,301)"} │
└────┴────────────────────────┘
(1 row)

PostgreSQL - Constraint on each custom type element in an array

Suppose I have this custom type:
CREATE TYPE post AS (
title varchar(100),
content varchar(10000)
);
And this table:
CREATE TABLE blogs (
-- Constraint on one custom type element:
one_post post NOT NULL
CHECK ((one_post).title IS NOT NULL AND (one_post).content IS NOT NULL),
-- Constraint on array elements:
posts post[] NOT NULL CHECK (???)
);
As commented in the code, I want to check that the title and content of each element of posts are NOT NULL, but I can't find a way to express that. What is the right way to do this?
(P.S.: I know the right way is to normalize my schema, I'm just curious if what I asked can be done.)
For complex check constraints usually used stored functions.
There is my attempt to make it in convenient way:
First of all:
create type post as (
title varchar(100),
content varchar(10000)
);
create function is_filled(post) returns bool immutable language sql as $$
select $1.title is not null and $1.content is not null
$$;
with t(x) as (values(('a','b')::post), (('a',null)::post), ((null,null)::post))
select *, is_filled(x), (x).is_filled from t;
╔═══════╤═══════════╤═══════════╗
║ x │ is_filled │ is_filled ║
╠═══════╪═══════════╪═══════════╣
║ (a,b) │ t │ t ║
║ (a,) │ f │ f ║
║ (,) │ f │ f ║
╚═══════╧═══════════╧═══════════╝
Here you can see two syntaxes of function calling (personally I like the second one - it is more OOP-like)
Next:
create function is_filled(post[]) returns bool immutable language sql as $$
select bool_and((x).is_filled) and $1 is not null from unnest($1) as x
$$;
with t(x) as (values(('a','b')::post), (('a',null)::post), ((null,null)::post))
select (array_agg(x)).is_filled from t;
╔═══════════╗
║ is_filled ║
╠═══════════╣
║ f ║
╚═══════════╝
Note that we are able to use functions with same name but with different parameters - it is Ok in PostgreSQL.
Finally:
create table blogs (
one_post post check ((one_post).is_filled),
posts post[] check ((posts).is_filled)
);
insert into blogs values(('a','b'), array[('a','b'),('c','d')]::post[]); -- Works
insert into blogs values(('a','b'), array[('a','b'),(null,'d')]::post[]); -- Fail
insert into blogs values(('a',null), array[('a','b')]::post[]); -- Fail
PS: script to clear our experiment:
/*
drop table if exists blogs;
drop function if exists is_filled(post);
drop function if exists is_filled(post[]);
drop type if exists post;
*/

Postgres: Create duplicates of existing rows, changing one value?

I am working in Postgres 9.4. I have a table that looks like this:
Column │ Type │ Modifiers
─────────────────┼──────────────────────┼───────────────────────
id │ integer │ not null default
total_list_size │ integer │ not null
date │ date │ not null
pct_id │ character varying(3) │
I want to take all values where date='2015-09-01', and create identical new entries with the date 2015-10-01.
How can I best do this?
I can get the list of values to copy with SELECT * from mytable WHERE date='2015-09-01', but I'm not sure what to do after that.
If the column id is serial then
INSERT INTO mytable (total_list_size, date, pct_id)
SELECT total_list_size, '2015-10-01', pct_id
FROM mytable
WHERE date = '2015-09-01';
else, if you want the ids to be duplicated:
INSERT INTO mytable (id, total_list_size, date, pct_id)
SELECT id, total_list_size, '2015-10-01', pct_id
FROM mytable
WHERE date = '2015-09-01';

How can I insert a row only if it does't already exist? [duplicate]

This question already has answers here:
Insert, on duplicate update in PostgreSQL?
(18 answers)
Closed 7 years ago.
I'm trying to insert a row but only if it is not already present in the table. At the moment I'm using code similar to the following:
insert into mentions (project_id, id, data_source, channel)
select 3, '123456789', 'example.com', 'twitter'
where not exists
(select 1 as "one" from mentions where (project_id = 3 and id = '123456789'))
returning reach;
But I sometimes see it fail with the following error in the logs
ERROR: duplicate key value violates unique constraint "mentions_pkey"
DETAIL: Key (project_id, id)=(3, 123456789) already exists.
and the constraint is defined as
Index "public.mentions_pkey"
┌────────────┬─────────┬────────────┐
│ Column │ Type │ Definition │
├────────────┼─────────┼────────────┤
│ project_id │ integer │ project_id │
│ id │ text │ id │
└────────────┴─────────┴────────────┘
primary key, btree, for table "public.mentions"
Since I'm only inserting when the exists clause is false, I don't understand how this can ever fail with a constraint violation.
Could it be a concurrency issue? I was under the assumption that individual statements are atomic but maybe this isn't the case?
I know that once 9.5 ships I can use ON CONFLICT DO NOTHING, but I'm running 9.4 at the moment.
use ON CONFLICT optional clause on insert:
insert into mentions (project_id, id, data_source, channel)
values (3, '123456789', 'example.com', 'twitter')
ON CONFLICT (project_id, id) DO NOTHING;
look: http://www.postgresql.org/docs/9.5/static/sql-insert.html#SQL-ON-CONFLICT
handle errors in stored procedure
CREATE OR REPLACE FUNCTION some_insert() RETURNS VOID AS
$$BEGIN
BEGIN
insert into mentions (project_id, id, data_source, channel)
select 3, '123456789', 'example.com', 'twitter';
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing
END;
END;$$
LANGUAGE plpgsql;
lock table
BEGIN;
LOCK TABLE mentions IN SHARE ROW EXCLUSIVE MODE;
INSERT INTO mentions (project_id, id, data_source, channel)
SELECT 3, '123456789', 'example.com', 'twitter'
WHERE NOT EXISTS
(SELECT 1 AS "one" FROM mentions WHERE (project_id = 3 AND id = '123456789'))
COMMIT;
handle errors on postgres' client side