PostgreSQL to find the next available value - sql

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.

Related

I need to validate data in a Table where 1 column has multiple values but 1 value can only be present in 1 row by unique key

Example:
A table Keyed by Unique Name and Email Address with a column for Type
The Type column can have Original, Work, Personal
where you can have multiple work and personal emails but only 1 Original email
I am using DB2 for i SQL and I want to constrain the data using UNIQUE or CHECK constraints but not sure how I can do this data set.
Scott scott#hotmail.com Original
Scott scott#gmail.com Personal
Scott scott#live.com Personal
Scott scott#NBC.com Work
Scott scott#ABC.com Work
Scott scott#yahoo.com Original
I want to identify that I cant have yahoo as Original if I already have hotmail as original.
the rest are valid.
Let me know if I need to add more.
If you have Db2 for IBM i, then you may create a UNIQUE INDEX with the corresponding WHERE clause.
CREATE TABLE TEST_IND_EXPR
(
NAME VARCHAR (20) NOT NULL
, EMAIL VARCHAR (20) NOT NULL
, TYPE VARCHAR (20) NOT NULL
);
CREATE UNIQUE INDEX TEST_IND_EXPR1 ON TEST_IND_EXPR (NAME, EMAIL);
CREATE UNIQUE INDEX TEST_IND_EXPR2 ON TEST_IND_EXPR (NAME, TYPE) WHERE TYPE = 'Original';
INSERT INTO TEST_IND_EXPR VALUES ('Scott', 'scott#hotmail.com', 'Original');
INSERT INTO TEST_IND_EXPR VALUES ('Scott', 'scott#gmail.com', 'Personal');
INSERT INTO TEST_IND_EXPR VALUES ('Scott', 'scott#live.com', 'Personal');
INSERT INTO TEST_IND_EXPR VALUES ('Scott', 'scott#yahoo.com', 'Original');
The last statement returns SQL0803 as this row violates uniqueness of the TEST_IND_EXPR2 index.

H2 - Create unique index with two columns

currently I am using a postgres query to create two unique indexes.
Each index consists of two columns, where the value of one column is checked for null/not null:
CREATE UNIQUE INDEX deleted_not_null_idx
ON user (ADDRESS, DELETED)
WHERE DELETED IS NOT NULL;
CREATE UNIQUE INDEX deleted_null_idx
ON user (ADDRESS)
WHERE DELETED IS NULL;
I am attempting to do the same on H2 but I am having issues understanding the syntax and structure of H2.
How would this expression be formed if written using H2 syntax?
A workaround to ensure "uniqueness of the columns on a subset of rows" can be worked out if you are willing to add an artificial extra column to the table, just for this purpose. Not sure it's the best idea, but can do the job.
For example:
create table t (
address varchar(20),
deleted int,
extra_column varchar(20) as
case when deleted is null then null else address end,
constraint uq1 unique (extra_column)
);
insert into t (address, deleted) values ('123 Maple', 20);
insert into t (address, deleted) values ('456 Oak', 25);
insert into t (address, deleted) values ('456 Oak', null); -- succeeds
insert into t (address, deleted) values ('456 Oak', 28); -- fails
Result:
select * from t;
ADDRESS DELETED EXTRA_COLUMN
--------- ------- ------------
123 Maple 20 123 Maple
456 Oak 25 456 Oak
456 Oak <null> <null>

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

Postgres SERIAL add value after error INSERT command

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....

SQL set UNIQUE only for two columns

I would like for example that user can add name JHONE and age 25, so next time he can add JHONE, 26 or ALEX 25, BUT not JHONE, 25 again.
So I'm looking for two column unique NOT separately.
P.S. I'm sorry if same question was mentioned before.
EDIT:
This is my example:
Would like to make userIdG and doWithCar will be like this
102163096246025413003 View
102163096246025413003 Buy
102163096246025413003 Let
102163096246025413003 Sell
And for Id = 102163096246025413003 you can't add any more values, BECAUSE column doWithCar will have only 4 possible choice view, buy, rent and sell
You could specify more than one column in UNIQUE:
CREATE TABLE tab(ID INT IDENTITY(1,1) PRIMARY KEY, name VARCHAR(100), age INT
,UNIQUE(name, age));
INSERT INTO tab(name, age) VALUES ('John', 25);
INSERT INTO tab(name, age) VALUES ('John', 26);
-- INSERT INTO tab(name,age) VALUES ('John', 25);
-- Violation of UNIQUE KEY constraint 'UQ__tab__CF0426FD76D3370A'.
-- Cannot insert duplicate key in object 'dbo.tab'.
-- The duplicate key value is (John, 25).
-- The statement has been terminated.
SELECT * FROM tab;
LiveDemo
Note:
You should store date of birth and not age itself (or make age calculated column and set UNIQUE(name, dob)).
this is what I do not understand) how database will know that it should be two columns as unique and not each column is unique
These are different concepts. DB "knows" it from UNIQUE constraint definition:
UNIQUE(userIdG,doWithCar) -- pair of column is unique
!=
UNIQUE(userIdG),UNIQUE(doWithCar) -- each column is unique