How does postgres db_link_build_sql_insert work? - postgresql

I can't seem to figure out how this function is supposed to work for pushing data across from one table in your local database to another on a separate database. I have looked at the documentation and still don't understand the example given. I am working with a postgres 9.2 which makes it possible to use dblink.
Here is some example code where I am creating a test database and pushing values from my local table to the table on the test database. Can someone please fill in the missing part of the dblink_build_sql_insert function?
--drop database if exists testdb;
--create database testdb;
drop table if exists t;
create table t ( a integer, b text);
insert into t values (1,'10'), (2,'10'), (3,'30'), (4,'30');
create extension if not exists dblink;
select dblink_connect('dbname=testdb');
select dblink('drop table if exists t;');
select dblink('create table t ( a integer, b text);');
select dblink_build_sql_insert('t', ????);
select * from dblink('select * from t;') as (a integer, b text);

from docs:
Synopsis
dblink_build_sql_insert(text relname,
int2vector primary_key_attnums,
integer num_primary_key_atts,
text[] src_pk_att_vals_array,
text[] tgt_pk_att_vals_array) returns text
You don't have PK specified, so I assume it is on (a), which automatically means, that primary_key_attnums = 1(PK on first column) and num_primary_key_atts=1 (one column PK). Two rest values made same to prepare statement ro "replicate" row with a=1 as is:
b=# select dblink_build_sql_insert('t',
'1'::int2vector,
1::int2, -- num of pkey values
'{1}'::text[], -- old pkey
'{1}'::text[] -- new pkey
)
;
dblink_build_sql_insert
-------------------------------------
INSERT INTO t(a,b) VALUES('1','10')
(1 row)
b=# select dblink($$INSERT INTO t(a,b) VALUES('1','10')$$);
dblink
----------------
("INSERT 0 1")
(1 row)
b=# select * from dblink('select * from t;') as (a integer, b text);
a | b
---+----
1 | 10
(1 row)
b=# select dblink_disconnect();
dblink_disconnect
-------------------
OK
(1 row)

Related

PostgreSQL and temp table triggers

I am looking for the best practice to define triggers and sequences on temporary tables with PostgreSQL.
When creating a temp table, PostgreSQL automatically creates a temporary schema with the name "pg_temp_nnn" (alias: "pg_temp")
It appears that one can create user functions and objects in this temporary schema.
I wonder if this is really valid SQL for PostgreSQL or just working by accident?
Tested with various PostgreSQL versions from 10 to 14.
Note: Triggers created on temp tables automatically land in the temp schema because the trigger inherits the schema of its table.
Tx!
CREATE TEMP TABLE tt1 (pk INTEGER NOT NULL, name VARCHAR(50));
CREATE SEQUENCE pg_temp.tt1_seq START 1;
CREATE FUNCTION pg_temp.tt1_srl() RETURNS TRIGGER AS
'DECLARE ls BIGINT;
BEGIN
SELECT INTO ls nextval(''pg_temp.tt1_seq'');
IF new.pk ISNULL OR new.pk=0 THEN
new.pk:=ls;
ELSE
IF new.pk>=ls THEN
PERFORM setval(''pg_temp.tt1_seq'',new.pk);
END IF;
END IF;
RETURN new;
END;'
LANGUAGE 'plpgsql';
CREATE TRIGGER tt1_srlt BEFORE INSERT ON tt1 FOR EACH ROW EXECUTE PROCEDURE pg_temp.tt1_srl();
INSERT INTO tt1 (name) VALUES ('aaaa');
SELECT 'Insert #1:', currval('pg_temp.tt1_seq');
INSERT INTO tt1 VALUES (0,'bbbb');
SELECT 'Insert #2:', currval('pg_temp.tt1_seq');
INSERT INTO tt1 VALUES (100,'cccc');
SELECT 'Insert #3:', currval('pg_temp.tt1_seq');
INSERT INTO tt1 (name) VALUES ('dddd');
SELECT 'Insert #4:', currval('pg_temp.tt1_seq');
SELECT * FROM tt1 ORDER BY pk;
Output:
CREATE TABLE
CREATE SEQUENCE
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 1
?column? | currval
------------+---------
Insert #1: | 1
(1 row)
INSERT 0 1
?column? | currval
------------+---------
Insert #2: | 2
(1 row)
INSERT 0 1
?column? | currval
------------+---------
Insert #3: | 100
(1 row)
INSERT 0 1
?column? | currval
------------+---------
Insert #4: | 101
(1 row)
pk | name
-----+------
1 | aaaa
2 | bbbb
100 | cccc
101 | dddd
(4 rows)
Yes, that works and is supported.
Creating objects in schema pg_temp creates temporary objects that will be removed when the session ends. CREATE TEMP TABLE x (...) is the same as CREATE TABLE pg_temp.x (...).

Postgres 10: do rows automatically move between partitions?

