Postgres SERIAL add value after error INSERT command - sql

I created a table
CREATE TABLE street (
id SERIAL PRIMARY KEY NOT NULL,
street_name CHAR (30) NOT NULL,
city_id INTEGER REFERENCES city,
building_number CHAR(10));
after that I insert some data:
INSERT INTO street (street_name, city_id) VALUES ('Sumskaya', 1);
The data was added with id=1. Then I insert next data
INSERT INTO street (street_name, city_id) VALUES ('Sumskaya', 10);
and get the error
Key (city_id)=(10) is not present in table "city".
I changed my insert data
INSERT INTO street (street_name, city_id) VALUES ('Sumskaya', 2);
and get a row in the table with id = 3. Id = 2 is missing. Why serial assigned the value 3, not 2 and how to change it?

Serials internally use sequences. For concurrency reasons, sequences do not roll back. They just move forward. Imagine if you had two clients inserting at the same time.
Client 1 inserts a row, goes on to do some other work.
Client 2 inserts a row, goes on to do some other work
client 1 commits.
Client 1 inserts another row, goes on to do some other work
client 2 errors and rolls back.
We would expect values with an id of 1 and 3, and 2 just gets omitted.Anything else and we have problems.
If you truly need gapless nubering then you have to use a separate table and row locks, but then you cannot have concurrent clients inserting....

Related

PostgreSQL to find the next available value

I have followed the example here to find the next available value on a table column: the generated value will be used by an application to insert data in another table. But, if multiple concurrent application instances run the same query, some of these instances could get the same value. How could I avoid these collisions without change the application? Is it possible write a PostreSQL function to handle this task?
You can use an IDENTITY column or a SEQUENCE.
Identity Column Example
create table t (
id int primary key not null generated always as identity,
name varchar(10)
);
insert into t (name) values ('New York');
insert into t (name) values ('Chicago');
Result:
id name
--- --------
1 New York
2 Chicago
Each INSERT statement will produce a different value for the id column, even when they are executed on separate simultaneous threads.
Sequence Example
create table u (
id int primary key not null,
name varchar(10)
);
create sequence sequ;
insert into u (id, name) values (nextval('sequ'), 'New York');
insert into u (id, name) values (nextval('sequ'), 'Chicago');
Result:
id name
--- --------
1 New York
2 Chicago
Again, each INSERT statement will produce a different value for the id column, even when they are executed on separate simultaneous threads.
See running example for both cases at DB Fiddle.

What happens if a new row is inserted when the primary key exists when using auto increment?

I am using PGsql and transferred some data from another database into my new one. The table has records starting at PK 200. The tables primary key (bigint - autoincrementing) is currently starting at 0. If I continue to insert records, eventually it will reach 200. My question is, will these records create an issue when trying to insert record 200? or will PGsql know the conflict, then find the next available AI index (say 234)?
Thanks! If it will cause a conflict, how can I set the current index of my table to the last index of data? (like 234).
My question is, will these records create an issue when trying to insert record 200?
Assuming that you have a serial column or the-like: yes, this will create an issue. The serial has no knowledge that some sequences are not available, sp this will result in a duplicate key error. Meanwhile the sequence increments also on such errors, and the next call will return the next number, and so on.
This is easily reproducible:
create table t (id serial primary key, val text);
insert into t (id, val) values (2, 'blocker');
-- 1 rows affected
insert into t (val) values ('foo');
-- 1 rows affected
insert into t (val) values ('bar');
-- ERROR: duplicate key value violates unique constraint "t_pkey"
-- DETAIL: Key (id)=(2) already exists.
insert into t (val) values ('baz');
-- 1 rows affected
select * from t order by id;
id | val
-: | :------
1 | foo
2 | blocker
3 | baz
One solution is to reset the sequence: the only safe starting point is the high watermark of the table:
select setval(
't_id_seq',
coalesce((select max(id) + 1 from t), 1),
false
);
Demo on DB Fiddlde: you can uncomment the setval() statement to see how it avoids the error.

How to UPDATE or INSERT in PostgreSQL

I want to UPDATE or INSERT a column in PostgreSQL instead of doing INSERT or UPDATE using INSERT ... ON CONFLICT ... because there will be more updates than more inserts and also I have an auto incrementing id column that's defined using SERIAL so it increments the id column everytime it tries to INSERT or UPDATE and that's not what I want, I want the id column to increase only if it's an INSERT so that all ids would be in an order instead
The table is created like this
CREATE TABLE IF NOT EXISTS table_name (
id SERIAL PRIMARY KEY,
user_id varchar(30) NOT NULL,
item_name varchar(50) NOT NULL,
code_uses bigint NOT NULL,
UNIQUE(user_id, item_name)
)
And the query I used was
INSERT INTO table_name
VALUES (DEFAULT, 'some_random_id', 'some_random_name', 1)
ON CONFLICT (user_id, item_name)
DO UPDATE SET code_uses = table_name.code_uses + 1;
Thanks :)
Upserts in PostgreSQL do exactly what you described.
Consider this table and records
CREATE TABLE t (id SERIAL PRIMARY KEY, txt TEXT);
INSERT INTO t (txt) VALUES ('foo'),('bar');
SELECT * FROM t ORDER BY id;
id | txt
----+-----
1 | foo
2 | bar
(2 Zeilen)
Using upserts the id will only increment if a new record is inserted
INSERT INTO t VALUES (1,'foo updated'),(3,'new record')
ON CONFLICT (id) DO UPDATE SET txt = EXCLUDED.txt;
SELECT * FROM t ORDER BY id;
id | txt
----+-------------
1 | foo updated
2 | bar
3 | new record
(3 Zeilen)
EDIT (see coments): this is the expected behaviour of a serial column, since they're nothing but a fancy way to use sequences. Long story short: using upserts the gaps will be inevitable. If you're worried the value might become too big, use bigserial instead and let PostgreSQL do its job.
Related thread: serial in postgres is being increased even though I added on conflict do nothing

