sql: insert object spread over multiple tables - sql

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.

Related

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';"

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;

SQL insert ID from IDENTITY ID of the row being inserted

I would like to know, if there is a direct way to insert ID (generated at ID column with IDENTITY(1,1)) to another columns.
In another words, I am looking for SCOPE_IDENTITY() I could get at the time of inserting, not after the INSERT is commited.
I have a table, where there is a column with secondary ID (SID), which references rows from the same table and in some special cases it references itself.
The only way I know to do that is to do the INSERT and consequently UPDATE SID in those cases. Simplified example:
DECLARE #ID INT
INSERT INTO Table (SID) VALUES (NULL);
SELECT #ID = SCOPE_IDENTITY();
UPDATE Table SET SID = ID WHERE ID = #ID;
There are some glitches, i.e. due to the fact that the row may or may not reference itself, etc.
You can do this with an AFTER INSERT trigger. In case of self-reference, leave the column NULL and have the trigger set the column equal to the IDENTITY column.
In pseudo:
Join the table with inserted, filter where SID is NULL
For those rows, update the table and set SID = ID
If it is not possible to use the NULL value, in cases where it should be possible to have no reference at all, you can use another stub value. E.g. -1 if the IDs will always be positive. In that case, apply the above way of working and substitute NULL with -1.

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)

Duplicating parent, child and grandchild records

I have a parent table that represents a document of-sorts, with each record in the table having n children records in a child table. Each child record can have n grandchild records. These records are in a published state. When the user wants to modify a published document, we need to clone the parent and all of its children and grandchildren.
The table structure looks like this:
Parent
CREATE TABLE [ql].[Quantlist] (
[QuantlistId] INT IDENTITY (1, 1) NOT NULL,
[StateId] INT NOT NULL,
[Title] VARCHAR (500) NOT NULL,
CONSTRAINT [PK_Quantlist] PRIMARY KEY CLUSTERED ([QuantlistId] ASC),
CONSTRAINT [FK_Quantlist_State] FOREIGN KEY ([StateId]) REFERENCES [ql].[State] ([StateId])
);
Child
CREATE TABLE [ql].[QuantlistAttribute]
(
[QuantlistAttributeId] INT IDENTITY (1, 1),
[QuantlistId] INT NOT NULL,
[Narrative] VARCHAR (500) NOT NULL,
CONSTRAINT [PK_QuantlistAttribute] PRIMARY KEY ([QuantlistAttributeId]),
CONSTRAINT [FK_QuantlistAttribute_QuantlistId] FOREIGN KEY ([QuantlistId]) REFERENCES [ql].[Quantlist]([QuantlistId]),
)
Grandchild
CREATE TABLE [ql].[AttributeReference]
(
[AttributeReferenceId] INT IDENTITY (1, 1),
[QuantlistAttributeId] INT NOT NULL,
[Reference] VARCHAR (250) NOT NULL,
CONSTRAINT [PK_QuantlistReference] PRIMARY KEY ([AttributeReferenceId]),
CONSTRAINT [FK_QuantlistReference_QuantlistAttribute] FOREIGN KEY ([QuantlistAttributeId]) REFERENCES [ql].[QuantlistAttribute]([QuantlistAttributeId]),
)
In my stored procedure, i pass in the QuantlistId I want to clone as #QuantlistId. Since the QuantlistAttribute table has a ForeignKey I can easily clone that as well.
INSERT INTO [ql].[Quantlist] (
[StateId],
[Title],
) SELECT
1,
Title,
FROM [ql].[Quantlist]
WHERE QuantlistId = #QuantlistId
SET #ClonedId = SCOPE_IDENTITY()
INSERT INTO ql.QuantlistAttribute(
QuantlistId
,Narrative)
SELECT
#ClonedId,
Narrative,
FROM ql.QuantlistAttribute
WHERE QuantlistId = #QuantlistId
The trouble comes down to the AttributeReference. If I cloned 30 QuantlistAttribute records, how do I clone the records in the reference table and match them up with the new records I just inserted in to the QuantlistAttribute table?
INSERT INTO ql.AttributeReference(
QuantlistAttributeId,
Reference,)
SELECT
QuantlistAttributeId,
Reference,
FROM ql.QuantlistReference
WHERE ??? I don't have a key to go off of for this.
I thought I could do this with some temporary linking tables that holds the old attribute id's along with the new attribute id's. I don't know how to go about inserting the old Attribute Id's in to a temp table along with their new ones. Inserting the existing Attributes, by QuantlistId, is easy enough, but I can't figure out how to make sure I link the correct new and old Id's together in some way, so that the AttributeReference table can be cloned right. If I could get the QuantlistAttribute new and old Id's linked, I could join on that temp table and figure out how to restore the relationship of the newly cloned references, to the newly cloned attributes.
Any help on this would be awesome. I've spent the last day and a half trying to figure this out with no luck :/
Please excuse some of the SQL inconsistencies. I re-wrote up the sql real quick, trimming out a lot of additional columns, related-tables and constraints that weren't needed for this question.
Edit
After doing a little digging around, I found that OUTPUT might be useful for this. Is there a way to use OUTPUT to map the QuantlistAttributeId records I just inserted, to the QuantlistAttributeId they originated from?
You can use OUTPUT to get the inserted rows.
You can insert the data into QuantlistAttribute based on the order of ORDER BY c.QuantlistAttributeId ASC
Have a temp table/table variable which 3 columns
an id identity column
new QuantlistAttributeId
old QuantlistAttributeId.
Use OUTPUT to insert new identity values of QuantlistAttribute into a temp table/table variable.
The new IDs are generated in the same order as c.QuantlistAttributeId
Use a row_number() ordered by QuantlistAttributeId to match the old QuantlistAttributeId and new QuantlistAttributeIds based on row_number() and id of the table variable and update the values or old QuantlistAttributeId in the table variable
Use the temp table and join with AttributeReference and insert records in one go.
Note:
ORDER BY during INSERT INTO SELECT and ROW_NUMBER() to get matching old QuantlistAttributeId is required because looking at your question, there seems to be no other logical key to map old and new records together.
Query for above Steps
DECLARE #ClonedId INT,#QuantlistId INT = 0
INSERT INTO [ql].[Quantlist] (
[StateId],
[Title]
) SELECT
1,
Title
FROM [ql].[Quantlist]
WHERE QuantlistId = #QuantlistId
SET #ClonedId = SCOPE_IDENTITY()
--Define a table variable to store the new QuantlistAttributeID and use it to map with the Old QuantlistAttributeID
DECLARE #temp TABLE(id int identity(1,1), newAttrID INT,oldAttrID INT)
INSERT INTO ql.QuantlistAttribute(
QuantlistId
,Narrative)
--New QuantlistAttributeId are created in the same order as old QuantlistAttributeId because of ORDER BY
OUTPUT inserted.QuantlistAttributeId,NULL INTO #temp
SELECT
#ClonedId,
Narrative
FROM ql.QuantlistAttribute c
WHERE QuantlistId = #QuantlistId
--This is required to keep new ids generated in the same order as old
ORDER BY c.QuantlistAttributeId ASC
;WITH CTE AS
(
SELECT c.QuantlistAttributeId,
--Use ROW_NUMBER to get matching id which is same as the one generated in #temp
ROW_NUMBER()OVER(ORDER BY c.QuantlistAttributeId ASC) id
FROM ql.QuantlistAttribute c
WHERE QuantlistId = #QuantlistId
)
--Update the old value in #temp
UPDATE T
SET oldAttrID = CTE.QuantlistAttributeId
FROM #temp T
INNER JOIN CTE ON T.id = CTE.id
INSERT INTO ql.AttributeReference(
QuantlistAttributeId,
Reference)
SELECT
T.NewAttrID,
Reference
FROM ql.AttributeReference R
--Use OldAttrID to join with ql.AttributeReference and insert NewAttrID
INNER JOIN #temp T
ON T.oldAttrID = R.QuantlistAttributeId
Hope this helps.

