Accessing old and new values in WHERE clause of CREATE RULE - sql

I'm trying to merge two dump files as outlined in this answer. In working through creating the rule I keep having issues trying to access both the OLD and NEW values of my account table.
The account schema is just a table with a single column of type jsonb called value. Inside the value column are two fields off which I want to create the skip_unique rule. Following is the latest query I've tried and the ensuing response:
CREATE OR REPLACE RULE skip_unique AS ON INSERT TO account WHERE NEW.value->>'opCo' = OLD.value->>'opCo' AND NEW.value->>'customerId' = OLD.value->>'customerId' DO INSTEAD NOTHING;
ERROR: invalid reference to FROM-clause entry for table "old"
LINE 1: ...S ON INSERT TO account WHERE NEW.value->>'opCo' = OLD.value-...
^
HINT: There is an entry for table "old", but it cannot be referenced from this part of the query.
I've tried numerous permutations of the same general query including:
CREATE OR REPLACE RULE skip_unique AS ON INSERT TO account WHERE (EXISTS (SELECT 1 FROM account WHERE NEW.value->>'opCo' = OLD.value->>'opCo' AND NEW.value->>'customerId' = OLD.value->>'customerId')) DO INSTEAD NOTHING;
CREATE OR REPLACE RULE skip_unique AS ON INSERT TO account WHERE (EXISTS (SELECT 1 FROM account AS b WHERE b.value->>'opCo' = account.value->>'opCo' AND b.value->>'customerId' = account.value->>'customerId')) DO INSTEAD NOTHING;
CREATE OR REPLACE RULE skip_unique AS ON INSERT TO account old WHERE (NEW.value->>'opCo' = old.value->>'opCo' AND NEW.value->>'customerId' = old.value->>'customerId') DO INSTEAD NOTHING;
None of them have worked for various reasons.
Thanks in advance for your help!