Auto-increment primary keys in SQL

I need help with the insert statements for a plethora of tables in our DB.
New to SQL - just basic understanding
Summary:
Table1
Col1 Col2 Col3
1 value1 value1
2 value2 value2
3 value3 value3
Table2
Col1 Col2 Col3
4 value1 value1
5 value2 value2
6 value3 value3
Multiple tables use the same sequence of auto-generated primary keys when user creates a static data record from the GUI.
However, creating a script to upload static data from one environment to the other is something I'm looking for.
Example from one of the tables:
Insert into RULE (PK_RULE,NAME,RULEID,DESCRIPTION)
values
(4484319,'TESTRULE',14,'TEST RULE DESCRIPTION')
How do I design my insert statement so that it reads the last value from the PK column (4484319 here) and auto inserts 4484320 without explicitly mentioning the same?
Note: Our DB has hundreds and thousands of records.
I think there's something similar to (SELECT MAX(ID) + 1 FROM MyTable) which could potentially solve my problem but I don't know how to use it.
Multiple tables use the same sequence of auto-generated primary keys when user creates a static data record from the GUI.
Generally, multiple tables sharing a single sequence of primary keys is a poor design choice. Primary keys only need to be unique per table. If they need to be unique globally there are better options such as UUID primary keys.
Instead, one gives each table their own independent sequence of primary keys. In MySQL it's id bigint auto_increment primary key. In Postgres you'd use bigserial. In Oracle 12c it's number generated as identity.
create table users (
id number generated as identity,
name text not null
);
create table things (
id number generated as identity,
description text not null
);
Then you insert into each, leaving off the id, or setting it null. The database will fill it in from each sequence.
insert into users (name) values ('Yarrow Hock'); -- id 1
insert into users (id, name) values (null, 'Reaneu Keeves'); -- id 2
insert into things (description) values ('Some thing'); -- id 1
insert into things (id, description) values (null, 'Shiny stuff'); -- id 2
If your schema is not set up with auto incrementing, sequenced primary keys, you can alter the schema to use them. Just be sure to set each sequence to the maximum ID + 1. This is by far the most sane option in the long run.
If you really must draw from a single source for all primary keys, create a sequence and use that.
create sequence master_seq
start with ...
Then get the next key with nextval.
insert into rule (pk_rule, name, ruleid, description)
values (master_seq.nextval, 'TESTRULE', 14, 'TEST RULE DESCRIPTION')
Such a sequence goes up to 1,000,000,000,000,000,000,000,000,000 which should be plenty.
The INSERT and UPDATE statements in Oracle have a ...RETURNING...INTO... clause on them which can be used to return just-inserted values. When combined with a trigger-and-sequence generated primary key (Oracle 11 and earlier) or an identity column (Oracle 12 and up) this lets you get back the most-recently-inserted/updated value.
For example, let's say that you have a table TABLE1 defined as
CREATE TABLE TABLE1 (ID1 NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
COL2 NUMBER,
COL3 VARCHAR2(20));
You then define a function which inserts data into TABLE1 and returns the new ID value:
CREATE OR REPLACE FUNCTION INSERT_TABLE1(pCOL2 NUMBER, vCOL3 VARCHAR2)
RETURNS NUMBER
AS
nID NUMBER;
BEGIN
INSERT INTO TABLE1(COL2, COL3) VALUES (pCOL2, vCOL3)
RETURNING ID1 INTO nID;
RETURN nID;
END INSERT_TABLE1;
which gives you an easy way to insert data into TABLE1 and get the new ID value back.
dbfiddle here

SQL Server trigger can't insert

I beginning to learn how to write trigger with this basic database.
I'm also making my very 1st database.
Schema
Team:
TeamID int PK (TeamID int IDENTITY(0,1) CONSTRAINT TeamID_PK PRIMARY KEY)
TeamName nvarchar(100)
History:
HistoryID int PK (HistoryID int IDENTITY(0,1) CONSTRAINT HistoryID_PK PRIMARY KEY)
TeamID int FK REF Team(TeamID)
WinCount int
LoseCount int
My trigger: when a new team is inserted, it should insert a new history row with that team id
CREATE TRIGGER after_insert_Player
ON Team
FOR INSERT
AS
BEGIN
INSERT INTO History (TeamID, WinCount, LoseCount)
SELECT DISTINCT i.TeamID
FROM Inserted i
LEFT JOIN History h ON h.TeamID = i.TeamID
AND h.WinCount = 0 AND h.LoseCount = 0
END
Executed it returns
The select list for the INSERT statement contains fewer items than the insert list. The number of SELECT values must match the number of INSERT columns.
Please help thank. I'm using SQL Server
The error text is the best guide, it is so clear ..
You try inserting one value from i.TeamID into three columns (TeamID,WinCount,LoseCount)
consider these WinCount and LoseCount while inserting.
Note: I Think the structure of History table need to revisit, you should select WinCount and LoseCount as Expressions not as actual columns.
When you specify insert columns, you say which columns you will be filling. But in your case, right after insert you select only one column (team id).
You either have to modify the insert to contain only one column, or select, to retrieve 3 fields as in insert.
If you mention the columns where values have to be inserted(Using INSERT-SELECT).
The SELECT Statement has to contain the same number of columns that have been specified to be inserted. Also, ensure they are of the same data type.(You might face some issues otherwise)