PostgreSQL INSERT INTO within Function [duplicate] - sql

This question already has an answer here:
Function in PostgreSQL to insert from one table to another?
(1 answer)
Closed 8 years ago.
I got the tables:
General
CREATE TABLE general
(
equipo character varying(30) NOT NULL,
partidos_jug integer,
partidos_gana integer,
partidos_emp integer,
partidos_perd integer,
puntos integer,
goles_favor integer,
CONSTRAINT general_pkey PRIMARY KEY (equipo)
)
Equipos
create table equipos
(num_eqpo serial,
ciudad varchar (30),
num_gpo int,
nom_equipo varchar (30),
primary key (num_eqpo),
foreign key (num_gpo) references grupos (num_gpo))
Partidos
create table partidos
(semana int,
num_eqpo_loc int,
num_eqpo_vis int,
goles_loc int,
goles_vis int, primary key (semana,num_eqpo_loc,num_eqpo_vis),
foreign key (num_eqpo_loc) references equipos (num_eqpo),
foreign key (num_eqpo_vis) references equipos (num_eqpo))
I want to copy certain data from the table Equipos to the table General using:
CREATE OR REPLACE FUNCTION sp_tablageneral () RETURNS void AS $$
DECLARE cont int:= (SELECT count(num_eqpo)FROM equipos);
BEGIN
while cont>0
LOOP
INSERT INTO general(equipo,partidos_jug)
SELECT nom_equipo, COUNT(*) FROM equipos E, partidos as P
WHERE E.num_eqpo = cont AND (P.num_eqpo_loc=cont OR P.num_eqpo_vis=cont);
cont:= cont - 1;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
I had that function separated in small blocks of code:
INSERT INTO general(equipo)
SELECT nom_equipo FROM equipos E
WHERE E.num_eqpo = 1;
SELECT COUNT(*) FROM partidos as P WHERE (P.num_eqpo_loc=1 OR P.num_eqpo_vis=1)
I want to copy all the information from all the records, not only the first one.
Using the the function, I get this error:
ERROR: the "e.nom_equipo" column must appear in the GROUP BY clause or be used in an aggregate function
LINE 2: SELECT nom_equipo, COUNT (*) FROM equipos E, partid ...
               ^
Any idea why this error appears in that simple query?
Thanks a lot!

The table already exists, so you don't need to create it. You want to insert the rows in general:
INSERT INTO general(col1, col2, . . .)
SELECT col1, col2, . ..
FROM equipos E
WHERE E.num_eqpo = 2;
I'm not quite sure what which columns correspond to which. You should be explicit about the column lists from each table.

I got the answer.
What I did is to store in many variables the values from the Select queries and then I inserted them into the table General thanks to the function developed:
CREATE OR REPLACE FUNCTION sp_tablageneral () RETURNS void AS $$
DECLARE
cont int:= (SELECT count(num_eqpo)FROM equipos);
jugados int;
ganados int;
empatados int;
perdidos int;
pts int;
favor int;
BEGIN
while cont>0
LOOP
jugados:=(SELECT COUNT(*) FROM partidos as P WHERE (P.num_eqpo_loc=cont OR P.num_eqpo_vis=cont));
ganados:=(SELECT COUNT(*) FROM partidos AS P WHERE (P.num_eqpo_loc=cont AND P.goles_loc>P.goles_vis OR P.num_eqpo_vis=cont AND P.goles_vis>P.goles_loc));
empatados:=(SELECT COUNT(*) FROM partidos AS P WHERE (P.num_eqpo_loc=cont AND P.goles_loc=P.goles_vis OR P.num_eqpo_vis=cont AND P.goles_loc=P.goles_vis));
perdidos:=(SELECT COUNT(*) FROM partidos as P WHERE ( (P.num_eqpo_loc=cont AND P.goles_loc<P.goles_vis) OR (P.num_eqpo_vis=cont AND P.goles_loc>P.goles_vis)));
pts:=empatados*1 + ganados*3;
favor:=(SELECT SUM(goles_loc) FROM partidos as P WHERE P.num_eqpo_loc=cont + (SELECT SUM(goles_vis) FROM partidos as P WHERE P.num_eqpo_vis=cont));
INSERT INTO general(equipo,partidos_jug,partidos_gana,partidos_emp,partidos_perd,puntos,goles_favor)
SELECT nom_equipo,jugados,ganados,empatados,perdidos,pts,favor FROM equipos E
WHERE E.num_eqpo = cont;
cont:= cont - 1;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;