Inserting row with foreign key relation in same transaction as primary row

I have a 2 tables where one has a foreign key relation to the other
CREATE TABLE foo (
id INT NOT NULL PRIMARY KEY IDENTITY,
value VARCHAR(50) DEFAULT NULL,
);
CREATE TABLE bar (
id INT NOT NULL PRIMARY KEY IDENTITY,
foo_key INT NOT NULL
value VARCHAR(50) DEFAULT NULL,
);
I'm using parameterized ADO.NET ExecuteReader to Insert new rows. My pickle is, if I want to insert 2 rows in different tables in the same transaction, i.e. before commit, I cannot insert rows in bar since I don't know the value that has been given foo.id yet. How would you go about doing that? i.e. How do I make sure that bar.foo_key get assigned the right value? Trying to select on it brings nothing, since I guess it is not actually there yet. Should I use a stored procedure to try and generate the key on the fly, or maybe there is an internal variable that can be used. Or is there a way to have the insert return the new id? Do I need a foreign key declaration, though I'm not sure that would be useful since again I still don't know what id to use?
The reason why I want to do it in one go, is due to error handling, I want to be able to roll everything back in case of an error.
You can use scope_identity() to retrieve the newly generated identity:
begin tran;
insert Foo (value) values ('6*7');
declare #fk int = scope_identity();
insert bar (foo_key, value) values (#fk, '42');
commit tran;
Per HLGEM's comment, to return the value of the newly generated identity to the client, you can use output:
insert Foo (value) output inserted.ID values ('6*7');
Note that for a transaction to span two sessions, you need a distributed transaction, which is very expensive.
I figured out I can return scope_identity() on the insert
INSERT INTO [foo] ([value]) VALUES (#0) SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY];