I'm having problem creating a trigger. The database model look something like this:
CREATE TABLE Unit (
SerialNO VARCHAR(3)
PRIMARY KEY (serialNO)
);
CREATE TABLE PackageConfig (
PartNO VARCHAR(15),
SomeValue VARCHAR(20),
PRIMARY KEY (PartNO)
);
CREATE TABLE Package (
ID INT IDENTITY(1,1),
Config VARCHAR(15),
PRIMARY KEY (ID),
FOREIGN KEY (Config) REFERENCES PackageConfig(PartNO)
);
CREATE TABLE UnitInPackage (
Package INT,
Unit VARCHAR(3),
PRIMARY KEY (Package,Unit),
FOREIGN KEY (Package) REFERENCES Package(ID),
FOREIGN KEY (Unit) REFERENCES Unit(SerialNO)
);
There are Units, PackageConfigurations and Packages. A package has exactly one PackageConfiguration and the relation between Package and Unit is a many (units) to at most one (package).
What I'm trying to accomplish is a trigger on a view that would let me do the following (assuming that the PackageConfig and Units exist):
INSERT INTO v_MultiUnitPackage (PackageConfig, Unit1, Unit2, Unit3)
VALUES ('SomeConfig','abc','bcd','cde');
Which inside the trigger would do something like:
DECLARE #last INT;
INSERT INTO Package (Config) VALUES ('SomeConfig');
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
VALUES (#last,'abc'),(#last,'bcd'),(#last,'cde');
Or for more columns:
INSERT INTO Package (Config) VALUES ('SomeConfig');
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
VALUES (#last,'hgf'),(#last,'gfe'),(#last,'fed'),(#last,'edc'),(#last,'dcb'),(#last,'cba');
To summarize, I'd like to create a trigger that takes a PackageConfig and N-number of Units and insert them in the corresponding tables.
I've been looking into creating a dummy view that simply has the correct data types and a great enough number of columns to allow for the number of Units I want but didn't find a solution.
I also looked into something like grouping by Package.ID and for each group selecting the first unit that hasn't already been selected to a previous column. Since GROUP BY is only usable with aggregate functions I'm unsure how to realize this idea.
Realistically I don't see ever needing more than 5 or so Units but would prefer a generic solution to my problem.
Perhaps there is a really simple solution that I'm just not seeing. Any help is greatly appreciated.
Insert with a view is one way of doing it, so you just need to have your dummy view update with the number of units you need. Here is how the trigger should looks like:
create view v_MultiUnitPackage
as
select 'Some-Config' as PackageConfig, 'abc' as Unit1, 'bcd' as Unit2, 'cde' as Unit3
go
create TRIGGER tg_I_v_MultiUnitPackage
ON v_MultiUnitPackage
INSTEAD OF INSERT
AS
BEGIN
DECLARE #last INT;
INSERT INTO Package (Config)
SELECT inserted.PackageConfig
FROM inserted
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
SELECT #last, inserted.Unit1
FROM inserted;
INSERT INTO UnitInPackage (Package,Unit)
SELECT #last, inserted.Unit2
FROM inserted;
INSERT INTO UnitInPackage (Package,Unit)
SELECT #last, inserted.Unit3
FROM inserted;
END
A better option in my opinion, would be passing the units to a stored procedure as XML and handle the insert there. Here is how the procedured should looks like:
CREATE PROCEDURE usp_i_PackageUnits
(
#PackageConfig varchar(15),
#Units xml -- <root><unit>abc</unit><unit>bcd</unit><unit>cde</unit>
)
AS
BEGIN
DECLARE #last INT;
INSERT INTO Package (Config) VALUES (#PackageConfig);
SET #last = SCOPE_IDENTITY();
INSERT INTO UnitInPackage (Package,Unit)
select node.value('(.)[1]', 'VARCHAR(3)') from #xml.nodes('/root/unit')as result(node)
END
Related
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.
I have seen some articles mention the possibility of a Trigger on a View, triggering on either insert, updates or deletes to one of the base tables from which the View is created.
However I am not able to get a simple example to work.
CREATE TABLE [Test].[Data] (
Id INT PRIMARY KEY IDENTITY (1,1),
Data VARCHAR(255) NOT NULL,
);
GO
CREATE VIEW [Test].[View] AS SELECT * FROM [Test].[Data];
GO
CREATE TABLE [Test].[Queue] (
Id INT PRIMARY KEY IDENTITY (1,1),
DataId INT NOT NULL,
Action VARCHAR(255) NOT NULL,
Timestamp DATETIME NOT NULL,
);
GO
CREATE TRIGGER InsertTrigger ON [Test].[View] INSTEAD OF INSERT AS
BEGIN
DECLARE #DataId INT;
DECLARE #Timestamp DATETIME;
SET #DataId = (SELECT Id FROM INSERTED);
SET #Timestamp = GETDATE();
INSERT INTO [Test].[Queue] (DataId, Action, Timestamp) VALUES (#DataId, 'Insert', #Timestamp)
END
GO
ENABLE TRIGGER InsertTrigger ON [Test].[View];
GO
INSERT INTO [Test].[Data] (Data) VALUES ('Testdata');
The trigger is not firing, is the above not possible or is there something wrong with my Sql?
Edit: Although answered I would like to clarify the question. The idea was to get the trigger on the View to fire, when there was an Insert to the base table and not the View itself.
A trigger on a view will only work on inserts into that view, not on any inserts into tables to which the view references.
In your script you're not inserting into that view, you're inserting into a table.
In addition to not testing this correctly, your view is wrong. You are not considering that inserted represents multiple rows, not one.
So:
CREATE TRIGGER InsertTrigger ON [Test].[View] INSTEAD OF INSERT AS
BEGIN
INSERT INTO [Test].[Queue] (DataId, Action, Timestamp)
SELECT i.Id, 'Insert', GETDATE()
FROM Inserted;
END;
GO
INSERT INTO [Test].[View] (Data)
VALUES ('Testdata');
I got a performance issue with an import task (user need to import new data into the system) and could use little help.
The data within the database looks like this:
Table Categories:
Columns:
Id int primary key identity(1,1)
Code nvarchar(20) not null
RootId int not null
ParentId int not null
Table CategoryNames:
Columns:
CategoryId int not null foreign key references Category (Id)
LanguageId int not null foreign key references Language (Id)
Name nvarchar(100)
Currently it's working like this: for each row
Create connection to database
Call stored procedure with data[i]
Close connection
I already got rid of the creating and closing connection for each row of data. But it's still not good enough.
Currently the task needs round about 36 minutes to insert ~22000 categories.
The stored procedure looks like this:
procedure Category_Insert
(#code nvarchar(20),
#root int,
#parent int,
#languageId int,
#name nvarchar(100),
#id int output)
as
set nocount on
insert into Categories (Code, RootId, ParentId)
values (#code, #root, #parent)
select id = scope_identity()
insert into CategoryNames (CategoryId, LanguageId, Name)
values (#id, #languageId, #name)
Got any advice how I can speed up the performance of that task?
I would love to use bulk insert or something like that, but how would I realize the logic of the stored procedure with bulk insert?
Or is the any other way to speed this up?
I'm having trouble creating a trigger for my database.
I Have two tables, lets call them A and AHistory.
A has an ID and a value.
I want AHistory to keep track of the value and date for a set time.
AHistory Has an Auto Incremented ID, the value and a timestamp.
this is as far as I have gotten:
CREATE TRIGGER Atrigger
ON A
AFTER INSERT
AS
BEGIN
INSERT INTO AHistory
SELECT value FROM INSERTED
END
GO
Assuming that your db schema looks like
CREATE TABLE a
(id int identity primary key,
value varchar(32));
CREATE TABLE ahistory
(id int identity primary key,
aid int, value varchar(32),
eventdate datetime);
Your trigger might look like
CREATE TRIGGER atrigger ON A
AFTER INSERT AS
INSERT INTO ahistory (aid, value, eventdate)
SELECT id, value, GETDATE() FROM INSERTED;
Here is SQLFddle demo
I have two tables:
Rooms:
ID (auto-incrementing primary key, int)
Topic (varchar(50))
MangerId (varchar(50))
Rooms_Users:
UserId (varchar(50))
RoomId (varchar(50))
both fields together are the primary key
I want to insert a room but I also must insert the manger to the table rooms_users.
Here is what I have so far:
ALTER PROCEDURE [dbo].[Creat_Room] #MangerId varchar(50) ,#Topic varchar(50)
AS
BEGIN
SET NOCOUNT
insert into Rooms(ManagerId,Topic) values(#MangerId,#Topic)
insert into Rooms_Users(UserId,RoomId) values(#MangerId,?????????????)
END
The ????????????? is the problem: I don't know what to put here i want to put the roomid i insert above.
You can use the output clause. Look at MSDN here: OUTPUT Clause (Transact-SQL)
Example:
declare #tbl table
(
NewID int
)
insert into Rooms(ManagerId,Topic)
output inserted.ID into #tbl
values(#MangerId,#Topic)
Then the table variable will contains the new id given to the row you inserted
Use the SCOPE_IDENTITY() function:
ALTER PROCEDURE [dbo].[Create_Room]
#ManagerId varchar(50),
#Topic varchar(50)
AS
BEGIN
DECLARE #NewRoomID INT
insert into Rooms(ManagerId, Topic) values(#MangerId, #Topic)
SELECT #NewRoomID = SCOPE_IDENTITY()
insert into Rooms_Users(UserId, RoomId) values(#ManagerId, #NewRoomID)
END
This function will return the last inserted IDENTITY value in this particular scope - the scope of your stored procedure.