Related

Oracle SQL Trigger Syntax

I am trying to create a trigger that checks if the faculty member to be added to the assigned table will is already on the qualified table for a specified class. Perhaps this is a tedious method. Nevertheless, I'd still like to know what I'm doing wrong. The following is my code with created the created tables and the trigger being the last part of the code:
CODE:
CREATE TABLE Faculty (
FId varchar(10),
FName varchar(20),
CONSTRAINT Faculty_ID_pk PRIMARY KEY(FId)
);
CREATE TABLE Course (
CId varchar(10),
CName varchar(20),
CONSTRAINT Course_ID_pk PRIMARY KEY(CId)
);
CREATE TABLE Qualify (
QDate DATE,
FId varchar(10),
CId varchar(10),
CONSTRAINT Qualifying_date CHECK(QDate >= TO_DATE('2020-08-24', 'YYYY-MM-DD')),
CONSTRAINT Qualify_FID_fk FOREIGN KEY(FId) REFERENCES Faculty(FId),
CONSTRAINT Qualify_CID_fk FOREIGN KEY(CId) REFERENCES Course(CId)
);
CREATE TABLE Assign (
ADate DATE,
FId varchar(10),
CId varchar(10),
CONSTRAINT Qualifying_check CHECK(ADate > TO_DATE('2020-08-24', 'YYYY-MM-DD')),
CONSTRAINT Assign_FID_fk FOREIGN KEY(FId) REFERENCES Faculty(FId),
CONSTRAINT Assign_CID_fk FOREIGN KEY(CId) REFERENCES Course(CId)
);
CREATE OR REPLACE TRIGGER Check_If_Qualified
BEFORE INSERT ON Assign
FOR EACH ROW
DECLARE
v_facNum number;
BEGIN
SELECT f.FId
into v_facNum
from Faculty f
where f.facnum = :new.fid;
END;
However, I keep receiving an error saying:
Error at line 7: PLS-00225: subprogram or cursor 'F' reference is out of scope
v_facNum number;
BEGIN
SELECT f.FId
into v_facNum
from Faculty f
Does anyone know what could be wrong?
There are lots of issues in your code. You can check the count of records into FACULTY table and use that count for whatever logic you want.
CREATE OR REPLACE TRIGGER CHECK_IF_QUALIFIED
BEFORE INSERT ON ASSIGN
FOR EACH ROW
DECLARE
CNT NUMBER;
BEGIN
SELECT COUNT(1)
INTO CNT
FROM FACULTY
WHERE FACNUM = :NEW.FID;
END;
/
That would be something like this (sample tables are really just a sample; they are here to make the trigger compile):
SQL> create table assign (fid number);
Table created.
SQL> create table faculty (facnum number, fid number);
Table created.
SQL> CREATE OR REPLACE TRIGGER Check_If_Qualified
2 BEFORE INSERT ON Assign
3 FOR EACH ROW
4 DECLARE
5 v_facNum number;
6 BEGIN
7 SELECT f.FId
8 into v_facNum
9 from Faculty f
10 where f.facnum = :new.fid;
11
12 -- now do something with v_facNum
13 END;
14 /
Trigger created.
SQL>

List the 5 most expensive toys ordened by price(Postgres)

I want a list of the 5 toys that have cost more money, in descending order if we have delivering each toy every single time.
For example, I have an Ipad that cost 600€ and is requested by child 1, 2 and 3. Now, I have a Nintendo that cost 300€ and is requested 3 times (1 time by child 1 and 2 times by the child 3). I have another toy (Laptop) that cost 360€ and is requested 8 times (2 times by child 1 and 6 times by child 3). Then, I must see:
Laptop because is requested 8 times and cost 360€ (in total 8 * 360€ = 2880€)
Ipad because is requested 3 times and cost 600€ (in total 3 * 600€ = 1800€)
Nintendo
When I have this, I want to see the child's data that more times request the same toy, for example, in case of Nintendo I would like see information about child 2. In case of laptop I would like see information about child 3.
I create this type:
CREATE TYPE ToysList AS (
t_Toy_name VARCHAR(255),
t_Price REAL,
t_Times_requested INTEGER,
t_Total_amount_money REAL,
t_Child_name VARCHAR(255),
t_Child_times_request SMALLINT,
t_Child_address VARCHAR(255),
t_Number_Siblings SMALLINT);
The tables are these:
CREATE TABLE CHILD(
child_id SMALLINT,
child_name VARCHAR(255) NOT NULL,
birth_date DATE NOT NULL,
gender VARCHAR(255) NOT NULL,
address VARCHAR(255),
city VARCHAR(255),
CONSTRAINT PK_CHILD PRIMARY KEY(child_id),
CONSTRAINT VALID_GENDER CHECK (gender IN ('m', 'f')),
CONSTRAINT VALID_DATE CHECK (birth_date <= now())
);
CREATE TABLE letter (
letter_id SMALLINT NOT NULL,
arrival_date DATE DEFAULT now() NOT NULL,
number_toys INTEGER NOT NULL,
child_id SMALLINT,
CONSTRAINT valid_child_id CHECK ((child_id IS NOT NULL)),
CONSTRAINT PK_LETTER PRIMARY KEY(letter_id),
CONSTRAINT CHILD_FK FOREIGN KEY (child_id) REFERENCES CHILD(child_id)
);
CREATE TABLE SIBLING(
child_id1 SMALLINT,
child_id2 SMALLINT,
CONSTRAINT PK_SIBLING PRIMARY KEY(child_id1, child_id2),
CONSTRAINT CHILD1_FK FOREIGN KEY (child_id1) REFERENCES CHILD(child_id),
CONSTRAINT CHILD2_FK FOREIGN KEY (child_id2) REFERENCES CHILD(child_id)
);
CREATE TABLE TOY(
toy_id SMALLINT,
toy_name VARCHAR(255) NOT NULL,
price REAL NOT NULL,
toy_type VARCHAR(255) NOT NULL,
manufacturer VARCHAR(255),
CONSTRAINT PK_TOY PRIMARY KEY(toy_id),
CONSTRAINT POSITIVE_PRICE CHECK (price > 0),
CONSTRAINT VALID_TYPE CHECK(toy_type IN ('symbolic', 'rule', 'educational', 'cooperative', 'other'))
);
CREATE TABLE WISHED_TOY(
letter_id SMALLINT,
toy_id SMALLINT,
CONSTRAINT PK_WISHED_TOY PRIMARY KEY(letter_id, toy_id),
CONSTRAINT LETTER_FK FOREIGN KEY (letter_id) REFERENCES LETTER(letter_id),
CONSTRAINT TOY_FK FOREIGN KEY (toy_id) REFERENCES TOY(toy_id)
);
At this moment I did this:
CREATE OR REPLACE FUNCTION list_top_Toys() RETURNS SETOF ToysList AS $$
DECLARE
l_Toy_name VARCHAR(255);
l_Price REAL;
l_Times_requested INTEGER;--total times requested for this toy
l_Total_amount_money REAL; --total times requested * price toy
l_Child_name VARCHAR(255);
l_Child_times_request SMALLINT; --times request for the child
l_Child_address VARCHAR(255);
l_Number_Siblings SMALLINT;
l_toy_id INTEGER;
l_child_id INTEGER;
l_letter_id INTEGER;
returnset ToysList;
BEGIN
FOR l_toy_id, l_Toy_name, l_Times_requested, l_Total_amount_money
IN SELECT t.toy_id, t.toy_name, COUNT(*), SUM(price) AS totalAmountMoney
FROM toy t INNER JOIN wished_toy WT ON t.toy_id = WT.toy_id
GROUP BY t.toy_id, t.toy_name
ORDER BY totalAmountMoney DESC, t.toy_name
LIMIT 5
LOOP
returnset.t_Toy_name = l_Toy_name;
returnset.t_Times_requested = l_Times_requested;
returnset.t_Total_amount_money = l_Total_amount_money;
SELECT c.child_id, c.child_name, c.address, SUM(L.number_toys) AS totalToys
INTO l_child_id, l_Child_name, l_Child_address, l_Child_times_request
FROM child c
INNER JOIN letter L ON c.child_id = L.child_id
INNER JOIN wished_toy WIS ON WIS.letter_id = L.letter_id
WHERE c.child_id = l_child_id
GROUP BY c.child_id, c.child_name
ORDER BY totalToys DESC
LIMIT 1;
returnset.t_Child_name = l_Child_name;
returnset.t_Child_address = l_Child_address;
returnset.t_Child_times_request = l_Child_times_request;
SELECT COUNT(s.child_id2) AS numberSiblings
INTO l_Number_Siblings
FROM sibling s
INNER JOIN child c1 ON c1.child_id = s.child_id1
WHERE s.child_id1 = l_child_id
LIMIT 1;
returnset.t_Number_Siblings = l_Number_Siblings;
return next returnset;
END LOOP;
END;
$$LANGUAGE plpgsql;
COMMIT;
Can anyone say me what I am doing wrong?
Thank you,
Your function returns setof type of data. So, just select from it`s result, like this:
select * from list_top_Toys();
After that You can manipulate with results as it is table.
But, as I can see, this function needs much more changes.
Second query gives same results in every LOOP iteration, so I changed it to reflect result of first SELECT, and to make Letters table as leading in query.
At first, why group by toy_name - there need to be only group by toy_id.
Also, group by child_name (in first inner query) is redundant.
I would include toy_id in result set, it may be useful in later computations.
Also, You did not set toy price as you says in your post, so first SELECT have to have toy`s price.
So, my version of your function will be:
CREATE OR REPLACE FUNCTION list_top_Toys()
RETURNS SETOF ToysList
AS $$
DECLARE
l_Toy_name VARCHAR(255);
l_Price REAL;
l_Times_requested INTEGER;--total times requested for this toy
l_Total_amount_money REAL; --total times requested * price toy
l_Child_name VARCHAR(255);
l_Child_times_request SMALLINT; --times request for the child
l_Child_address VARCHAR(255);
l_Number_Siblings SMALLINT;
l_toy_id INTEGER;
l_child_id INTEGER;
l_letter_id INTEGER;
returnset ToysList;
BEGIN
FOR l_toy_id, l_Toy_name, l_Price, l_Times_requested, l_Total_amount_money
IN SELECT t.toy_id, t.toy_name, t.price, COUNT(*), SUM(price) AS totalAmountMoney
FROM toy t
INNER JOIN wished_toy WT ON t.toy_id = WT.toy_id
GROUP BY t.toy_id
ORDER BY totalAmountMoney DESC, t.toy_name
LIMIT 5
LOOP
returnset.t_Toy_name = l_Toy_name;
returnset.t_Price = l_price;
returnset.t_Times_requested = l_Times_requested;
returnset.t_Total_amount_money = l_Total_amount_money;
SELECT c.child_id, c.child_name, c.address, SUM(L.number_toys) AS totalToys
INTO l_child_id, l_Child_name, l_Child_address, l_Child_times_request
FROM letter L
INNER JOIN child c ON c.child_id = L.child_id
INNER JOIN wished_toy WIS ON WIS.letter_id = L.letter_id
WHERE wis.toy_id = l_toy_id
GROUP BY c.child_id, c.child_name
ORDER BY totalToys DESC
LIMIT 1;
returnset.t_Child_name = l_Child_name;
returnset.t_Child_address = l_Child_address;
returnset.t_Child_times_request = l_Child_times_request;
SELECT COUNT(s.child_id2) AS numberSiblings
INTO l_Number_Siblings
FROM sibling s
INNER JOIN child c1 ON c1.child_id = s.child_id1
WHERE s.child_id1 = l_child_id
LIMIT 1;
returnset.t_Number_Siblings = l_Number_Siblings;
return next returnset;
END LOOP;
END;
$$LANGUAGE plpgsql;
I did not touch siblings query.

SQL: Stored procedure

I am implementing a library management system in SQL. I have the following table structure and some values inserted in them:
create table books
(
IdBook number(5),
NameBook varchar2(35),
primary key(IdBook)
);
create table users
(
IdUsers number(5),
NameUser varchar2(20),
primary key(IdUsers)
);
create table borrowed
(
IdBorrowed number(5),
IdUsers number(5),
IdBook number(5),
DueDate date,
DateReturned date,
constraint fk_borrowed foreign key(IdUsers) references users(IdUsers),
constraint fk_borrowed2 foreign key(IdBook) references books(IdBook)
);
insert into books values(0,'FairyTale');
insert into books values(1,'Crime and Punishment');
insert into books values(2,'Anna Karenina');
insert into books values(3,'Norwegian Wood');
insert into users values(01,'Robb Dora');
insert into users values(02,'Pop Alina');
insert into users values(03,'Grozavescu Teodor');
insert into users values(04,'Popa Alin');
insert into borrowed values(10,02,3,'22-Jan-2017',null);
insert into borrowed values(11,01,1,'25-Jan-2017','19-Dec-2016');
insert into borrowed values(12,01,3,'22-Jan-2017',null);
insert into borrowed values(13,04,2,'22-Jan-2017','13-Dec-2016');
What I want now is that my db to allow "borrowing" books for the users(i.e insert into the borrowed table) that have no unreturned books(i.e date returned is not null) and if they have unreturned books I want to abandon the whole process. I thought to implement this in the following way:
create or replace procedure borrowBook(IdBorrowed in number,IdUsers number,IdBook number,DueDate date,DateReturned date) as begin
if exists (SELECT u.IdUsers, u.NameUser, b.DateReturned
FROM users u, borrowed b
WHERE u.IDUSERS = b.IdUsers and DateReturned is not null),
insert into borrowed values(IdBorrowed,IdUsers,IdBook,DueDate,DateReturned);
end borrowBook;
The above procedure does not check if the parameter I pass to this function is the same as the one in my select and I do not know how to do this and correctly insert a value in my table.
Any help would be much appreciated. Thank in advance!
You should not name your parameters the same as columns also used inside the procedure.
You can also simplify your procedure to a single INSERT statement, no IF required:
create or replace procedure borrowBook(p_idborrowed in number, p_idusers number, p_idbook number, p_duedate date, p_datereturned date)
as
begin
insert into borrowed (idborrowed, idusers, idbook, duedate, datereturned)
select p_idborrowed, p_idusers, p_idbook, p_duedate, p_datereturned
from dual
where not exists (select *
from users u
join borrowed b on u.idusers = b.idusers
and b.datereturned is not null);
end borrowBook;
It's also good coding style to explicitly list the columns for an INSERT statement. And you should get used to the explicit JOIN operator instead of using implicit joins in the where clause.
What about this one:
create or replace procedure borrowBook( p_IdBorrowed in number ,
p_IdUsers number ,
p_IdBook number ,
p_DueDate date ,
p_DateReturned date )
as
begin
if (SELECT COUNT(*)
FROM borrowed
WHERE IDUSERS = p_IdUsers
AND DateReturned IS NULL) = 0 THEN
insert into borrowed values (p_IdBorrowed ,
p_IdUsers ,
p_IdBook ,
p_DueDate ,
p_DateReturned );
end if ;
end borrowBook;
You would seem to want something like this:
create or replace procedure borrowBook (
in_IdBorrowed in number,
in_IdUsers number,
in_IdBook number,
in_DueDate date,
in_DateReturned date
) as
v_flag number;
begin
select (case when exists (select 1
from borrowed b
where b.IdUsers = in_IdUsers and b.DateReturned is not null
)
then 1 else 0
end)
into v_flag
from dual;
if (flag = 0) then
insert into borrowed
values(in_IdBorrowed, in_IdUsers, in_IdBook, in_DueDate, v_DateReturned);
end if
end -- borrowBook;