Assuming I have a parent table with child partitions that are created based on the value of a field.
If the value of that field changes, is there a way to have Postgres automatically move the row into the appropriate partition?
For example:
create table my_table(name text)
partition by list (left(name, 1));
create table my_table_a
partition of my_table
for values in ('a');
create table my_table_b
partition of my_table
for values in ('b');
In this case, if I change the value of name in a row from aaa to bbb, how can I get it to automatically move that row into my_table_b.
When I tried to do that, (i.e. update my_table set name = 'bbb' where name = 'aaa';), I get the following error:
ERROR: new row for relation "my_table_a" violates partition constraint
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=f0e44751d7175fa3394da2c8f85e3ceb3cdbfe63
it doesn't handle updates that cross partition boundaries.
thus you need to create one yourself... here's your set:
t=# insert into my_table select 'abc';
INSERT 0 1
t=# insert into my_table select 'bcd';
INSERT 0 1
t=# select tableoid::regclass,* from my_table;
tableoid | name
------------+------
my_table_a | abc
my_table_b | bcd
(2 rows)
here's rule and fn():
t=# create or replace function puf(_j json,_o text) returns void as $$
begin
raise info '%',': '||left(_j->>'name',1);
execute format('insert into %I select * from json_populate_record(null::my_table, %L)','my_table_'||left(_j->>'name',1), _j);
execute format('delete from %I where name = %L','my_table_'||left(_o,1), _o);
end;
$$language plpgsql;
CREATE FUNCTION
t=# create rule psr AS ON update to my_table do instead select puf(row_to_json(n),OLD.name) from (select NEW.*) n;
CREATE RULE
here's update:
t=# update my_table set name = 'bbb' where name = 'abc';
INFO: : b
puf
-----
(1 row)
UPDATE 0
checking result:
t=# select tableoid::regclass,* from my_table;
tableoid | name
------------+------
my_table_b | bcd
my_table_b | bbb
(2 rows)
once again:
t=# update my_table set name = 'a1' where name = 'bcd';
INFO: : a
puf
-----
(1 row)
UPDATE 0
t=# select tableoid::regclass,* from my_table;
tableoid | name
------------+------
my_table_a | a1
my_table_b | bbb
(2 rows)
Of course using json to pass NEW record looks ugly. And it is ugly indeed. But I did not have time to study the new PARTITION feature of 10, so don't know the elegant way to do this task. Hopefully I could give the generic idea of how you can possible solve the problem and you will produce a better neat code.
update
its probablygood idea to limit such rule to ON update to my_table where left(NEW.name,1) <> left(OLD.name,1) do instead, to release the heavy manipulations need

Import CSV Table Definitions into PostgreSQL

