Why I can not INSERT with SELECT in SQL? - sql

I am trying to update my table Assembly. Can someone understand why it does not work? Why I can not INSERT into an existing table with a SELECT Statement?
DROP TABLE IF EXISTS Assembly;
GO
CREATE TABLE Assembly
(
AssemblyID INTEGER,
Part VARCHAR(100),
checksum INT,
PRIMARY KEY (AssemblyID, Part)
);
GO
INSERT INTO Assembly (AssemblyID, Part)
VALUES (1001, 'Bolt'), (1001, 'Screw'),
(2002, 'Nut'), (2002, 'Washer'),
(3003, 'Toggle'), (3003, 'Bolt');
GO
INSERT INTO Assembly (checksum)
VALUES (SELECT checksum(AssemblyID, Part) AS checksum
FROM Assembly)
SELECT *
FROM Assembly

You already have rows in that table. So you would UPDATE instead of INSERT.
eg
DROP TABLE IF EXISTS Assembly;
GO
CREATE TABLE Assembly
(
AssemblyID INTEGER,
Part VARCHAR(100),
checksum int,
PRIMARY KEY (AssemblyID, Part)
);
GO
INSERT INTO Assembly (AssemblyID, Part) VALUES
(1001,'Bolt'),(1001,'Screw'),(2002,'Nut'),
(2002,'Washer'),(3003,'Toggle'),(3003,'Bolt');
GO
update Assembly set checksum = checksum(AssemblyID, Part)
SELECT *
FROM Assembly
or simply make checksum a computed column:
CREATE TABLE Assembly
(
AssemblyID INTEGER,
Part VARCHAR(100),
checksum as checksum(AssemblyID, Part) persisted,
PRIMARY KEY (AssemblyID, Part)
);

Related

How to get ID of an identity column?

