I have two tables which are not fully equal but similar. They look like this:
CREATE TABLE FIRST_TABLE(
FIRST_ID RAW(16) NOT NULL CONSTRAINT FIRST_PK PRIMARY KEY,
FIRST_NAME VARCHAR2(2000),
FIRST_VALID NUMBER(1) NOT NULL,
AUDIT_CRE_AT TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
AUDIT_CRE_FROM VARCHAR2(32) DEFAULT 'system' NOT NULL,
CONSTRAINT SECOND_VALID CHECK (SECOND_VALID IN (0,1))
);
CREATE TABLE SECOND_TABLE(
SECOND_ID RAW(16) NOT NULL CONSTRAINT SECOND_PK PRIMARY KEY,
SECOND_NAME VARCHAR2(2000),
AUDIT_CRE_AT TIMESTAMP(0) DEFAULT CURRENT_TIMESTAMP NOT NULL,
AUDIT_CRE_FROM VARCHAR2(32) DEFAULT 'system' NOT NULL,
);
Now I want to migrate all records from the FIRST_TABLE into the SECOND_TABLE if the record is valid. A record is valid, if FIRST_VALID is 1.
That is what I want to to:
Select all valid records from FIRST_TABLE
SELECT * FROM FIRST_TABLE f WHERE f.FIRST_VALID like '1';
Loop through all records I got from the select query and INSERT everything except VALID INTO the SECOND_TABLE and DELETE them.
Thats where I got problems. This is what I have:
DECLARE
v_count_valids NUMBER(10):= 0;
v_i NUMBER(10):=0;
BEGIN
SELECT COUNT(*) INTO v_count_valids FROM FIRST_TABLE f WHERE f.VALID like '1';
WHILE v_i <= v_count_valids
LOOP
--INSERT INTO SECOND_TABLE VALUES (current record...)
--DELETE current record
v_i := v_i + 1;
END LOOP;
END;
Can you give me a hint how I can access the current in the loop record to insert some values into the other table? Or is there a better way to do this?
Just use insert . . . select:
insert into second_table(second_id, second_name, audit_cre_at, audit_cre_from)
SELECT first_id, first_name, audit_cre_at, audit_cre_from
FROM FIRST_TABLE f
WHERE f.FIRST_VALID = 1;
You don't need PL/SQL for this. Also, when comparing numbers, use numeric constants and =, not like and strings.
You don't need a loop. You could use an insert-select statement instead:
INSERT INTO second_table (second_id, second_name, audit_cre_at, audit_cre_from)
SELECT first_id, first_name, audit_cre_at, audit_cre_from
FROM first_table
WHERE valid = 1
As others mentioned, no need of writing PL/SQL code. First, put all your specific data from First_Table to Second_Table using below query.
INSERT INTO SECOND_TABLE(SECOND_ID, SECOND_NAME, AUDIT_CRE_AT, AUDIT_CRE_FROM)
SELECT FIRST_ID, FIRST_NAME, AUDIT_CRE_AT, AUDIT_CRE_FROM FROM FIRST_TABLE WHERE FIRST_VALID = 1;
And then, as you mentioned, you need to delete them. Then, write delete query
DELETE FROM FIRST_TABLE WHERE FIRST_VALID = 1;
Related
Im new with SQL, and I want to knw how can I add a SQL in this structure to check if the row already exists before insert, Im tried this way, but I think its not the best.
DECLARE
v_project_id NUMBER;
v_group_name VARCHAR2(100);
v_user_id NUMBER;
v_group_id NUMBER;
BEGIN
FOR project IN (
SELECT project_id
FROM JSON_TABLE(:p_request,
'$' COLUMNS(NESTED PATH '$.projects[*]'
COLUMNS(
project_id VARCHAR2(100) PATH '$.project.id'
)
)
)
)
LOOP
v_project_id := project.project_id;
v_group_name := json_value (:p_request,'$.group_name');
v_user_id := json_value (:p_request,'$.user_id');
v_group_id := s_project_group.NEXTVAL;
dbms_output.put_line(v_group_id||' - '||v_group_name||' - '||v_user_id||' - '||v_project_id);
--------- HERE, I WANT TO CHECK IF THE NEW ROW ALREADY EXISTS BEFORE INSERT, IM TRIED THIS WAY, BUT I THINK ITS NOT THE BEST------------
(select case
when NOT exists (select 1 FROM APP_PROJECT_GROUP WHERE GROUP_NAME = :v_group_name AND USER_ID = :v_user_id)
INSERT INTO APP_PROJECT_GROUP (GROUP_ID, GROUP_NAME, PROJECT_ID, USER_ID) VALUES (v_group_id, v_group_name, v_project_id, v_user_i)
);
----------------------------------------------------------------------------------------------------------------------------------------
COMMIT;
END LOOP;
END;
As I commented on your previous question, use a MERGE statement then you can perform all the INSERTs in a single SQL statement (without having to use use cursor loops and repeatedly context-switching from PL/SQL to SQL):
MERGE INTO app_project_group dst
USING (
SELECT group_id,
group_name,
TO_NUMBER(project_id) AS project_id,
user_id
FROM JSON_TABLE(
:p_request,
'$'
COLUMNS(
group_name VARCHAR2(150) PATH '$.group_name',
group_id NUMBER(15) PATH '$.group_id',
user_id NUMBER(15) PATH '$.user_id',
NESTED PATH '$.projects[*]'
COLUMNS (
project_id VARCHAR2(15) PATH '$.project.id'
)
)
)
) src
ON (src.group_name = dst.group_name AND src.user_id = dst.user_id)
WHEN NOT MATCHED THEN
INSERT (group_id, group_name, project_id, user_id)
VALUES (s_project_group.NEXTVAL, src.group_name, src.project_id, src.user_id);
db<>fiddle here
So I have been trying to find the best way to re-write a large chunk of SQL in plan script, in form of
WITH
A AS (...<SUB_QA>...),
B AS (...<SUB_QB>...),
C AS (...<SUB_QC>...),
...
SELECT ... FROM
A
LEFT JOIN B
LEFT JOIN C
LEFT JOIN ...
ON ....
into a FUNCTION. This is mainly to facilitate the reuse the same logic represented by that big chunk in multiple places.
Contraint 1: can only use RECORD instead of creating customized TYPE;
Contraint 2: have to keep the content of the those subqueries (e.g.
, etc) under WITH clause as they are, since each is
considerably complex.
So far, I only came up with the following, as a simplified example.
It involves putting the WITH clause in the cursor loop
But does it run the WITH clause in each loop, which would be a big worry in term of performance? When written in function form, and when I run it with SqlDeveloper's 'Explain Plan' function, it doesn't reveal much helpful information at all.
Is there better/cleaner/more performant way to do this?
SQL to create data:
--------PERSON table------------
DROP TABLE Test_Persons;
CREATE TABLE Test_Persons (
PersonID int,
LastName varchar2(255),
FirstName varchar2(255)
);
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(1,'LN_1','FN_1');
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(2,'LN_2','FN_2');
INSERT INTO Test_Persons
(PersonID,LastName,FirstName)
values(3,'LN_21','FN_2');
--------Salary table------------
DROP TABLE TEST_SALARY_A;
CREATE TABLE TEST_SALARY_A ( -- no 'OR REPLACE' for ORACLE
SalaryID int,
PersonID int,
Amount int,
Tax int,
Bank varchar2(20)
);
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(1, 1, 1000, 300, 'BOA1');
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(2, 2, 2000, 600, 'JP1');
INSERT INTO TEST_SALARY_A
(SalaryID, PersonID, Amount, Tax, Bank)
VALUES
(3, 3, 3000, 900, 'TD1');
--------Address table------------
DROP TABLE TEST_ADDRESS_A;
CREATE TABLE TEST_ADDRESS_A (
AddressID int,
PersonID int,
Address varchar2(255)
);
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(1, 1, 'address1');
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(2, 2, 'address2');
INSERT INTO TEST_ADDRESS_A
(AddressID, PersonID, Address)
VALUES
(3, 3, 'address3');
commit;
Original SQL in Chunk:
------------------Original--------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid WHERE tps.LASTNAME = 'LN_1'
),
TEST_JOINED_2 AS (
SELECT
tps.PERSONID,
tsl.BANK,
tsl.TAX
FROM TEST_PERSONS tps
LEFT JOIN TEST_SALARY_A tsl ON tps.personid = tsl.personid WHERE tps.LASTNAME = 'LN_1'
)
SELECT tj1.PERSONID as tj1_ID, tj1.LASTNAME, tj1.ADDRESS, tj2.PERSONID as tj2_ID, tj2.BANK, tj2.TAX
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_JOINED_2 tj2 ON tj1.PERSONID = tj2.PERSONID
WHERE tj1.LASTNAME = 'LN_1';
Rewrite in FUNCTION:
------------------Rewritten in functions with ------------------
------------------Contraint 1: can only use RECORD instead of creating customized TYPE;------------
------------------Contraint 2: have to keep the content of the two subqueries under WITH clause exactly as it is --------------
CREATE OR REPLACE PACKAGE MY_JOIN_TEST_SP_PACKAGE_3 AS
TYPE join_record_type IS RECORD(
PersonID1 int,
LastName varchar2(255),
Address varchar2(255),
PersonID2 int,
Bank varchar2(20),
Tax int
);
TYPE join_record_table_type IS TABLE OF join_record_type;
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY MY_JOIN_TEST_SP_PACKAGE_3 AS
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED
AS
join_record join_record_type;
BEGIN
FOR x IN (
-------------------start - WITH clause -- does this run for every RECORD x in the loop??? -----------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid WHERE tps.LASTNAME = last_name
),
TEST_JOINED_2 AS (
SELECT
tps.PERSONID,
tsl.BANK,
tsl.TAX
FROM TEST_PERSONS tps
LEFT JOIN TEST_SALARY_A tsl ON tps.personid = tsl.personid WHERE tps.LASTNAME = last_name
)
-------------------end - WITH clause -------------------
-------------------start - main select-----------------------
SELECT tj1.PERSONID as tj1_ID, tj1.LASTNAME, tj1.ADDRESS, tj2.PERSONID as tj2_ID, tj2.BANK, tj2.TAX
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_JOINED_2 tj2 ON tj1.PERSONID = tj2.PERSONID
WHERE tj1.LASTNAME = last_name
-------------------end - main select--------------------------
)
LOOP
SELECT x.tj1_ID, x.LASTNAME, x.ADDRESS, x.tj2_ID, x.BANK, x.TAX
INTO join_record
FROM DUAL;
PIPE ROW (join_record);
END LOOP;
END;
END; -- END of CREATE
/
select * from table(MY_JOIN_TEST_SP_PACKAGE_3.get_joined_data('LN_1'));
EDITED: modified example code to have variable in the WITH clause
----------------------Create GLOBAL TEMPORARY TABLE -------------------------
DROP TABLE my_global_temp_table;
CREATE GLOBAL TEMPORARY TABLE my_global_temp_table (
PersonID int,
LastName varchar2(255),
Address varchar2(255),
Bank varchar2(20)
)
ON COMMIT DELETE ROWS;
----------------------Create PACKAGE AND FUNCTION -------------------------
CREATE OR REPLACE PACKAGE MY_JOIN_TEST_SP_PACKAGE_3 AS
TYPE join_record_type IS RECORD(
PersonID int,
LastName varchar2(255),
Address varchar2(255),
Bank varchar2(20)
);
TYPE join_record_table_type IS TABLE OF join_record_type;
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY MY_JOIN_TEST_SP_PACKAGE_3 AS
FUNCTION get_joined_data(last_name VARCHAR2)
RETURN join_record_table_type
PIPELINED
AS
join_record join_record_type;
BEGIN
--------------------use GLOBAL TEMPORARY TABLE-------------------------
INSERT INTO my_global_temp_table
-------------------start - WITH ... SELECT ... clause -- does this run for every RECORD x in the loop??? -----------------------
WITH
TEST_JOINED_1 AS (
SELECT
tps.PERSONID,
tps.LASTNAME,
tsd.ADDRESS
FROM TEST_PERSONS tps
LEFT JOIN TEST_ADDRESS_A tsd ON tps.personid = tsd.personid
WHERE tps.LASTNAME = last_name
)
SELECT tj1.PERSONID, tj1.LASTNAME, tj1.ADDRESS, ts.BANK
FROM TEST_JOINED_1 tj1
LEFT JOIN TEST_SALARY_A ts ON tj1.PERSONID = ts.PERSONID
WHERE tj1.LASTNAME = last_name;
-------------------end - WITH ... SELECT ... clause --
FOR x IN (
SELECT * FROM my_global_temp_table
)
LOOP
SELECT x.PERSONID, x.LASTNAME, x.ADDRESS, x.BANK
INTO join_record
FROM DUAL;
PIPE ROW (join_record);
END LOOP;
END;
END; -- END of CREATE
/
--------------------Call the FUNCTION-------------------------
select * from table(MY_JOIN_TEST_SP_PACKAGE_3.get_joined_data('LN_1'));
EDITED: following #Littefoot suggestion, try out using CREATE GLOBAL TEMP table but giving out '17/21 PL/SQL: ORA-00936: missing expression'. I am not sure why?
EDITED: Corrected the Insert syntax, but will get error 'ORA-14551: cannot perform a DML operation inside a query', I believe that this is because I called the function that contains that Insert from a SELECT
If you are not using variables and other pl sql constructs, I would suggest you break with clauses in the form of a table or materialize view. In this way you don't need to take risk of rewriting the logic of query in pl sql block and missing something.
I would suggest using materialize view advantage of materialize view over the table is that you don't need to drop table next time you load data and also you can use nologging with materialized view.No logging makes it very fast.
It will be very fast and have minimum risk.
Thanks
Bhanu Yadav
As there's nothing dynamic in the WITH factoring clause (i.e. you don't use variables - at least, I didn't notice any), I'd suggest you to create a view (based on that WITH) and use it whenever needed.
If the real query is really complex and takes time to execute, you could create a global temporary table (GTT), most probably choosing to keep its data during the session (ON COMMIT PRESERVE ROWS) properly index it and store view (or WITH's) contents in there. Then you'd use the GTT in your code
Although, Oracle will keep date returned by a query in memory so you might even have to really "execute" it once, but the memory isn't unlimited so ... test it, compare results you get, pick the one that seems to be the best.
To me, the GTT idea sounds promising, but without actual information it is difficult to decide.
[EDIT, about GTT]
Oracle's "Global temporary table" is, actually, "local" from your point if view (note that, if you're on 18c (I don't think you are, though) you can create a private temporary table). You create it once, using the create global temporary table .... Data you insert into it is visible ONLY to you, nobody else; it is restricted to your own transaction (if it is created with the ON COMMIT DELETE ROWS) or session (ON COMMIT PRESERVE ROWS). Pick the one that suits you best.
What does it mean? It means that you'd create the GTT once, providing column list and their datatypes. Every user, that uses your procedure, would insert its own data set into it (you'd use a query with the LAST NAME parameter, as you said) and use it throughout the transaction (or session). Many users can do that at the same time, but - as I've said - everyone would see only its own data.
Here's the pseudocode:
-- create table once. Do NOT create it, drop it, create again tomorrow, drop ...
-- Create it once, use it many times.
create global temporary table gtt_my_data
(id number,
c_name varchar2(20), ...
)
on commit preserve rows;
create index i1_gmd_id on gtt_my_data (id);
-- your procedure
procedure p_myproc (par_last_name in varchar2) is
begin
insert into gtt_my_data (id, c_name, ...)
select id, c_name, ...
from some_table join some_other_table ...
where some_table.last_name = par_last_name;
-- now, do whatever you do. When you need to fetch data from the GTT, do so
select ... into ...
from table_x join gtt_my_data on ...
update ... set some_column = (select another_column
from table_y join gtt_my_data on ...
)
end;
Once you're done: if you end the session, data will be removed from the GTT. If you want, you can do it manually (either delete or truncate its contents).
[EDIT #2: INSERT INTO A GTT]
Insert is wrong; you don't insert values, but something like this:
INSERT INTO my_global_temp_table
WITH test_joined_1 AS
(SELECT tps.personid,
tps.lastname,
tsd.address
FROM test_persons tps
LEFT JOIN test_address_a tsd ON tps.personid = tsd.personid
WHERE tps.lastname = last_name
)
SELECT tj1.personid,
tj1.lastname,
tj1.address,
ts.bank
FROM test_joined_1 tj1
LEFT JOIN test_salary_a ts ON tj1.personid = ts.personid
WHERE tj1.lastname = last_name;
Simplified, on Scott's schema:
SQL> create table test (empno number, deptno number);
Table created.
SQL> insert into test (empno, deptno)
2 with temp as
3 (select empno, deptno from emp)
4 select t.empno, t.deptno
5 from temp t join dept d on d.deptno = t.deptno
6 where d.deptno = 10;
3 rows created.
SQL>
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;
I have two tables and I try to insert rows from SECOND_TABLE to FIRST_TABLE.
I create two tables:
CREATE TABLE FIRST_TABLE(
F_ID NUMBER(10) not null,
F_NAME VARCHAR(8 BYTE) not null,
F_DESCRIPTION NVARCHAR2(1000) not null
);
CREATE TABLE SECOND_TABLE(
S_ID NUMBER(10) not null,
S_NAME VARCHAR(8 BYTE) not null,
S_DESCRIPTION NVARCHAR2(1000) not null
);
I found differences between SECOND_TABLE and FIRST_TABLE:
select S_NAME,S_DESCRIPTION from SECOND_TABLE minus select F_NAME,F_DESCRIPTION from FIRST_TABLE;
I create a statement:
insert into FIRST_TABLE F_ID,F_NAME,F_DESCRIPTION (select * from SECOND_TABLE where not exists (select * from FIRST_TABLE where SECOND_TABLE.S_NAME = FIRST_TABLE.F_NAME));
but this statement copy F_ID, I don't have idea how to change this statement to not copy F_ID, but generate it from sequence CSS_F. It should looks like:
insert into FIRST_TABLE (F_ID) values (CSS_F.nextval);
Could you give me an advice how to add inserting genereted id to this statement?
You can select the name and description columns from the second_table and use the sequence for inserting the F_ID column.
insert into FIRST_TABLE (F_ID,F_NAME,F_DESCRIPTION)
select CSS_F.nextval, S_NAME, S_DESCRIPTION
from SECOND_TABLE S
where not exists (select * from FIRST_TABLE where S.S_NAME = F_NAME)
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
;