Unique Constraint Ignored by Null - sql

I am using Postgres 9.6, and have an issue where I am using jsonb_populate_recordset.
I created an UNIQUE constraint on the table, but I am able to bypass this when performing an INSERT with null values.
Is there was a way to force the unique constraint to keep only 1 record, even if it has null values, and not allow duplicates afterward?
Here is a quick example:
CREATE TABLE person(
person_id SERIAL PRIMARY KEY,
person_name TEXT,
CONSTRAINT unq_person UNIQUE(person_name)
);
INSERT INTO person (person_name) VALUES ('Frank');
CREATE TABLE locations(
location_id SERIAL PRIMARY KEY,
city TEXT,
state TEXT,
address TEXT,
address_country TEXT,
postal_code TEXT,
person_id INTEGER REFERENCES person(person_id)
ON DELETE CASCADE,
CONSTRAINT unq_location UNIQUE(city, state, address, address_country, postal_code, person_id)
);
In this example, city and address are null (but theoretically, they could all be null, or any combination of record properties).
Every time I run the following query, a new record gets inserted. I don't want more than one of these records.
INSERT INTO locations (city, state, address, address_country, postal_code, person_id)
SELECT city, state, address, address_country, postal_code, person_id
FROM jsonb_populate_recordset(NULL::locations, '[{"city": null, "address": null, "address_country": "USA", "state": "NY", "person_id": 1, "postal_code": "10001"}]'::jsonb)
How can I only allow 1 record, and not multiple when inserting a JSONB object into Postgres?

To your existing query:
insert into locations
(fields)
select values
from etc
add this filter
where not exists
(select 1
from locations l2
where locations.person_id = l2.person_id
)
It has nothing to do with null values.

Related

SQL - cannot add or update a child row: a foreign key constraint fails

I keep getting the error:
Cannot add or update a child row: a foreign key constraint fails
(myWork.Bookingss, CONSTRAINT Bookingss_ibfk_1 FOREIGN KEY
(CustomersID) REFERENCES Customers (CustomersID))
I have spent a while researching as this is apart of a school assignment and can not seem to resolve the problem.
Here is my SQL code:
USE myWork ;
DROP TABLE IF EXISTS Bookingss ;
DROP TABLE IF EXISTS Customers ;
CREATE TABLE myWork.Customers
(
CustomersID INT NOT NULL AUTO_INCREMENT,
Surname CHAR(30) NOT NULL ,
FirstName CHAR(30) NOT NULL ,
Title CHAR(10),
DOB DATE,
HouseNumber INT,
StreetName CHAR(30),
Town CHAR(30),
PostCode CHAR(9),
Telephone INT,
PRIMARY KEY (CustomersID)
) ;
CREATE TABLE myWork.Bookingss
(
BookingsID INT NOT NULL AUTO_INCREMENT,
CustomersID INT NOT NULL,
AdultsBooked INT NOT NULL,
ChildrenBooked INT NOT NULL,
Check_In DATE,
Check_Out DATE,
PRIMARY KEY (BookingsID),
FOREIGN KEY (CustomersID)
REFERENCES myWork.Customers (CustomersID)
) ;
SHOW TABLES ;
INSERT INTO myWork.Bookingss ( CustomersID, AdultsBooked , ChildrenBooked , Check_In , Check_Out )
VALUES ("1", "2", "3", "2022-04-10", "2022-04-13" ) ;
INSERT INTO myWork.Customers ( Surname , FirstName , Title , DOB )
VALUES ( "smith" , "ryan" , "Mr" , "1998-02-16" ) ;
SELECT * FROM Customers , Bookingss;
You got the order wrong and some challenges to overcome. You are trying to insert a booking first, and this with an id for a customer that does not yet exist in the customer table at that moment.
You have to insert the customer first, then use the customer's id to insert the booking for the customer (using his id).
And there is the first challenge. The customer id is an auto increment field. You would not know the id when you insert a customer in the table.
You would have to fetch the customer and use the id of that customer to insert a booking for the customer. How do you fetch the customer? Selecting the customer with a specific name surname and first name is not a correct choice, any other field (or composition of fields) that I see in the table definition is not a good choice neither.
You'd need to think further about a good solution. There are several options I could tell you about. But the appropriate solution depends on your assignment/context.
By the way, the last select clause may not deliver the result you expect. You are producing a cartesian product (every row of the first table with every row of the second table). What you probably want is a JOIN where you link the first table with the second table accordingly (e.g. using the primary key and the foreign key).

PostgreSQL: INSERT into and get the new ID for usage in LO-BASE

I'd like to add a line to a table:
CREATE TABLE actors (
id_act serial NOT NULL,
first_name text NOT NULL,
last_name text NOT NULL,
CONSTRAINT actors_pkey PRIMARY KEY (id_act)
);
INSERT INTO actors (first_name, last_name) VALUES ('Tom', 'Hanks');
Using dBeaver, this statement provides the new ID:
select CurrVal(pg_get_serial_sequence('actors', 'id_act'));
With LibreOffice-BASE, I have to add the name of the scheme and this results in
ERROR: column "scheme_name.table_name" does not exist
I've got the same error using:
"scheme_name.table_name"
"scheme_name"."table_name"
"table_name"
How can I get the new ID for further usage (calculation, check, ...)? I don't mind to use CurrVal or RETURNING or something else. But I don't find the proper syntax.
Thank you!
The simplest option is to use the RETURNING clause in your INSERT query:
INSERT INTO actors (first_name, last_name) VALUES ('Tom', 'Hanks')
RETURNING id_act;
You can use an insert inside a CTE and then return the value:
WITH i AS (
INSERT INTO actors (first_name, last_name) VALUES ('Tom', 'Hanks')
RETURNING id_act
)
SELECT i.*
FROM i;
The outer query is a SELECT, so your UI should be comfortable with it returning a value.
You can even continue the processing in this statement -- by adding more CTEs for instance -- so you don't need to actually fetch the value.
There are three ways, which will provide you the new id's
Using SERIAL and PRIMARY KEY, postgres will automatically insert an unique value
CREATE TABLE actors (
id_act SERIAL PRIMARY KEY
first_name text NOT NULL,
last_name text NOT NULL
);
If you have sequence, you can use this sequence while creating DDL and every time data gets inserted,
new id will be generated
CREATE TABLE actors (
id_act integer NOT NULL DEFAULT nextval('sequence_name')
first_name text NOT NULL,
last_name text NOT NULL,
CONSTRAINT actors_pkey PRIMARY KEY (id_act)
);
If you have sequence, use the sequence in the insert query
CREATE TABLE actors (
id_act integer,
first_name text NOT NULL,
last_name text NOT NULL,
CONSTRAINT actors_pkey PRIMARY KEY (id_act)
);
INSERT INTO actors (id_act, first_name, last_name) VALUES (nextval('sequence_name'), 'Tom', 'Hanks');

Auto Increment FK how does it update?

Ok so i am currently developing in Oracle 11G express edition for a college assignment. I have ran into an issue on how to auto increment and update in the following parent table. So i have an address table and then a city table for instance. Here is the address SQL code
create table address(
addressid int primary key,
cityid int,
countyid int,
streetnameid int,
postcodeid int,
doornumid int,
natid int,
foreign key (doornumid) references doornum,
foreign key (postcodeid) references postcode,
foreign key (streetnameid) references streetname,
foreign key (countyid) references county,
foreign key (cityid) references city,
foreign key (natid) references nat);
So as you can see I am referencing the city table as a foreign key and this is the city SQL table code below:
create table city(
cityid int primary key,
city varchar(45));
The city code is using a sequence to auto increment when stuff is inserted it:
create SEQUENCE seq_cityID
MINVALUE 1
START WITH 1
INCREMENT BY 1
CACHE 11;
So simply I am auto incrementing all inputs by a cityid like this:
INSERT INTO city (cityid, city)
values(seq_cityID.nextval, Oxford);
INSERT INTO city (cityid, city)
values(seq_cityID.nextval, Oxford);
But my issue is that when I am referencing this in its parent table e.g. the address table how do i reference the ID for that row of data and make sure that the correct ID is pulled without having to manually type it in?
INSERT INTO address (addressid, cityid, countyid, streetnameid, postcodeid, doornumid, natid)
values(seq_addressID.nextval, seq_cityID.nextval, seq_countyID.nextval, seq_streetnameID.nextval, seq_postcodeID.nextval, seq_doornumID.nextval, seq_natID.nextval);
INSERT INTO address (addressid, cityid, countyid, streetnameid, postcodeid, doornumid, natid)
values(seq_addressID.nextval, seq_cityID.nextval, seq_countyID.nextval, seq_streetnameID.nextval, seq_postcodeID.nextval, seq_doornumID.nextval, seq_natID.nextval);
This is the simple insert into address which is only currently referencing nextval but i do not believe it will pull that row of data from that ID. How do i effectively pull through that ID from the child table and have it correctly into the parent automatically?
You have a misconception about tables and their relations.
Your datamodel has one table for all city names, one for all street names, one for all door numbers, etc. But why would you have a table that contains door numbers? What does it tell you? Door numbers are no entity by themselves; they belong to a street. And streets belong to cities. It would make no sense to find all people who live in some number 12 or in some Main Street.
One possible data model:
country (country_id, country_name, country_code)
pk country_id
county (county_id, county_name)
pk county_id
fk country_id -> country
city (city_id, city_name, postcode, county_id)
pk city_id
fk county_id -> county
street (street_id, street name, city_id)
pk street_id
fk city_id -> city
address (address_id, street_id, door_number
pk address_id
fk street_id -> street
With this data model we can check for consistency. If we want to enter the address 111 Millstreet, Oxford, Italy, the database will tell us there is no Oxford in Italy. We can also easily find addresses in the same street (and not like "there are five million addresses in Main Street", but "800 addresses in Main Street, Ohio").
If you want to insert a new address, you look up the country, then the county, etc. until you get to the street ID.
With your original tables:
declare
v_countyid integer;
v_cityid integer;
v_streetnameid integer;
begin
insert into city (city_name) values ('Saint Petersburg') returning cityid into v_city_d;
insert into streetname (street_name) values ('Park Drive') returning streetnameid into v_streetnameid;
...
insert into address (cityid, streetnameid, ...) values (v_cityid, v_streetnameid, ...);
commit;
end;
But then, there would be duplicate cities, street names, etc. in the tables. So we'd rather:
declare
v_countyid integer;
v_cityid integer;
v_streetnameid integer;
begin
select cityid into v_cityid from city where city_name = 'Saint Petersburg';
if cityid is null then
insert into city (city_name) values ('Saint Petersburg') returning cityid into v_city_d;
end if;
select streetnameid into v_streetnameid from streetname where street_name = 'Park Drive';
if v_streetnameid is null then
insert into streetname (street_name) values ('Park Drive') returning streetnameid into v_streetnameid;
end if;
...
insert into address (cityid, streetnameid, ...) values (v_cityid, v_streetnameid, ...);
commit;
end;
But as mentioned in my other answer. This data model doesn't make much sense anyway.

How can I insert a set of child records while updating the parent?

I'm using SQL Server 2005 and wish to create a number address records, updating the contact records with the new Id's:
Take the following tables
create table contact(id int primary key identity, home_address_id int, work_address_id int)
create table address(id int primary key identity, street varchar(25), number int)
And foreign keys:
ALTER TABLE dbo.contact ADD CONSTRAINT FK_contact_address1 FOREIGN KEY (home_address_id) REFERENCES dbo.address(id)
ALTER TABLE dbo.contact ADD CONSTRAINT FK_contact_address2 FOREIGN KEY (work_address_id) REFERENCES dbo.address(id)
some dummy data
insert into contact default values
insert into contact default values
insert into contact default values
How can I insert a default empty address record for all contacts who have no home address, and update the home_address_id in one go?
The first part is simple:
insert into address(street) select null from contact where home_address_id is null
I can even get the newly create address id's:
declare #addressTable table(id int)
insert into address(street)
OUTPUT INSERTED.Id INTO #addressTable
select null from contact where home_address_id is null
Here's the new id's
select * from #addressTable
But how to update the contact table with these new Id's?
If possible, I would suggest normalizing your database by adding a Contact_Addresses table:
CREATE TABLE Contact_Addresses
(
contact_id INT NOT NULL,
address_id INT NOT NULL,
address_type VARCHAR(10) NOT NULL,
CONSTRAINT PK_Contact_Addresses PRIMARY KEY CLUSTERED (contact_id, address_id, address_type),
CONSTRAINT FK_ContactAddresses_Contacts (contact_id) REFERENCES Contacts (id),
CONSTRAINT FK_ContactAddresses_Addresses (address_id) REFERENCES Addresses (id),
CONSTRAINT CK_ContactAddresses_address_type CHECK address_type IN ('HOME', 'WORK')
)
Next, I would suggest not putting "dummy" records in your database. It's going to end up causing headaches down the road. The database should contain an accurate record of the data in your system. If you want to display some value by default when no address exists in the system for a contact then handle that in your UI.
If you really must though, then the following code should do the trick:
;WITH C_CTE AS
(
SELECT
id,
home_address_id,
ROW_NUMBER() OVER(ORDER BY id) AS seq
FROM
Contacts
),
(
SELECT
id,
ROW_NUMBER() OVER(ORDER BY id) AS seq
FROM
Addresses
)
UPDATE
C_CTE
SET
home_address_id = A.id
FROM
C_CTE C
INNER JOIN A_CTE A ON A.seq = C.seq
I would do it from the moment you get a new contact, thusly:
[receive contact information]
//prior to inserting contact
declare #homeAddress int, #workAddress int
[insert home address here (real or default based on input)]
set #homeAddress = ##Identity
[insert work address here (real or default)]
set #workAddress = ##Identity
[insert contact here referencing #homeAddress & #workAddress]
For the stuff already in your table, you're going to have to associate all of your null value ids to a contact id. Or, you could clear out your null value addresses, and modify the above statement to an update somehow (brain's not working at the moment, so all I'm coming up with is a cursor, and cursors are evil).

Why does this query only select a single row?

SELECT * FROM tbl_houses
WHERE
(SELECT HousesList
FROM tbl_lists
WHERE tbl_lists.ID = '123') LIKE CONCAT('% ', tbl_houses.ID, '#')
It only selects the row from tbl_houses of the last occuring tbl_houses.ID inside tbl_lists.HousesList
I need it to select all the rows where any ID from tbl_houses exists within tbl_lists.HousesList
It's hard to tell without knowing exactly what your data looks like, but if it only matches the last ID, it's probably because you don't have any % at the end of the string, so as to allow for the list to continue after the match.
Is that a database in zeroth normal form I smell?
If you have attributes containing lists of values, like that HousesList attribute, you should instead be storing those as distinct values in a separate relation.
CREATE TABLE house (
id VARCHAR NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE list (
id VARCHAR NOT NULL,
PRIMARY KEY (id),
);
CREATE TABLE listitem (
list_id VARCHAR NOT NULL,
FOREIGN KEY list_id REFERENCES list (id),
house_id VARCHAR NOT NULL,
FOREIGN KEY house_id REFERENCES house (id),
PRIMARY KEY (list_id, house_id)
);
Then your distinct house listing values each have their own tuple, and can be selected like any other.
SELECT house.*
FROM house
JOIN listitem
ON listitem.house_id = house.id
WHERE
listitem.list_id = '123'