Optimize SQL query with 3 FOR loops

I have a fully working SQL query. However, it is very very slow. I am looking for a way to optimize it.
CREATE TABLE trajectory_geom (
id SERIAL PRIMARY KEY,
trajectory_id BIGINT,
user_id BIGINT,
geom GEOMETRY(Linestring, 4326)
);
INSERT INTO trajectory_geom (trajectory_id, user_id, geom)
SELECT
p.trajectory_id,
p.user_id,
ST_Transform(ST_MakeLine(p.geom), 4326)
FROM point p
GROUP BY p.trajectory_id
;
DO $$
DECLARE
urow record;
vrow record;
wrow record;
BEGIN
FOR wrow IN
SELECT DISTINCT(p.user_id) FROM point p
LOOP
raise notice 'User id: %', wrow.user_id;
FOR vrow IN
SELECT DISTINCT(p.trajectory_id) FROM point p WHERE p.user_id = wrow.user_id
LOOP
FOR urow IN
SELECT
analyzed_tr.*
FROM trajectory_start_end_geom analyzed_tr
WHERE
analyzed_tr.user_id = wrow.user_id
AND
ST_Intersects (
(
analyzed_tr.start_geom
)
,
(
SELECT g.geom
FROM trajectory_geom g
WHERE g.trajectory_id = vrow.trajectory_id
)
) = TRUE
LOOP
INSERT INTO trajectories_intercepting_with_starting_point (initial_trajectory_id, mathced_trajectory_id, user_id)
SELECT
vrow.trajectory_id,
urow.trajectory_id,
wrow.user_id
WHERE urow.trajectory_id <> vrow.trajectory_id
;
END LOOP;
END LOOP;
END LOOP;
END;
$$;
It has 3 loops...how can I avoid them?
Basically, I am looping all user IDs, for each user looping all trajectories and checking is trajectory interact with any other trajectory of this user.
Schema:
CREATE TABLE public.trajectory_start_end_geom
(
id integer NOT NULL DEFAULT nextval('trajectory_start_end_geom_id_seq'::regclass),
trajectory_id bigint,
user_id bigint,
start_geom geometry(Polygon,4326),
end_geom geometry(Polygon,4326),
CONSTRAINT trajectory_start_end_geom_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
CREATE TABLE public.trajectory_geom
(
id integer NOT NULL DEFAULT nextval('trajectory_geom_id_seq'::regclass),
trajectory_id bigint,
user_id bigint,
geom geometry(LineString,4326),
CONSTRAINT trajectory_geom_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
CREATE TABLE public.point
(
id integer NOT NULL DEFAULT nextval('point_id_seq'::regclass),
user_id bigint,
date date,
"time" time without time zone,
lat double precision,
lon double precision,
trajectory_id integer,
geom geometry(Geometry,4326),
CONSTRAINT point_pkey PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
Try this SQL query. Hope this helps.
INSERT INTO trajectories_intercepting_with_starting_point
(initial_trajectory_id, mathced_trajectory_id, user_id)
SELECT
TG.trajectory_id AS first_trajectory_id,
TG2.trajectory_id AS last_trajectory_id,
TG.user_id
FROM Trajectory_geom AS TG
JOIN Trajectory_geom AS TG2 ON TG.user_id = TG2.user_id
AND TG.trajectory_id < TG2.trajectory_id
JOIN Trajectory_start_end_geom AS TSE ON TSE.trajectory_id = TG.trajectory_id
WHERE ST_Intersects(TSE.start_geom, TG2.geom) = TRUE
This should do the trick:
WITH vrow AS(
INSERT INTO trajectory_geom (trajectory_id, user_id, geom)
SELECT
p.trajectory_id,
p.user_id,
ST_Transform(ST_MakeLine(p.geom), 4326) AS geom
FROM point p
GROUP BY p.trajectory_id
RETURNING trajectory_id, user_id, geom
)
INSERT INTO trajectories_intercepting_with_starting_point (initial_trajectory_id, mathced_trajectory_id, user_id)
SELECT
vrow.trajectory_id,
urow.trajectory_id,
vrow.user_id
FROM trajectory_start_end_geom AS urow
JOIN vrow
ON urow.user_id = vrow.user_id
AND urow.trajectory_id <> vrow.trajectory_id
AND ST_Intersects(urow.start_geom, vrow.geom)
If you don't need insert into trajectory_geom eliminating it (and the CTE) will speed it up

SQL - Efficient versioning of DNS records

So far I have come up with this solution that needs further refinement (big thanks to #postgresql on freenode).
The problem I am trying to overcome is an efficient way of storing DNS records whilst maintaining some sort of history. The issue I am currently having is with the wCTE which is inserting new records and deleting old records correctly. It isn't, however, readding records. The wCTE is:
WITH deltas AS (
SELECT o, n FROM (
SELECT id, name, domain_id, class_id, addr FROM record WHERE tld_id = $1
) AS o FULL OUTER JOIN record_temp n
ON (
o.name = n.name AND
o.domain_id = n.domain_id AND
o.class_id = n.class_id AND
o.addr = n.addr
)
WHERE (o.name, o.domain_id, o.class_id, o.addr)
IS DISTINCT FROM (n.name, n.domain_id, n.class_id, n.addr)
), mark_dead AS (
UPDATE record SET alive = FALSE
WHERE id IN (
SELECT (o).id FROM deltas WHERE (o).id IS NOT NULL
) RETURNING *
)
INSERT INTO record (name, domain_id, tld_id, class_id, addr)
SELECT (n).name, (n).domain_id, (n).tld_id, (n).class_id, (n).addr
FROM deltas WHERE
(n).name IS NOT NULL AND
(n).domain_id IS NOT NULL AND
(n).tld_id IS NOT NULL AND
(n).class_id IS NOT NULL AND
(n).addr IS NOT NULL
;
The o result has all the old records that do not exist in record_temp, n has all the records that are new and need to be inserted. I expect I need to add another join which pulls in (an inner join?) results that exist on both tables (which if marked as dead, need to be marked as alive).
The rest of the schema for reference is:
CREATE TABLE record (
id SERIAL,
name VARCHAR(255),
domain_id INT,
tld_id INT,
class_id INT,
addr INET,
alive BOOLEAN DEFAULT TRUE,
PRIMARY KEY (id),
CONSTRAINT fk1 FOREIGN KEY (domain_id) REFERENCES domain (id) MATCH SIMPLE,
CONSTRAINT fk2 FOREIGN KEY (tld_id) REFERENCES tld (id) MATCH SIMPLE,
UNIQUE(name, domain_id, class_id, addr)
);
CREATE TABLE record_history (
id SERIAL,
record_id INT,
history_type record_history_type,
stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk1 FOREIGN KEY (record_id) REFERENCES record (id) MATCH SIMPLE,
PRIMARY KEY(id)
);
CREATE TEMP TABLE record_temp (
name VARCHAR(255),
domain_id INT,
tld_id INT,
class_id INT,
addr INET,
UNIQUE(name, domain_id, class_id, addr)
)
ON COMMIT DROP;
record_history is populated using functions and triggers and is populating how I expect it to, below are these triggers:
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO record_history (record_id, history_type) VALUES (NEW.id, 'added');
RETURN NEW;
END;
$$ language 'plpgsql';
RETURNS TRIGGER AS $$
BEGIN
IF NEW.alive = OLD.alive THEN
RETURN NEW;
END IF;
IF NEW.alive THEN
INSERT INTO record_history (record_id, history_type) VALUES (NEW.id, 'added');
END IF;
IF NOT NEW.alive THEN
INSERT INTO record_history (record_id, history_type) VALUES (NEW.id, 'deleted');
END IF;
RETURN NEW;
END;
$$ language 'plpgsql';
ON record FOR EACH ROW EXECUTE PROCEDURE
add_insert_record_history();
ON record FOR EACH ROW EXECUTE PROCEDURE
add_update_record_history();
I seem to have it working how I want with the following query, which I feel is incredibly unoptimized:
WITH deltas AS (
SELECT o, n FROM (
SELECT id, name, domain_id, class_id, addr FROM record WHERE tld_id = $1
) AS o FULL OUTER JOIN record_temp n
ON (
o.name = n.name AND
o.domain_id = n.domain_id AND
o.class_id = n.class_id AND
o.addr = n.addr
)
WHERE (o.name, o.domain_id, o.class_id, o.addr)
IS DISTINCT FROM (n.name, n.domain_id, n.class_id, n.addr)
), mark_dead AS (
UPDATE record SET alive = FALSE
WHERE id IN (
SELECT (o).id FROM deltas WHERE (o).id IS NOT NULL
) RETURNING *
), mark_alive AS (
UPDATE record SET alive = TRUE
WHERE alive = FALSE AND id IN (
SELECT id FROM (
SELECT id, name, domain_id, class_id, addr FROM record WHERE tld_id = $1
) AS o INNER JOIN record_temp n
ON (
o.name = n.name AND
o.domain_id = n.domain_id AND
o.class_id = n.class_id AND
o.addr = n.addr
)
) RETURNING *
)
INSERT INTO record (name, domain_id, tld_id, class_id, addr)
SELECT (n).name, (n).domain_id, (n).tld_id, (n).class_id, (n).addr
FROM deltas WHERE
(n).name IS NOT NULL AND
(n).domain_id IS NOT NULL AND
(n).tld_id IS NOT NULL AND
(n).class_id IS NOT NULL AND
(n).addr IS NOT NULL
;