PostgreSQL sequence out of sync "duplicate key violates unique constraint" - spring

I have a problem, when inserting a new row in the table I get the error: "duplicate key violates unique constraint".
After some analysis, I found this solution that helps to find out that the sequence is out of sync.
I used these two queries to find out if my id is out of sync:
SELECT MAX(id) FROM schema.table t; -- Value: 456
SELECT nextval(PG_GET_SERIAL_SEQUENCE('"table"', 'id')); -- Value: 462
They say that if the ID of the first query is greater than the ID of the second, it is out of sync.
But in my case, the ID of the first one is lower than the ID of the second one.
What can I conclude from here and how can I resolve my error when inserting a newline?
Note:
select * from pg_sequences;
With this query, I just discovered that I have 2 sequences with the same name: one is table_id_seq and another is table_id_seq1, could this be a problem?

After more analysis, I could check and confirm that my sequence was out of sync.
I've followed this with some adjustments.
Commands to check if the problem is out of sync:
SELECT MAX(id) FROM schema.table b ; -- 456 rows
SELECT nextval(PG_GET_SERIAL_SEQUENCE('"table"', 'id')); -- 462 rows
Like they said in the post: If the first value is higher than the second value, your sequence is out of sync.
This was the final command that I had to run to update the sequence and fix my bug:
SELECT setval(PG_GET_SERIAL_SEQUENCE('"table"', 'id'), (SELECT MAX(id) FROM schema.table b)+1);

Related

Trigger with subquery

I'm trying to create a trigger that controls cycles in a self-referencing table.
Unfortunately I've got an error. Could anyone tell me what I'm doing wrong?
CREATE OR REPLACE TRIGGER CHECK_CYCLE
BEFORE INSERT OR UPDATE ON DEPARTMENTS
FOR EACH ROW
BEGIN
IF :NEW.PARENT_ID =
(SELECT ID, PARENT_ID, NAME, LEVEL,
CONNECT_BY_ISLEAF AS ISLEAF,
PRIOR NAME AS PARENT_NAME,
CONNECT_BY_ROOT NAME AS ROOT
FROM DEPARTMENTS
START WITH PARENT_ID IS NULL
CONNECT BY PRIOR ID = PARENT_ID)
THEN
RAISE_APPLICATION_ERROR(20000, 'Sorry.');
END IF;
END;
Error(9,25): PLS-00103: Encountered the symbol "AS" when expecting one of the following:
, from
This seems to be the parser getting confused. If you remove the AS ROOT completely then it reverts to the expected PLS-00405: subquery not allowed in this context error. No idea why it is confused - you can run that query standalone - but since you can't do what you're attempting it's probably not worth worrying about too much.
Your subquery also has three columns and will return multiple rows, so comparing with the current row's scalar PARENT_ID isn't going to work. You could run your subquery separately and select the value you're actually interested in into a local variable, and check against that.
But you have a before-insert trigger and you're querying the table the trigger is against, so you'll get a mutating table exception anyway when you try to insert (or at least, if you try to insert multiple rows at once).
If I understand what you're trying to achieve, you can use an after-insert trigger instead:
create or replace trigger check_cycle
after insert or update on departments
declare
l_hascycle pls_integer;
begin
select max(connect_by_iscycle)
into l_hascycle
from departments
start with parent_id is null
connect by nocycle prior id = parent_id;
if l_hascycle = 1 then
raise_application_error(-20000, 'Sorry.');
end if;
end;
/
This uses the CONNECT_BY_ISCYCLE pseudocolumn, which will be zero for all rows if there is no cycling, and one for any column that cycles. Selecting and checking the max() of that means you'll have a single value of 0 or 1 in the local l_hascycle variable, and you can use that to decide whether to throw an exception.
Inserting a couple of test rows on top of existing non-cycling data such as:
ID PARENT_ID NAME
---------- ---------- ------------
1 Test
2 1 Test
... where the first new row is OK and the second would cause a cycle:
insert into departments (id, parent_id, name) values (3, 2, 'OK');
1 row inserted.
insert into departments (id, parent_id, name) values (2, 3, 'Cycles');
ORA-20000: Sorry.
ORA-06512: at "SCHEMA.CHECK_CYCLE", line 11
ORA-04088: error during execution of trigger 'SCHEMA.CHECK_CYCLE'
The first insert is still in effect (but not yet committed), the second was implicitly rolled back:
select * from departments:
ID PARENT_ID NAME
---------- ---------- ------------
1 Test
2 1 Test
3 2 OK
This won't catch cycles in data that is not connected to an existing tree culminating in a null parent, because of the start with clause; so for exampel with data you could insert 4,5 and 5,4 without raising an exception, as there is no route from either 4 or 5 to a row with a null parent. But that's a different issue and something you might want to test for separately.
It also can't see data from uncommitted changes in other sessions, so it's possible for two sessions to simultaneously insert rows that are valid independently but which still would form a cycle once both are committed.
I think I found the error in your query, it's in this line -
CONNECT_BY_ROOT NAME AS ROOT
which is not built correctly.
Try changing it to something like
CONNECT_BY_ROOT AS ROOT

Oracle identity column and insert into select

Oracle 12 introduced nice feature (which should have been there long ago btw!) - identity columns. So here's a script:
CREATE TABLE test (
a INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
b VARCHAR2(10)
);
-- Ok
INSERT INTO test (b) VALUES ('x');
-- Ok
INSERT INTO test (b)
SELECT 'y' FROM dual;
-- Fails
INSERT INTO test (b)
SELECT 'z' FROM dual UNION ALL SELECT 'zz' FROM DUAL;
First two inserts run without issues providing values for 'a' of 1 and 2. But the third one fails with ORA-01400: cannot insert NULL into ("DEV"."TEST"."A"). Why did this happen? A bug? Nothing like this is mentioned in the documentation part about identity column restrictions. Or am I just doing something wrong?
I believe the below query works, i havent tested!
INSERT INTO Test (b)
SELECT * FROM
(
SELECT 'z' FROM dual
UNION ALL
SELECT 'zz' FROM dual
);
Not sure, if it helps you any way.
For, GENERATED ALWAYS AS IDENTITY Oracle internally uses a Sequence only. And the options on general Sequence applies on this as well.
NEXTVAL is used to fetch the next available sequence, and obviously it is a pseudocolumn.
The below is from Oracle
You cannot use CURRVAL and NEXTVAL in the following constructs:
A subquery in a DELETE, SELECT, or UPDATE statement
A query of a view or of a materialized view
A SELECT statement with the DISTINCT operator
A SELECT statement with a GROUP BY clause or ORDER BY clause
A SELECT statement that is combined with another SELECT statement with the UNION, INTERSECT, or MINUS set operator
The WHERE clause of a SELECT statement
DEFAULT value of a column in a CREATE TABLE or ALTER TABLE statement
The condition of a CHECK constraint
The subquery and SET operations rule above should answer your Question.
And for the reason for NULL, when pseudocolumn(eg. NEXTVAL) is used with a SET operation or any other rules mentioned above, the output is NULL, as Oracle couldnt extract them in effect with combining multiple selects.
Let us see the below query,
select rownum from dual
union all
select rownum from dual
the result is
ROWNUM
1
1

Validate person without value in date column

I have a table with several employees. They have the following columns empid,datecolumn1,is_valid.
Very few employees have a more than one record in the table. If an employee has more than one record in the table I would like to 'invalidate' one of the records on the following condition:
1. If a employee has more than one record in the table then the record with no value in the datecolumn1 is valid (update is_valid to 1) and the record with value in datecolumn1 is not valid (update is_valid to 0).
How do I accomplish this?
As Ben points out, you've stated that if datecolumn1 is NULL you want the is_valid column to be set to both 0 and 1. Assuming you fix that, you may need to adjust this CASE statement depending on which way you decide is correct.
UPDATE employees
SET is_valid = (CASE WHEN datecolumn1 IS NULL
THEN 1
ELSE 0
END)
WHERE empid IN (SELECT e.empid
FROM employees e
GROUP BY emempid
HAVING COUNT(*) > 1)
create a staging table, and fill it by a SELECT on the original table with a GROUP BY employee Id (or whatever your unique identifier is). Create a second staging table and fill it by SELECTING on the original table and excluding all rows that match rows in your grouped table. Now you have a table that contains only those people with multiple rows. From your original table, set is_valid to 0 on all rows that match employee id with the second staging table and also have no datecolumn1 (or perhaps that also have a datecolumn1 - your question as of this writing is a bit unclear.) and is_valid to 1 on the others. Once done with that, delete the staging tables, and you should have what you need.
You could also do this with a single more complicated multiselect call, but I find it helpful to use staging tables when things get complicated.

Oracle select max id from table returns null value

