Let's say I have three tables implemented with a many-to-many relationship. Something like, Person(personID), PersonMovies(personID, movieID), and Movies(movieID). What is the correct way to do multiple inserts in sql server? I would like to insert the person, the movies and then be able to get all of the movies a person owns. So would it be three inserts within a transaction? If so, I would assume the easy part is inserting into the person and movie table, but how would I insert into the PersonMovies table, since that table relies on the existing ID's in the other two tables. I'm assuming that I would insert into Person and Movies, then some way set assign the ID's of the newly inserted tables to a variable from those two tables, then use those variables to insert into the bridge table. I have no idea, but I hope this makes some kind of sense as I'm VERY confused by this!!
Begin by inserting the Person record and use SCOPE_IDENTITY to get the unique ID if the inserted record. You can then use this to insert the person's Movies. Before you can insert a persons Movie you need to see whether it exists or not using IF EXISTS. If it does SELECT it from the existing table and assign it's unique ID to a variable. If it doesn't yet exist use the same technique for adding the person and insert the Movie then assign SCOPE_IDENTITY to the movie variable.
In PL/SQL there is an UPSERT statement which combines updating records or inserting them when required. I've added code below for a procedure which does an UPSERT in T/SQL and return the unique ID if a record had to be created.
IF EXISTS (SELECT id FROM dbo.sysobjects WHERE name = 'fts_upsert_team') DROP PROCEDURE fts_upsert_team
GO
CREATE PROCEDURE fts_upsert_team
#teamID INT OUTPUT,
#name VARCHAR(100)
AS
UPDATE
fts_teams
SET
name = #name
WHERE
teamID = #teamID
IF ##ROWCOUNT = 0
BEGIN
INSERT INTO fts_teams
(
name
)
VALUES
(
#name
)
SET #teamID = SCOPE_IDENTITY()
END
GO
I assume that you are having Person and Movies auto increment. If this is the case you need to capture what the key field is after the insert. You can use the scope_identity() function to get the this value. After each insert, save thes to a variable, and then when you isert into PersonMovies, use the saved values.
Related
Given 3 columns in a table tblEmails,
email1, nvarchar(50), NULLs not permitted
email2, nvarchar(50), NULLs permitted
email3, nvarchar(50), NULLs permitted
how do I prevent insertions or updates of a (non-NULL) value to any of the three columns if the value already exists in any of the other columns?
I was hoping to apply a CONSTRAINT by checking if the UNION ALL of the three tables contains the value to be inserted, but it seems count() can't be used in CONSTRAINTs.
Any solution implementable via the SSMS gui would be ideal.
I looked through at least a dozen SO posts, some SE posts, and articles online, but could not find a solution (or one that I could understand).
I would suggest creating a function which is then called by the check constraint. Following an example:
CREATE FUNCTION dbo.fn_chkMail(#mail nvarchar(100)) RETURNS INT AS
BEGIN
RETURN (SELECT COUNT(*) FROM mails WHERE mail1 = #mail) + (SELECT COUNT(*) FROM mails WHERE mail2 = #mail) + (SELECT COUNT(*) FROM mails WHERE mail3 = #mail)
END
and then
ALTER TABLE dbo.mails WITH CHECK ADD CONSTRAINT [CK_mails]
CHECK ((dbo.fn_chkMail([mail1]))+(dbo.fn_chkMail([mail2]))+(dbo.fn_chkMail([mail3]))=1)
See fiddle for details: http://sqlfiddle.com/#!18/6f375/7/1
You want to prevent modifying values using update when values are already in the table. Unfortunately, that suggests a trigger.
I think the logic looks like this:
CREATE TRIGGER trg_tblEmails_update ON tblEmails
AFTER UPDATE
AS BEGIN
IF (EXISTS (SELECT 1
FROM inserted i JOIN
deleted d
ON i.<primary key> = d.<primary key>
WHERE (d.email1 IS NOT NULL OR
d.email2 IS NOT NULL OR
d.email3 IS NOT NULL
) AND
(COALESCE(d.email1, '') <> COALESCE(i.email1, '') OR
COALESCE(d.email2, '') <> COALESCE(i.email2, '') OR
COALESCE(d.email3, '') <> COALESCE(i.email3, '')
)
)
)
BEGIN
RAISERROR('You can not update emails when value is already present', 16, 1);
ROLLBACK TRANSACTION;
END;
END;
I would suggest though that there might be a simpler data model. For instance, I would recommend storing the emails in a separate table with one row per email. You would use this table as:
When you insert a value into one email, you insert all three.
You have a unique index on the entity id and email number.
You don't allow updates on the table.
EDIT:
I suspect that you really want a unique constraint. You are not looking within a row but across all rows.
If that is the case, you simply have the wrong data model. You need a table with one email per row. This might require a column to identify which email, but something like this:
create table entity_emails (
entity_email_id int identity(1, 1) primary key,
which_email int,
email varchar(255)
);
Then you want the following constraints:
check (which_email in (1, 2, 3));
unique (entity_id, which_email);
unique (email);
The first two limits the number of emails to 3 per entity. The third insists that the email be unique over all rows and entities.
With the right data model, what you need to do may not require a trigger.
I have imported some data to a temp SQL table from an Excel file. Then I have tried to insert all rows to two related tables. Simply like this: There are Events and Actors tables with many to many relationship in my database. Actors are already added. I want to add all events to Events table and then add relation(ActorId) for each event to EventActors tables.
(dbo.TempTable has Title, ActorId columns)
insert into dbo.Event (Title)
Select Title
From dbo.TempTable
insert into dbo.EventActor (EventId, ActorId)
Select SCOPE_IDENTITY(), ActorId --SCOPE_IDENTITY() is for EventId
From dbo.TempTable
When this code ran, all events inserted into Events, but the relations didn't inserted into EventActors because of Foreign Key error.
I think there should be a loop. But I am confused. I don't want to write C# code for this. I know there would be a simple but advanced solution trick for this in SQL Server. Thanks for your help.
Use the output clause to capture the new IDs, with a merge statement to allow capture from both source and destination tables.
Having captured this information, join it back to the temp table for the second insert.
Note you need a unique id per row, and this assumes 1 row in the temp table creates 1 row in both the Event and the EventActor tables.
-- Ensure every row has a unique id - could be part of the table create
ALTER TABLE dbo.TempTable ADD id INT IDENTITY(1,1);
-- Create table variable for storing the new IDs in
DECLARE #NewId TABLE (INT id, INT EventId);
-- Use Merge to Insert with Output to allow us to access all tables involves
-- As Insert with Output only allows access to columns in the destination table
MERGE INTO dbo.[Event] AS Target
USING dbo.TempTable AS Source
ON 1 = 0 -- Force an insert regardless
WHEN NOT MATCHED THEN
INSERT (Title)
VALUES (Source.Title)
OUTPUT Source.id, Inserted.EventId
INTO #NewId (id, EventId);
-- Insert using new Ids just created
INSERT INTO dbo.EventActor (EventId, ActorId)
SELECT I.EventId, T.ActorId
FROM dbo.TempTable T
INNER JOIN #NewId I on T.id = T.id;
I created two tables, Employeeinfo and Employeerequests.
Table Employeeinfo can have one unique user with columns:
id (primary key, auto increment)
name
dep
address
and table Employeerequests can have multiple requests against one unique user ID with columns
id (primary key, auto increment)
CustomerID(foreign key to Employeeinfo(ID column))
category
requests.
Now I want to design a stored procedure in such a way so that I can insert values into both tables at the same time. Please help. I am very new to SQL. Thanks in advance.
This is a bit long for a comment.
SQL Server only allows you to insert into one table in a single query. You presumably want to provide both employee and request information. So that limitation on insert is a real problem.
You can get around the limitation by creating a view combining the two table and then defining an instead of insert trigger on the view. This is explained in the documentation.
That said, you seem to not have extensive SQL knowledge. So, I would recommend simply using two separate statements, one for each table. You can wrap them in a stored procedure, if you find that convenient.
In the stored procedure, you can use Output clause of Insert statement as:
DECLARE #MyTableVar TABLE (NewCustomerID INT);
-- The OUTPUT clause have access to all the columns in the table,
-- even those not part of Insert statement ex:EmployeeID.
INSERT INTO [dbo].[Employeeinfo] ([Name], [dep], [address])
OUTPUT INSERTED.Id INTO #MyTableVar
SELECT 'Test', 'TestDep', 'TestAddress'
-- then make insert in child table as
INSERT INTO [dbo].[Employeerequests] (CustomerID, category)
SELECT NewCustomerID, 'TestCat'
FROM #MyTableVar
Sample code here...
Hope that helps!
I have SQL TVP object with multiple records (for example 2 records).
I need to insert these records into two almost identical tables, the only difference is that second table has one more column which is foreign key pointing to first table. So it should loop TVP records and insert one by one into both tables, but getting scope_identity() of inserted record in first table and use it for record in second table.
1st iteration
insert into first table
get scope_identity() of inserted record
insert into second table (using scope indentity from first table to fill additional column)
And so on, depending on how many records are in TVP.
How can I achieve this?
Obviously I have left out a ton of code since we don't have your column and table names etc. You want an ID value in your TVP so you can count rows and use it in a where clause and while loop.
Declare #Var1 Int
Declare #YourTVP YourTVPName
Declare #RowCounter Int = 1
While (1=1)
Insert Into YourTable1 (Column1, ...)
Select (Column1, ...)
From #YourTVP
Where #RowCounter = SomeIDColumn
#Var1 = Select ##SCOPE_IDENTITY()
Insert Into YourTable2 (Column1, ...)
(#Var1, ...)
If (Some logic to Break your While loop)
Break
Else #RowCounter = #RowCounter + 1
End
Ok, let me be more clear. I will give demonstrative example::
I have TVP (let name it as PersonTVP) containing FirstName and LastName columns and assume PersonTVP has two records.
I have two tables, Person and PersonExtra. Person table has Id, FirstName and LastName columns, and PersonExtra has same columns + one additional column PersonId.
I need to insert data from PersonTVP into these two tables. Flow should be:
Take record from PersonTVP and insert into Person table
Get Scope_Identity() of inserted record (the value from Id column)
Insert same record into PersonExtra table and use Scope_Identity() for PersonId column (additional column)
And so on, loop as long as PersonTVP has records.
How do I retrieve the ID of an inserted row in SQL?
Users Table:
Column | Type
--------|--------------------------------
ID | * Auto-incrementing primary key
Name |
Age |
Query Sample:
insert into users (Name, Age) values ('charuka',12)
In MySQL:
SELECT LAST_INSERT_ID();
In SQL Server:
SELECT SCOPE_IDENTITY();
In Oracle:
SELECT SEQNAME.CURRVAL FROM DUAL;
In PostgreSQL:
SELECT lastval();
(edited: lastval is any, currval requires a named sequence)
Note: lastval() returns the latest sequence value assigned by your session, independently of what is happening in other sessions.
In SQL Server, you can do (in addition to the other solutions already present):
INSERT INTO dbo.Users(Name, Age)
OUTPUT INSERTED.ID AS 'New User ID'
VALUES('charuka', 12)
The OUTPUT clause is very handy when doing inserts, updates, deletes, and you can return any of the columns - not just the auto-incremented ID column.
Read more about the OUTPUT clause in the SQL Server Books Online.
In Oracle and PostgreSQL you can do this:
INSERT INTO some_table (name, age)
VALUES
('charuka', 12)
RETURNING ID
When doing this through JDBC you can also do that in a cross-DBMS manner (without the need for RETURNING) by calling getGeneratedKeys() after running the INSERT
I had the same need and found this answer ..
This creates a record in the company table (comp), it the grabs the auto ID created on the company table and drops that into a Staff table (staff) so the 2 tables can be linked, MANY staff to ONE company. It works on my SQL 2008 DB, should work on SQL 2005 and above.
===========================
CREATE PROCEDURE [dbo].[InsertNewCompanyAndStaffDetails]
#comp_name varchar(55) = 'Big Company',
#comp_regno nchar(8) = '12345678',
#comp_email nvarchar(50) = 'no1#home.com',
#recID INT OUTPUT
-- The '#recID' is used to hold the Company auto generated ID number that we are about to grab
AS
Begin
SET NOCOUNT ON
DECLARE #tableVar TABLE (tempID INT)
-- The line above is used to create a tempory table to hold the auto generated ID number for later use. It has only one field 'tempID' and its type INT is the same as the '#recID'.
INSERT INTO comp(comp_name, comp_regno, comp_email)
OUTPUT inserted.comp_id INTO #tableVar
-- The 'OUTPUT inserted.' line above is used to grab data out of any field in the record it is creating right now. This data we want is the ID autonumber. So make sure it says the correct field name for your table, mine is 'comp_id'. This is then dropped into the tempory table we created earlier.
VALUES (#comp_name, #comp_regno, #comp_email)
SET #recID = (SELECT tempID FROM #tableVar)
-- The line above is used to search the tempory table we created earlier where the ID we need is saved. Since there is only one record in this tempory table, and only one field, it will only select the ID number you need and drop it into '#recID'. '#recID' now has the ID number you want and you can use it how you want like i have used it below.
INSERT INTO staff(Staff_comp_id)
VALUES (#recID)
End
-- So there you go. I was looking for something like this for ages, with this detailed break down, I hope this helps.