I have a file of table definitions, in the following format
Table Name Field Name Field Data Type
ATableName1 AFieldName1 VARCHAR2
ATableName1 AFieldName2 NUMBER
...
ATableNameX AFieldNameX1 TIMESTAMP(6)
Is there any easy way to import this into Postgres to automatically create the tables?
What if I split the file up into individual tables, and just had a csv of field names/data types for each table?
Field Name Field Data Type
AFieldName1 VARCHAR2
AFieldName2 NUMBER
My searching has only yielded data import via copy, and table creation (based on data) using pgfutter.
mind I change varchar2 to varchar and number to integer.alsoyou have tsv - in order to use it, change chr(44) in my code to chr(9). Mind I dont check for injection, otherwise here's working example:
t=# do
$$
declare
_r record;
begin
for _r in (
with t(l) as (values('ATableName1,AFieldName1i, VARCHAR
ATableName1,AFieldName2,INTEGER
ATableNameX,AFieldNameX1,TIMESTAMP(6)'::text)
)
, r as (select unnest(string_to_array(l,chr(10))) rw from t)
, p as (select split_part(rw,chr(44),1) tn, split_part(rw,chr(44),2) cn,split_part(rw,chr(44),3) tp from r)
select tn||' ('||string_agg(cn||' '||tp, ', ')||')' s from p
group by tn
) loop
raise info '%','create table '||_r.s;
execute 'create table '||_r.s;
end loop;
end;
$$
;
INFO: create table ATableNameX (AFieldNameX1 TIMESTAMP(6))
INFO: create table ATableName1 (AFieldName1i VARCHAR, AFieldName2 INTEGER)
DO
Time: 16.743 ms
t=# \dt AF
t=# \dt atablename*
List of relations
Schema | Name | Type | Owner
--------+-------------+-------+-------
public | atablename1 | table | vao
public | atablenamex | table | vao
SQL is your friend, it is very expressive, you can construct your tables def using string_agg function. Have a look on the example here.
http://sqlfiddle.com/#!17/0fe14/1

How can I get result by using “execute ‘delete from table1'"

when I use execute command to run a sql cmd, I want to get the result of it.
As we know, I can get total counts by variable sc when I use :
execute 'select * from table" into sc;
But How can I get result by using:
execute 'delete from table1'"?
when I use INTO, it turns out
ERROR: "INTO used with a command that cannot return data"
execute 'WITH row_deleted AS (DELETE FROM table1 RETURNING *) SELECT count(*) FROM row_deleted' into c;
You can use it inside a plsql funtion as following:
--Drop the table and the functin if it exist:
DROP TABLE IF EXISTS table1;
DROP FUNCTION if exists _deleted_rows();
--Create the table for the example:
CREATE TABLE table1
(
row_id serial NOT NULL,
col1 character varying,
CONSTRAINT table1_pkey PRIMARY KEY (row_id)
);
--Insert some rows:
insert into table1 (col1) values ('test1');
insert into table1 (col1) values ('test2');
insert into table1 (col1) values ('test3');
--Ctreate the function that count the number of deleted rows of the table: table1
CREATE OR REPLACE FUNCTION _deleted_rows()
RETURNS character varying AS
$BODY$declare
nbr_deleted integer;
begin
execute 'WITH row_deleted AS (DELETE FROM table1 RETURNING *) SELECT count(*) FROM row_deleted' into nbr_deleted;
return (nbr_deleted);
end;$BODY$
LANGUAGE plpgsql VOLATILE;
Test that function (got problem building shema on sqlfidlle):
select * from _deleted_rows();
_deleted_rows
---------------
3
(1 ligne)
Execute command
DELETE command
It's a little unclear to me what you are trying to do, but you should be able use "RETURNING". Here I am just returning the rows that were deleted:
CREATE TEMP TABLE foo(id int, description text);
INSERT INTO foo VALUES
(1, 'HELLO'),
(2, 'WORLD');
DELETE FROM foo returning *;
+----+-------------+
| id | description |
+----+-------------+
| 1 | HELLO |
| 2 | WORLD |
+----+-------------+
(2 rows)
Also, if you need them moved "into" a table (for example), you could do something like:
DROP TABLE IF EXISTS foo;
DROP TABLE IF EXISTS deleted_foo;
CREATE TEMP TABLE foo(id int, description text);
INSERT INTO foo VALUES
(1, 'HELLO'),
(2, 'WORLD');
CREATE TEMP TABLE deleted_foo(id int, description text);
WITH x AS (DELETE FROM foo RETURNING *)
INSERT INTO deleted_foo
SELECT * FROM x;
SELECT * FROM deleted_foo;
+----+-------------+
| id | description |
+----+-------------+
| 1 | HELLO |
| 2 | WORLD |
+----+-------------+
(2 rows)
Assuming that you are doing this from inside a plpgsql function, you could also use the ROW_COUNT variable. For example:
GET DIAGNOSTICS integer_var = ROW_COUNT;
This would give you the number of rows that were deleted.

Using return value from DELETE for UPDATE in Postgres

I need to update a table using a value deleted from another table. The situation is a comment vote scorekeeper similar to the one on SO. I'm using python to work the postgres, but that shouldn't make a difference.
query="""
UPDATE comment SET score=score-(DELETE FROM history
WHERE commentId=%(commentId)s AND
userIdentity=%(userIdentity)s RETURNING vote)
WHERE commentId=%(commentId)s;
"""
cursor.execute(query, data)
The error arises at (DELETE FROM; a syntax error arises. I can replace the DELETE statement with a SELECT statement and it will work, is there something I am missing here? I want to use the returning value in an update. Is this possible? Anything helps.
Relevent schema:
CREATE TABLE history (
commentId bigint,
vote int,
userIdentity varchar(256),
);
CREATE TABLE comment (
id bigint,
score bigint,
);
history.vote is normally 1 or -1.
PostgreSQL doesn't allow mix UPDATE and DELETE statements as subquery.
You can use a little bit different strategy - updateable CTE
postgres=# WITH t1 AS (DELETE FROM foo RETURNING *),
t2 AS (INSERT INTO deleted
SELECT * FROM t1 RETURNING *)
SELECT max(a) FROM t2;
so
postgres=# CREATE TABLE comment(id int, score int);
CREATE TABLE
postgres=# CREATE TABLE history(id int, comment_id int, vote int);
CREATE TABLE
postgres=# INSERT INTO comment values(1,10);
INSERT 0 1
postgres=# INSERT INTO comment values(2,20);
INSERT 0 1
postgres=# INSERT INTO history values(1,1,5);
INSERT 0 1
postgres=# WITH t1 AS (DELETE FROM history
WHERE id=1
RETURNING comment_id, vote)
UPDATE comment SET score=score-t1.vote
FROM t1
WHERE t1.comment_id=comment.id;
UPDATE 1
postgres=# select * from comment;
id | score
----+-------
2 | 20
1 | 5
(2 rows)
Attention: It require 9.1 or newer