There is no OLD variable (or rather it's unassigned) on an INSERT trigger. After all, with an INSERT you're inserting something new, so what would you expect OLD to hold?
OLD
Data type RECORD; variable holding the old database row for
UPDATE/DELETE operations in row-level triggers. This variable is
unassigned in statement-level triggers and for INSERT operations.
https://www.postgresql.org/docs/current/static/plpgsql-trigger.html
You should be looking for the value in the table rather than OLD.

Related

How to insert into the table a user name record

I've a table. In this table I have two columns - 'insert_name' and 'modified_name'. I need to insert into this columns data about who has inserted data into the table('insert_name') and who has changed these data in the table (modified_name). How it can be done?
You are looking for basic DML statements.
If your record is already in the table, then you need to UPDATE it. Otherwise, when you are about to add your record to it and it doesn't already exist in the destination table then you are looking for INSERT INTO statement.
Example of updating information for record with first id:
UPDATE yourtable SET insert_name = 'value1', modified_name = 'value2' WHERE id = 1
Example of inserting new record:
INSERT INTO yourtable(id, company_name, product_name, insert_name)
VALUES (1, 'Google', 'PC', 'value1')
If you are looking for automatic changes to those columns then you need to look into triggers.
Remember that more often than not you may find that the application connecting to the database is using single database user in which case you probably know the context within the application itself (who inserts, who updates). This does eliminate triggers and put the task straight on simple insert/update commands from within your application layer.
You might be able to use the CURRENT_USER function to find the name of the user making the change.
The value from this function could then be used to update the appropriate column. This update could be done as part of the INSERT or UPDATE statement. Alternatively use an INSERT or UPDATE trigger.
Personally I avoid triggers if I can.
For those 2 columns add Current_User as Default constraint.
As the first time Insert Statement will save them with current login user names. For update write an Update trigger with the same Current_User statement for the column Modified_Name.
If and only if your application business logic can't update the column modified_nme then only go for Trigger.
See the use of Current_Use
https://msdn.microsoft.com/en-us/library/ms176050.aspx

How to log old/new rows as XML in triggers

I would like to the the old & new rows as XML to an exceptions table when a trigger cannot succeed. I am used to using a generic EXCEPTION WHEN OTHERS THEN clause to log out failures, what I cannot figure out is have to capture (so I can log) the OLD and NEW pseudorows into XML.
It seems like
old_x := dbms_xmlgen.getxml('select * from OLD');
ought to work, but perhaps I am missing something simple.
You can't select from old, and there is no way to access the old values generically, I'm afraid - you have to specify old.empno, old.ename etc. one by one.
Tom Kyte has shown how to generate triggers to overcome this here on asktom.oracle.com.
Reading the fine document:
A trigger that fires at row level can access the data in the row that it is processing by using correlation names. The default correlation names are OLD, NEW, and PARENT.
OLD, NEW, and PARENT are also called pseudorecords, because they have record structure, but are allowed in fewer contexts than records are. The structure of a pseudorecord is table_name%ROWTYPE, where table_name is the name of the table on which the trigger is created (for OLD and NEW) or the name of the parent table (for PARENT).
(Pseudorecords have also other restrictions explicitly mentioned in the documentation but not quoted here.)
Among all the other things the restrictions mean one can't use pseudorecords e.g. in insert statement extension.
So very much the only thing you can do with a pseudorecord is to refer it's fields one-by-one.
Example
create table a (a number, b date);
create table b (a number, b date);
create or replace trigger a_trg
before insert on a
for each row
begin
-- NEW pseudorecord is not a real record and therefore compilation will fail:
-- PLS-00049: bad bind variable 'NEW'
-- insert into b values :new;
-- NEW pseudorecord fields have to be referenced one-by-one
insert into b(a, b) values(:new.a, :new.b);
end;
/
show errors
insert into a values(1, sysdate);
select * from a;
select * from b;

ON CONFLICT DO UPDATE has missing FROM-clause

I have a simple table (id and name column, both unique), which I am importing a tab delimited CSV file.
I am running psql 9.5, and wanted to try out the new ON CONFLICT feature to update the name column if the ID already exists.
CREATE TEMP TABLE tmp_x AS SELECT * FROM repos LIMIT 0;
COPY tmp_x FROM '/Users/George/git-parser/repo_file' (format csv, delimiter E'\t');
INSERT INTO repos SELECT * FROM tmp_x
ON CONFLICT(name) DO UPDATE SET name = tmp_x.name;
DROP TABLE tmp_x;
I am getting this error:
SELECT 0
COPY 1
ERROR: missing FROM-clause entry for table "tmp_x"
LINE 4: ON CONFLICT(name) DO UPDATE SET name = tmp_x.name;
^
Query failed
PostgreSQL said: missing FROM-clause entry for table "tmp_x"
Not too sure whats going wrong here.
If you look at the documentation of the ON CONFLICT clause, it says this about the "conflict action":
The SET and WHERE clauses in ON CONFLICT DO UPDATE have access to the existing row using the table's name (or an alias)
In your query, the target table is repos.
tmp_x, on the other hand, is the source of the data you are trying to insert, but the ON CONFLICT clause cannot "see" that - it is looking at a particular row that has been calculated and failed. Consider if you'd written something like this:
INSERT INTO repos SELECT max(foo_id) FROM tmp_x
Clearly, it wouldn't make sense for a row which failed to insert into repos to have access to any one row from tmp_x.
If there was no way of seeing the rejected data, the whole feature would be pretty useless, but if we read on:
... and to rows proposed for insertion using the special excluded table.
So instead, you need to access the magic table alias excluded, which contains the values which you tried to insert but got a conflict on, giving you this:
INSERT INTO repos SELECT * FROM tmp_x
ON CONFLICT(name) DO UPDATE SET name = excluded.name;
If it seems weird that an imaginary table name pops up for this purpose, consider that a similar thing happens when writing triggers, where you get OLD and NEW (depending on the kind of trigger you're writing).

multithreading with the trigger

I have written a Trigger which is transferring a record from a table members_new to members_old. The Function of trigger is to insert a record into members_old on after insert in members_new. So suppose a record is getting inserted into a members_new like
nMmbID nMmbName nMmbAdd
1 Abhi Bangalore
This record will get inserted into members_old with the same data structure of the table
My trigger is like :
create trigger add_new_record
after
insert on members_new
for each row
INSERT INTO `test`.`members_old`
(
`nMmbID`,
`nMmbName`,
`nMmbAdd`
)
(
SELECT
`members_new`.`nMmbID`,
`members_new`.`nMmbName`,
`members_new`.`nMmbAdd`
FROM `test`.`members_new`
where nMmbID = (select max(nMmbID) from `test`.`members_new` // written to read the last record from the members_new and stop duplication on the members_old , also this will reduce the chances of any error . )
)
This trigger is working for now , but my confusion is that what will happen if a multiple insertion is happening at one instance of time.
Will it reduce the performance?
Will I face deadlock condition ever in any case as my members_old have FKs?
If any better solution for this situation is there, please give limelight on that
From the manual:
You can refer to columns in the subject table (the table associated with the trigger) by using the aliases OLD and NEW. OLD.col_name refers to a column of an existing row before it is updated or deleted. NEW.col_name refers to the column of a new row to be inserted or an existing row after it is updated.
create trigger add_new_record
after
insert on members_new
for each row
INSERT INTO `test`.`members_old`
SET
`nMmbID` = NEW.nMmbID,
`nMmbName` = NEW.nMmbName,
`nMmbAdd` = NEW.nMmbAdd;
And you will have no problem with deadlocks or whatever. Also it should be much faster, because you don't have to read the max value before (which is also unsecure and might lead to compromised data). Read about isolation levels and transactions if you're interested why...

Getting INSERT errors when I do UPDATE?

At work we have a SQL Server database. I don't know the db that well. I have created a new column in the table for some new functionality....straight away I have started seeing errors
My statement was this:
ALTER TABLE users
ADD locked varchar(50) NULL
GO
The error is:
Insert Error: Column name or number of supplied values does not match table definition
I have read that the error message appears when during an INSERT operation either the number of supplied column names or the number of supplied values does not match the table definition.
But I have checked so many times and i have changed the PHP code to include this columns data yet I still receive the error.
I have run the SQL query directly on the db and still get the error.
Funny enough the query which gets the error is an Update.
UPDATE "users"
SET "users"."date_last_login" = GETDATE()
WHERE id = 1
Have you considered it could be a trigger causing it? 
This is the error message you would get.
If its an Update action causing it check trigger actions that Updates on that table run.
Do it with:
#sp_helptrigger Users, 'UPDATE';
This will show triggers occuring with ‘update’ actions.
If there is a trigger, grab the triggers name and run the below (but replace TriggerNameHere with real trigger):
#sp_helptext TriggerNameHere;
This will give you any SQL that the trigger runs and could be the INSERT the error message is referring to.
Hope this helps
Aside from TRIGGERS,
the reason for that is because you are using implicit type of INSERT statement. Let's say your previous number of columns on the table is 3. You have this syntax of INSERT statement,
INSERT INTO tableName VALUES ('val1','val2','val3')
which executes normally fine. But then you have altered the table to add another column. All of your INSERT queries are inserting only three values on the table which doesn't matches to the total number of columns.
In order to fix the problem, you have to update all INSERT statements to insert 4 values on the table,
INSERT INTO tableName VALUES ('val1','val2','val3', 'val4')
and it will normally work fine.
I'll advise you to use the EXPLICIT type of INSERT wherein you have to specify the columns you want to insert values with. Eg,
INSERT INTO tableName (col1, col2, col3) VALUES ('val1','val2','val3')
in this ways, even if you have altered your tables by adding additional columns, your INSERT statement won't be affected unless the column doesn't have a default value and which is non-nullable.