I have a many-to-many relationship between two tables, Users and Projects.
The table that connects those two together is named ProjectsUsers.
Here is the description of the tables and their relationships:
CREATE TABLE "Users"
(
Email VARCHAR(320) COLLATE SQL_Latin1_General_CP1_CI_AS PRIMARY KEY CHECK(LEN(Email) >= 3),
--More....
);
CREATE TABLE "Projects"
(
ProjectID INT PRIMARY KEY IDENTITY,
--More....
);
CREATE TABLE "ProjectsUsers"
(
UsersEmail VARCHAR(320) COLLATE SQL_Latin1_General_CP1_CI_AS CHECK(LEN(UsersEmail) >= 3) NOT NULL,
ProjectsID INT NOT NULL,
CONSTRAINT ProjectsUsers_PK PRIMARY KEY (UsersEmail, ProjectsID),
CONSTRAINT ProjectsID_FK FOREIGN KEY (ProjectsID) REFERENCES Projects (ProjectID)
ON DELETE CASCADE ON UPDATE CASCADE ,
CONSTRAINT UsersEmail_FK FOREIGN KEY (UsersEmail) REFERENCES Users(Email)
ON DELETE CASCADE ON UPDATE CASCADE
);
I am now trying to create a stored procedure that will insert a new project to the Projects table. After I add the project I want to create a reference to it in the ProjectsUsers table. The problem is, there is no possible way for me to know what the id of the project I just created - thus, I am unable to know what ID should I insert into the ProjectsUsers.
So if my stored procedure was something like this:
INSERT INTO Projects (Project, CreationDate, ProjectName)
VALUES (#project, GETDATE(), #email);
INSERT INTO ProjectsUsers VALUES (#email, ???)
How can I get the ID?
Just use SCOPE_IDENTITY like this:
INSERT INTO Projects (Project, CreationDate, ProjectName)
VALUES (#project, SYSDATETIME(), #email);
DECLARE #ProjectID INT = SCOPE_IDENTITY();
INSERT INTO ProjectsUsers
VALUES (#email, #ProjectID)
More all the relevant details about SCOPE_IDENTITY on the official Microsoft Documentation site.
As Sean Lange mentions, you can use SCOPE_IDENTITY to get last id inserted from within your proc
You can also use the OUTPUT clause and get possibly many ids. You can output in the screen or in a table, but it wont work if you are selecting from a table that has triggers.
Use the OUTPUT clause! Do not use the various identity functions or variables. This directly solves your problem:
DECLARE #ids TABLE (ProjectId int);
INSERT INTO Projects (Project, CreationDate, ProjectName)
OUTPUT inserted.ProjectId INTO #ids;
VALUES (#project, GETDATE(), #email);
INSERT INTO ProjectsUsers (UsersEmail, ProjectId)
SELECT #email, ProjectId
FROM #ids;
All the other methods of returning the identity have peculiarities:
Perhaps they don't work when the insert has multiple statements.
Perhaps concurrent inserts mess up the value.
Perhaps they don't work well with triggers.

Multiple table Set-based insert with identity column in table1 and foreign key in table2 [duplicate]

This question already has an answer here:
T-SQL - Insert Data into Parent and Child Tables
(1 answer)
Closed 3 years ago.
I am writing an Application that imports configuration data from multiple external systems. To decouple the user-data from imported data y am using an intermediate table. The following tables are simplified for the sake of the argument.
table [connection] (generic connection information referenced by other tables not included)
table [importConfig] (Connection configuration from one of the external systems)
table [connectionImportConfig] (intermediate table, linking [connection] and [importConfig]
For each connection in [importConfig] I want to create a row in [connection] with an identity column. Then I want to insert the new IDs from [connection] together with the identifier from [importConfig] in [connectionImportConfig].
Constraint: I don't want to use a cursor. Must be a set-based solution.
The examples I have found on stackoverflow are either not set-based or I found them not applicable for other reasons.
T-SQL Insert into multiple linked tables using a condition and without using a cursor (not valid for multiple rows, using ##IDENTITY)
How to perform INSERT/UPDATE to Linking (Join) table which has FKs to IDENTITY PKs (not valid for multiple rows. Not telling how to join the identity values with the original data I want to insert)
I have tried a lot and am stuck at the point where I have to insert the new IDs into connectionImportConfig
-- Test tables&data (stripped down version)
CREATE TABLE connection (ConnectionID int identity(1,1) NOT NULL, comment nvarchar(max) null)
CREATE TABLE connectionImportConfig(connectionID int NOT NULL, ConfigCode nvarchar(50) NOT NULL)
CREATE TABLE importConfig (ConfigCode nvarchar(50) NOT NULL)
DECLARE #MyConnection table (ConnectionID int not null);
insert into importConfig values ('a')
insert into importConfig values ('b')
insert into importConfig values ('c')
-- Insert into PK-table creating the IDs
INSERT INTO connection (comment)
OUTPUT INSERTED.ConnectionID INTO #MyConnection
SELECT * from importConfig
-- How do I insert the new IDs together with the ConfigCode? 1 has to be replaced with the ID.
-- JOIN doesn't seem to work because there is no join condition to use
INSERT INTO connectionImportConfig (connectionID, ConfigCode)
SELECT 1, ConfigCode FROM ImportConfig
select * from #MyConnection
select * from connectionImportConfig
-- Cleanup
DROP TABLE importConfig;
DROP TABLE connection;
DROP table connectionImportConfig;
Have a look at this. I am sure you could loosely modify this to your needs. I don't typically like inserting right off the output but it really depends on your data. Hope this example helps.
IF OBJECT_ID('tempdb..#ImportConfig') IS NOT NULL DROP TABLE #ImportConfig
IF OBJECT_ID('tempdb..#Config') IS NOT NULL DROP TABLE #Config
IF OBJECT_ID('tempdb..#Connection') IS NOT NULL DROP TABLE #Connection
GO
CREATE TABLE #ImportConfig (ImportConfigID INT PRIMARY KEY IDENTITY(1000,1), ImportConfigMeta VARCHAR(25))
CREATE TABLE #Config (ConfigID INT PRIMARY KEY IDENTITY(2000,1), ImportConfigID INT, ConfigMeta VARCHAR(25))
CREATE TABLE #Connection (ConnectionID INT PRIMARY KEY IDENTITY(3000,1), ConfigID INT, ConnectionString VARCHAR(50))
INSERT INTO #ImportConfig (ImportConfigMeta) VALUES
('IMPORT_ConfigMeta1'),('IMPORT_ConfigMeta2')
;MERGE
INTO #Config AS T
USING #ImportConfig AS S
ON T.ConfigID = S.ImportConfigID
WHEN NOT MATCHED THEN
INSERT (ImportConfigID, ConfigMeta) VALUES (
S.ImportConfigID,
REPLACE(S.ImportConfigMeta,'IMPORT_','')
)
OUTPUT INSERTED.ConfigID, 'CONNECTION_STRING: ' + INSERTED.ConfigMeta INTO #Connection;
SELECT 'IMPORT CONFIG' AS TableName, * FROM #ImportConfig
SELECT 'CONFIG' AS TableName, * FROM #Config
SELECT 'CONNECTION' AS TableName, * FROM #Connection

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.

Bulk adding records to one table and using their auto-ids in a relation table

I'm building an application that generates forms, however since these forms can have 100's of fields on them I would like to create them in a few queries as possible. I have no issues against using stored procedures or functions.
Forms
id - auto_inc
...
Fields
id - auto_inc
...
FormFields
formID - foreign key
fieldID - foreign key
When I create a form I run
INSERT INTO forms (xx) VALUES ('xx')
and then use
SELECT SCOPE_IDENTITY()
to get the ID from the form
Next I would like to add all the fields and their formfields relation in one or two queries.
INSERT INTO fields (xx, xx) VALUES ('xx', 'xx'), ('xx', 'xx'), ('xx', 'xx')...
Insert INTO formfields (formID, fieldID)
VALUES (#FORM_ID, #fieldID1), (#FORM_ID #fieldID2)...
I'm using php to generate these queries dynamically so #FORM_ID would actually be a php variable.
If I understand correctly you can use a combination of SCOPE_IDENTITY() and OUTPUT, here's an example
CREATE TABLE Foo (
PK INT IDENTITY(1,1) PRIMARY KEY,
I CHAR(1)
)
CREATE TABLE Bar (
PK INT IDENTITY(1,1) PRIMARY KEY,
J INT
)
CREATE TABLE Map (
FooPK INT,
BarPK INT
)
GO
INSERT INTO Foo(I) VALUES ('A')
DECLARE #FooPK INT = SCOPE_IDENTITY()
DECLARE #temp TABLE (BarPK INT)
INSERT INTO Bar(J)
OUTPUT INSERTED.PK INTO #temp
VALUES (4), (5),(6),(7)
INSERT INTO Map(FooPK, BarPK)
SELECT #FooPK, BarPK
FROM #temp
SELECT * FROM Foo
SELECT * FROM Bar
SELECT * FROM Map
GO
DROP TABLE Foo
DROP TABLE Bar
DROP TABLE Map
GO

insertion in a self referenced table

If I have a table
Table
{
ID int primary key identity,
ParentID int not null foreign key references Table(ID)
}
how does one insert first row into a table?
From a business logic point of view not null constraint on ParentID should not be dropped.
In SQL Server, a simple INSERT will do:
create table dbo.Foo
(
ID int primary key identity,
ParentID int not null foreign key references foo(ID)
)
go
insert dbo.Foo (parentId) values (1)
select * from dbo.Foo
results in
ID ParentID
----------- -----------
1 1
If you're trying to insert a value that will be different from your identity seed, the insertion will fail.
UPDATE:
The question is not too clear on what the context is (i.e. is the code supposed to work in a live production system or just a DB setup script) and from the comments it seems hard-coding the ID might not be an option. While the code above should normally work fine in the DB initialization scripts where the hierarchy root ID might need to be known and constant, in case of a forest (several roots with IDs not known in advance) the following should work as intended:
create table dbo.Foo
(
ID int primary key identity,
ParentID int not null foreign key references foo(ID)
)
go
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
Then one could query the last identity as usual (SCOPE_IDENTITY, etc.). To address #usr's concerns, the code is in fact transactionally safe as the following example demonstrates:
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
select * from dbo.Foo
select IDENT_CURRENT('dbo.Foo')
begin transaction
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
rollback
select IDENT_CURRENT('dbo.Foo')
insert dbo.Foo (parentId) values (IDENT_CURRENT('dbo.Foo'))
select * from dbo.Foo
The result:
ID ParentID
----------- -----------
1 1
2 2
3 3
currentIdentity
---------------------------------------
3
currentIdentity
---------------------------------------
4
ID ParentID
----------- -----------
1 1
2 2
3 3
5 5
If you need to use an explicit value for the first ID, when you insert your first record, you can disable the checking of the IDENTITY value (see: MSDN: SET IDENTITY_INSERT (Transact-SQL)).
Here's an example that illistrates this:
CREATE TABLE MyTable
(
ID int PRIMARY KEY IDENTITY(1, 1),
ParentID int NOT NULL,
CONSTRAINT MyTable_ID FOREIGN KEY (ParentID) REFERENCES MyTable(ID)
);
SET IDENTITY_INSERT MyTable ON;
INSERT INTO MyTable (ID, ParentID)
VALUES (1, 1);
SET IDENTITY_INSERT MyTable OFF;
WHILE ##IDENTITY <= 5
BEGIN
INSERT INTO MyTable (ParentID)
VALUES (##IDENTITY);
END;
SELECT *
FROM MyTable;
IF OBJECT_ID('MyTable') IS NOT NULL
DROP TABLE MyTable;
It seems like the NOT NULL constraint is not true for the root node in the tree. It simply does not have a parent. So the assumption that ParentID is NOT NULL is broken from the beginning.
I suggest you make it nullable and add an index on ParentID to validate that there is only one with value NULL:
create unique nonclustered index ... on T (ParentID) where (ParentID IS NULL)
It is hard to enforce a sound tree structure in SQL Server. You can get multiple roots for example or cycles in the graph. It is hard to validate all that and it is unclear if it is worth the effort. It might well be, depending on the specific case.