Is it possible to store a query in a variable and use that variable in Insert query? "#countrid =SELECT id FROM COUNTRIES WHERE description = 'asdf';" - sql

So I've been going through SQL migrations to insert data in a SEQUENTIAL manner specifically from parent to child.
I've inserted data in the parent table. Now I've to store the primary key value of that
specific row (WHERE condition is defined in query for reference " where description = '1234'") in a variable.
And while inserting data to the child table I've to use that primary key value stored in a variable in place of a foreign key column("country_code_id") of the child table.
I'm using Postgresql
CREATE TABLE Countries
(
id SERIAL,
description VARCHAR(100),
CONSTRAINT coutry_pkey PRIMARY KEY (id)
);
CREATE TABLE Cities
(
country_code_id int ,
city_id int,
description VARCHAR(100),
CONSTRAINT cities_pkey PRIMARY KEY (city_id),
CONSTRAINT fk_cities_countries FOREIGN KEY (country_code_id) REFERENCES Countries (id)
);
INSERT INTO COUNTRIES (description) VALUES('asdf');
#countrid = SELECT id FROM COUNTRIES WHERE description = 'asdf';
INSERT INTO cities VALUES (countrid, 1 , 'abc');

SQL does not have variables. The normal way to do this is to use INSERT ... RETURNING:
INSERT INTO countries (description) VALUES ('1234')
RETURNING id;
This will return the automatically generated primary key. You store that in a variable on the client side and run a second statement:
INSERT INTO cities (country_code_id, city_id, description)
VALUES (4711, 1, 'abc');
where 4711 is the value returned from the first statement. To avoid hard-coding the value, you can use a prepared statement, which also will boost performance.
An alternative, more complicated, solution is to run both statements in a single statement using a common table expression:
WITH country_ids AS (
INSERT INTO countries (description) VALUES ('1234')
RETURNING id
INSERT INTO (country_code_id, city_id, description)
SELECT id, 1, 'abc'
FROM country_ids;

Related

INSERT + SELECT data type mismatch on similar fields

I'm running the following SQLite workaround to add a primary key to a table that did not have one. I am getting a datatype mismatch on
INSERT INTO cities
SELECT id, name FROM old_cities;
However, the fields have exactly the same type. Is it possible that his happens due to running the queries from DbBrowser for SQLite?
CREATE table cities (
id INTEGER NOT NULL,
name TEXT NOT NULL
);
INSERT INTO cities (id, name)
VALUES ('pan', 'doul');
END TRANSACTION;
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE cities RENAME TO old_cities;
--CREATE TABLE cities (
-- id INTEGER NOT NULL PRIMARY KEY,
-- name TEXT NOT NULL
--);
CREATE TABLE cities (
id INTEGER NOT NULL,
name TEXT NOT NULL,
PRIMARY KEY (id)
);
SELECT * FROM old_cities;
INSERT INTO cities
SELECT id, name FROM old_cities;
DROP TABLE old_cities;
COMMIT;
You have defined the column id of the table cities to be INTEGER, but with this:
INSERT INTO cities (id, name) VALUES ('pan', 'doul');
you insert the string 'pan' as id.
SQLite does not do any type checking in this case and allows it.
Did you mean to insert 2 rows each having the names 'pan' and 'doul'?
If so, you should do something like:
INSERT INTO cities (id, name) VALUES (1, 'pan'), (2, 'doul');
Later you rename the table cities to old_cities and you recreate cities but you do something different: you define id as INTEGER and PRIMARY KEY.
This definition is the only one that forces type checking in SQLite.
So, when you try to insert the rows from old_cities to cities you get an error because 'pan' is not allowed in the column id as it is defined now.

How can I insert a row that references another postgres table via foreign key, and creates the foreign row too if it doesn't exist?

In Postgres, is there a way to atomically insert a row into a table, where one column references another table, and we look up to see if the desired row exists in the referenced table and inserts it as well if it is not?
For example, say we have a US states table and a cities table which references the states table:
CREATE TABLE states (
state_id serial primary key,
name text
);
CREATE TABLE cities (
city_id serial,
name text,
state_id int references states(state_id)
);
When I want to add the city of Austin, Texas, I want to be able to see whether Texas exists in the states table, and if so use its state_id in the new row I'm inserting in the cities table. If Texas doesn't exist in the states table, I want to create it and then use its id in the cities table.
I tried this query, but I got an error saying
ERROR: WITH clause containing a data-modifying statement must be at the top level
LINE 2: WITH inserted AS (
^
WITH state_id AS (
WITH inserted AS (
INSERT INTO states(name)
VALUES ('Texas')
ON CONFLICT DO NOTHING
RETURNING state_id),
already_there AS (
SELECT state_id FROM states
WHERE name='Texas')
SELECT * FROM inserted
UNION
SELECT * FROM already_there)
INSERT INTO cities(name, state_id)
VALUES
('Austin', (SELECT state_id FROM state_id));
Am I overlooking a simple solution?
Here is one option:
with inserted as (
insert into states(name) values ('Texas')
on conflict do nothing
returning state_id
)
insert into cities(name, state_id)
values (
'Dallas',
coalesce(
(select state_id from inserted),
(select state_id from states where name = 'Texas')
)
);
The idea is to attempt to insert in a CTE, and then, in the main insert, check if a value was inserted, else select it.
For this to work properly, you need a unique constraint on states(name):
create table states (
state_id serial primary key,
name text unique
);
Demo on DB Fiddlde
You can force the insert statement to return a value:
WITH inserted AS (
INSERT INTO states (name)
VALUES ('Texas')
ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.NAME
RETURNING state_id
)
. . .
The DO UPDATE SET forces the INSERT to return something.
I notice that you don't have a unique constraint, so you also need that:
ALTER TABLE states ADD CONSTRAINT unq_state_name
UNIQUE (name);
Otherwise the ON CONFLICT doesn't have anything to work with.

How do I select insert into select a table which already has values in the primary key column without adding new rows?

I'm working on a database for my school project in which I have to produce a functional database by normalizing sample tables given to us.
One table I'm having trouble with is itineraries. I produce 3 tables from the normalization which are "Destinations", "Itineraries" and "Itinerary_Destinations".
The code for Destinations is:
create table Destinations
(
DestinationID varchar(5) primary key,
Name varchar(45)
);
The code for Itineraries is:
create table Itineraries
(
ItineraryID varchar(5),
Name varchar(45)
);
The code for the last table is:
create table Itinerary_Destinations
(
DI varchar(5) primary key,
ItineraryID varchar(5) foreign key references Itineraries(ItineraryID),
Itinerary_Name varchar(45),
DestinationID varchar(5) foreign key references Destinations(DestinationID),
Destination_Name varchar(45)
);
Data has already been inserted into all 3 tables with the exception of 'Destination_Name' and 'Itinerary_Name' columns. The code I'm attempting to use is returning as error. The code is shown below.
insert into Itinerary_Destinations (Itinerary_name)
select Name from Itineraries where
Itineraries.ItineraryID = ItineraryID;
The error it returns is
Msg 515, Level 16, State 2, Line 1 Cannot insert the value NULL into
column 'DI', table 'DDDAssignment.dbo.Itinerary_Destinations'; column
does not allow nulls. INSERT fails. The statement has been terminated.
Is there a method to accomplish the task of inserting the Destination_Name and Itinerary_Name without creating new records that require primary keys?
Or should I do it manually?
If you want to modify records which already exist, then you should be using an UPDATE rather than an INSERT:
UPDATE a
SET Itinerary_name = b.Name
FROM Itinerary_Destinations a
INNER JOIN Itinerary_name b
ON a.ItineraryID = b.ItineraryID;
But, if you do have some data which is not already logically associated with the Itinerary_Destinations table, then using an insert is appropriate.
use coalesce funtion in case null it will insert blank string, as your column does not allow null value thats why you got that error in your query
insert into Itinerary_Destinations (Itinerary_name)
select coalesce(Name,' ') from Itineraries where
Itineraries.ItineraryID = ItineraryID;

how to create a Foreign-Key constraint to a subset of the rows of a table?

I have a reference table, say OrderType that collects different types of orders:
CREATE TABLE IF NOT EXISTS OrderType (name VARCHAR);
ALTER TABLE OrderType ADD PRIMARY KEY (name);
INSERT INTO OrderType(name) VALUES('sale-order-type-1');
INSERT INTO OrderType(name) VALUES('sale-order-type-2');
INSERT INTO OrderType(name) VALUES('buy-order-type-1');
INSERT INTO OrderType(name) VALUES('buy-order-type-2');
I wish to create a FK constraint from another table, say SaleInformation, pointing to that table (OrderType). However, I am trying to express that not all rows of OrderType are eligible for the purposes of that FK (it should only be sale-related order types).
I thought about creating a view of table OrderType with just the right kind of rows (view SaleOrderType) and adding a FK constraint to that view, but PostgreSQL balks at that with:
ERROR: referenced relation "SaleOrderType" is not a table
So it seems I am unable to create a FK constraint to a view (why?). Am I only left with the option of creating a redundant table to hold the sale-related order types? The alternative would be to simply allow the FK to point to the original table, but then I am not really expressing the constraint as strictly as I would like to.
I think your schema should be something like this
create table order_nature (
nature_id int primary key,
description text
);
insert into order_nature (nature_id, description)
values (1, 'sale'), (2, 'buy')
;
create table order_type (
type_id int primary key,
description text
);
insert into order_type (type_id, description)
values (1, 'type 1'), (2, 'type 2')
;
create table order_nature_type (
nature_id int references order_nature (nature_id),
type_id int references order_type (type_id),
primary key (nature_id, type_id)
);
insert into order_nature_type (nature_id, type_id)
values (1, 1), (1, 2), (2, 1), (2, 2)
;
create table sale_information (
nature_id int default 1 check (nature_id = 1),
type_id int,
foreign key (nature_id, type_id) references order_nature_type (nature_id, type_id)
);
If the foreign key clause would also accept an expression the sale information could omit the nature_id column
create table sale_information (
type_id int,
foreign key (1, type_id) references order_nature_type (nature_id, type_id)
);
Notice the 1 in the foreign key
You could use an FK to OrderType to ensure referential integrity and a separate CHECK constraint to limit the order types.
If your OrderType values really are that structured then a simple CHECK like this would suffice:
check (c ~ '^sale-order-type-')
where c is order type column in SaleInformation
If the types aren't structured that way in reality, then you could add some sort of type flag to OrderType (say a boolean is_sales column), write a function which uses that flag to determine if an order type is a sales order:
create or replace function is_sales_order_type(text ot) returns boolean as $$
select exists (select 1 from OrderType where name = ot and is_sales);
$$ language sql
and then use that in your CHECK:
check(is_sales_order_type(c))
You don't of course have to use a boolean is_sales flag, you could have more structure than that, is_sales is just for illustrative purposes.

sql: insert object spread over multiple tables

There are already posts, for example this one, which state that "naive" inheritance in SQL, namely using one table per class level, is a common thing. Example
create table parent
( id integer primary key
, more-parent-attributes
);
create table child
( id integer primary key references parent(id) on delete cascade
, more-child-attributes
);
My question is only how to insert a child in an idiomatic ANSI SQL way
into the table. The foreign key constraint makes the requirement that we first
insert a new row into parent and then a new row into child, using the id
of the parent row. I don't know how to do this (get this id) safely and portably, and
using only one request.
Hint: I'm rather a beginner and don't know imperative SQL programming--just in
case there is an obvious imperative solution.
You must execute two insert.
The first insert add row in parent table, the second insert add row in the child table.
Two insert operations can be grouped in the same transaction.
To get the correct inserted id in the parent table you must get a select id from parent.
Show below:
Step 1:
INSERT INTO parent (id, more att) values (your ID, other values)
Pay attention about ID value, you can use newid() (Sql server) uuid() (mySql) or autoincremental integer field
Step 2:
You retrieve your key querying your parent table with a functional key.
SELECT id FROM parent where functional_key satisfacted
For example, if I store in my parent table a list of employes, a functional key can be register number.
So your query becomes:
SELECT id FROM parent WHERE register_no = 'YOUR_REGISTER_NUMBER'
Step 3:
INSERT INTO child (id, fk_parent, other fields) values(id, fk_parent, other fields)
The fk_parent field must be valued with the result of Step 2.
In this step you can:
value fk_parent with a variable or you can use a subquery (step 2) in your insert statement.
I ended up doing something similar. You need to have some identifying piece of data that you can insert into the Parent in order to get the Id. If you're using this in some kind of application then you can use a GUID. In my application I used a concatenation of source columns that I knew would produce a unique value.
CREATE TABLE Parent
(
Id INT IDENTITY NOT NULL PRIMARY KEY
,SourceId VARCHAR(50) NOT NULL
);
CREATE TABLE Child
(
ParentId INT NOT NULL REFERENCES Parent (Id)
,Data VARCHAR(20)
);
-- Some procedure inserts the unique value
INSERT INTO Parent (SourceId) VALUES ('UNIQUE VALUE');
-- Another procedure inserts data using the unique value
DECLARE #Id INT;
SELECT #Id = Id FROM Parent WHERE SourceId = 'UNIQUE VALUE';
INSERT INTO Child (ParentId, Data) VALUES (#Id, 'Some Data');
Scope_Identity() is what you looking for:
DECLARE #Id INT
INSERT INTO parent (more-parent-attributes) values (.....)
SET #Id = Scope_Identity()
INSERT INTO child (parent(id), more-child-attributes) SELECT #Id, ....more-child-attributes
Scope_Identity() returns identity column in the same scope. It means that Parent key should be Identity column:
id int IDENTITY(1,1)PRIMARY KEY
and I think this is the case as if you were deciding what is the Parent key id, you would use the same for child insert.