I have the query:
SELECT MAX(prod_id) FROM products;
It returns the maximum value if there are records. But, if I truncate table and run the same query I am unable to get the max id.
In case you want to query a table's column and suspect that the max function may return null, then you can return 0 in case null is encountered
SELECT NVL(MAX(P.PROD_ID), 0) AS MAX_VAL
FROM PRODUCTS P
This will return at least 0 , if no value is encountered for the column that you mention ()
Yes, by truncating the table you have removed all data in it, with no need for a commit. Therefore there is no data in the table and the max of nothing is nothing.
If you truncate the table, there are no rows left in the table. By definition, Max() returns NULL when run against an empty table.... or did I miss something here?
As Gerald P. Wright commented to an answer, if id is generated by a sequence, you can use it to find the value.
But
SELECT prod_id_seq.currval FROM DUAL
won't work, because currval works only in the session where you fetched a value with currval.
so,
SELECT prod_id_seq.nextval FROM DUAL
can be an workaround for you, but would be a realy, realy BAD solution(if you get the id with this several times you'll get incremented values).

Pattern to substitute for MERGE INTO Oracle syntax when not allowed

I have an application that uses the Oracle MERGE INTO... DML statement to update table A to correspond with some of the changes in another table B (table A is a summary of selected parts of table B along with some other info). In a typical merge operation, 5-6 rows (out of 10's of thousands) might be inserted in table B and 2-3 rows updated.
It turns out that the application is to be deployed in an environment that has a security policy on the target tables. The MERGE INTO... statement can't be used with these tables (ORA-28132: Merge into syntax does not support security policies)
So we have to change the MERGE INTO... logic to use regular inserts and updates instead. Is this a problem anyone else has run into? Is there a best-practice pattern for converting the WHEN MATCHED/WHEN NOT MATCHED logic in the merge statement into INSERT and UPDATE statements? The merge is within a stored procedure, so it's fine for the solution to use PL/SQL in addition to the DML if that is required.
Another way to do this (other than Merge) would be using two sql statements one for insert and one for update. The "WHEN MATCHED" and "WHEN NOT MATCHED" can be handled using joins or "in" Clause.
If you decide to take the below approach, it is better to run the update first (sine it only runs for the matching records) and then insert the non-Matching records. The Data sets would be the same either way, it just updates less number of records with the order below.
Also, Similar to the Merge, this update statement updates the Name Column even if the names in Source and Target match. If you dont want that, add that condition to the where as well.
create table src_table(
id number primary key,
name varchar2(20) not null
);
create table tgt_table(
id number primary key,
name varchar2(20) not null
);
insert into src_table values (1, 'abc');
insert into src_table values (2, 'def');
insert into src_table values (3, 'ghi');
insert into tgt_table values (1, 'abc');
insert into tgt_table values (2,'xyz');
SQL> select * from Src_Table;
ID NAME
---------- --------------------
1 abc
2 def
3 ghi
SQL> select * from Tgt_Table;
ID NAME
---------- --------------------
2 xyz
1 abc
Update tgt_Table tgt
set Tgt.Name =
(select Src.Name
from Src_Table Src
where Src.id = Tgt.id
);
2 rows updated. --Notice that ID 1 is updated even though value did not change
select * from Tgt_Table;
ID NAME
----- --------------------
2 def
1 abc
insert into tgt_Table
select src.*
from Src_Table src,
tgt_Table tgt
where src.id = tgt.id(+)
and tgt.id is null;
1 row created.
SQL> select * from tgt_Table;
ID NAME
---------- --------------------
2 def
1 abc
3 ghi
commit;
There could be better ways to do this, but this seems simple and SQL-oriented. If the Data set is Large, then a PL/SQL solution won't be as performant.
There are at least two options I can think of aside from digging into the security policy, which I don't know much about.
Process the records to merge row by row. Attempt to do the update, if it fails to update then insert, or vise versa, depending on whether you expect most records to need updating or inserting (ie optimize for the most common case that will reduce the number of SQL statements fired), eg:
begin
for row in (select ... from source_table) loop
update table_to_be_merged
if sql%rowcount = 0 then -- no row matched, so need to insert
insert ...
end if;
end loop;
end;
Another option may be to bulk collect the records you want to merge into an array, and then attempted to bulk insert them, catching all the primary key exceptions (I cannot recall the syntax for this right now, but you can get a bulk insert to place all the rows that fail to insert into another array and then process them).
Logically a merge statement has to check for the presence of each records behind the scenes anyway, and I think it is processed quite similarly to the code I posted above. However, merge will always be more efficient than coding it in PLSQL as it will be only 1 SQL call instead of many.

Resources