Use a Trigger After Insert for updating a table? - sql

I'm using a Trigger on SQL server to update a table stock when a sell is inserted into another table, but the trigger is not doing anything to the table, I suspect I must have an error I can't decipher. When I execute the test Inserts it shows no change to the first table.
The tables are:
Sku VARCHAR (50) PRIMARY KEY,
Stock NUMERIC (38)
);
CREATE TABLE dbo.Salida_Producto (
Numero_Salida INT PRIMARY KEY,
Sku VARCHAR (50),
Cantidad_Salida INT,
FOREIGN KEY(Sku) REFERENCES Stock(Sku)
);
--Test Tabla Stock. Test Values.
INSERT INTO dbo.Stock VALUES ('El Mitchies',100);
INSERT INTO dbo.Stock VALUES ('La Karencilla',200);
INSERT INTO dbo.Stock VALUES ('Perro',300);```
The Trigger:
CREATE TRIGGER [dbo].[tr_for_insert]
ON [dbo].[Salida_Producto]
AFTER INSERT
AS
BEGIN
DECLARE
#Item varchar,
#Cuantos numeric
SELECT #Item = INSERTED.Sku,
#Cuantos = INSERTED.Cantidad_Salida
FROM INSERTED
UPDATE Stock
SET Stock = Stock - #Cuantos
WHERE Sku = #Item
END;
GO
Test Inserts
INSERT INTO dbo.Salida_Producto VALUES (1, 'El Mitchies',3);
INSERT INTO dbo.Salida_Producto VALUES (2, 'La Karencilla',6);
INSERT INTO dbo.Salida_Producto VALUES (3,'Perro',130); ```
The problem I have is that the message box says:
(0 row(s) affected)
(1 row(s) affected)

You have thought your trigger with a row by row approach. It is not good practice. You must learn to think with a "by set" approach.
So a better way to achieve your goal should be something like :
Update S
Set S.Stock = S.Stock - I.Cantidad_Salida
From Stock S
Inner Join inserted I
On S.Sku = I.Sku

Related

How to create trigger that updates field on parent with the sum from 2 children

Updated to include screenshot - I need to create a trigger to update a field on a parent table with the sum of the values from two child tables. When the parent record is saved it should calculate ParentTotalEmployees = Sum(CountryTotEmployees) + Sum(StateTotEmployees). I can get it to populate if I only reference one child table but I haven't been able to figure out how to include the second child table.
ALTER TRIGGER [dbo].[DD_UpdateTotEmp] ON [dbo].[DEALDATA]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
;WITH GrandTotCountry AS (
SELECT c.QDEALDATA1,
SUM(QTOTCOUNTRYEMP) AS TotCountryEmp
FROM
DEALDATA1 c
GROUP BY c.QDEALDATA1
),
GrandTotState AS (
SELECT c.QDEALDATA,
SUM(QNUMSTATEEMP) AS TotStateEmp
FROM
DEALDATA2 c
GROUP BY c.QDEALDATA)
UPDATE T1
SET T1.QGRANDTOTEMP = (SELECT TotCountryEmp
FROM GrandTotCountry T2
WHERE T2.QDEALDATA=i.QDEALDATA)
FROM DEALDATA T1
INNER JOIN Inserted i ON T1.QDEALDATA=i.QDEALDATA
END
OR THIS ONE
CREATE TRIGGER [dbo].[DD_UpdateTotEmp] ON [dbo].[DEALDATA]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
UPDATE T1
SET T1.QGRANDTOTEMP = (SELECT SUM(QTOTCOUNTRYEMP)
FROM DEALDATA1 T2
WHERE T2.QDEALDATA=i.QDEALDATA)
FROM DEALDATA T1
INNER JOIN Inserted i ON T1.QDEALDATA=i.QDEALDATA
END
Sample Data
USE TEMPDB
GO
-- Parent Table
CREATE TABLE [dbo].[DEALDATA](
[QDEALDATA] [varchar](36) NOT NULL PRIMARY KEY CLUSTERED,
[MATTERSYSID] [varchar](36) NULL,
[QGRANDTOTEMP] [numeric](12, 0) NULL )
GO
INSERT INTO DEALDATA VALUES ('1404fcb1','C333897E',NULL);
INSERT INTO DEALDATA VALUES ('a51f9f8a','8AE3F809',NULL);
GO
-- Country Emp Table
CREATE TABLE [dbo].[DEALDATA1](
[QDEALDATA1] [varchar](36) NOT NULL PRIMARY KEY CLUSTERED,
[QDEALDATA] [varchar](36) NULL,
[QCOUNTRY] [varchar](40) NULL,
[QTOTCOUNTRYEMP] [numeric](12, 0) NULL )
GO
INSERT INTO DEALDATA1 VALUES ('60ae5737','a51f9f8a','Monaco',5);
INSERT INTO DEALDATA1 VALUES ('62ceecb9','a51f9f8a','Australia',10);
INSERT INTO DEALDATA1 VALUES ('a645fcd1','1404fcb1','United States',100);
GO
-- State Emp Table
CREATE TABLE [dbo].[DEALDATA2](
[QDEALDATA2] [varchar](36) NOT NULL PRIMARY KEY CLUSTERED,
[QDEALDATA] [varchar](36) NULL,
[QEMPSTATE] [varchar](40) NULL,
[QNUMSTATEEMP] [numeric](12, 0) NULL )
GO
INSERT INTO DEALDATA2 VALUES ('453b7b64','a51f9f8a','NY',50);
INSERT INTO DEALDATA2 VALUES ('e803b38f','a51f9f8a','KY',50);
INSERT INTO DEALDATA2 VALUES ('413954e1','1404fcb1','MO',20);
INSERT INTO DEALDATA2 VALUES ('ef2213e5','1404fcb1','HI',10);
GO
Thank you in advance in helping me with this.
A trigger (insert, Update, and/or Delete) belongs to a particular table. If you need a trigger on two tables (or many tables) you will need two triggers (or many triggers).
However, you can write a stored-procedure and call it from two triggers. And Since you have used after trigger, you don't need to use Inserted, Deleted objects.
It can be like this:
ALTER TRIGGER Trigger1 ON Table1
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
EXEC‌ TheProcedure
END‌
and
ALTER TRIGGER Trigger2 ON Table2
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
EXEC‌ TheProcedure
END‌
As you see the notes bellow, the above code performance is really bad. The best you can do is to redesign your tables. However, if you prefer slight modification on your data base design, you can create two aggregate tables for your child tables and then use a VIEW‌ to combine them into a single result.
Here is the solution. Thanks to all who responded!
UPDATE dst
SET dst.GrandTotEmp = COALESCE(tot1.TotCountryEmp, 0) + COALESCE(tot2.TotStateEmp, 0)
FROM DEALDATA as dst
JOIN inserted AS i ON dst.QDEALDATA = i.QDEALDATA
LEFT JOIN GrandTotCountry AS tot1 ON tot1.QDEALDATA = dst.QDEALDATA
LEFT JOIN GrantTotState AS tot2 ON tot2.QDEALDATA = dst.QDEALDATA

tsql - How to copy data in the same table with new ID and with foreign key relationship

I have 3 tables, All 3 tables have Auto-Increment primary key column with foreign key relationship.
My problem is that I want to copy the related data in same table.
And also to maintain the PK-->FK relationship.
For example I want to copy GoalID = 1.
I have also created variable tables and data as my scenario.
Declare #tblCompanyGoal Table
(
GoalID int identity(1,1), --PKID
APID int,
Goal nvarchar(500)
)
Declare #tblPMCompanyObjectives Table
(
ObjectID int identity(1,1), --PKID
ApID int,
Objective nvarchar(500),
GoalID int --FK --> #tblCompanyGoal.GoalID
)
Declare #tblPMCompanyStrategies Table
(
StrgID int identity(1,1), --PKID
Strategies nvarchar(500),
ObjectID int --FK --> ##tblPMCompanyObjectives.ObjectID
)
Insert into #tblCompanyGoal (APID, Goal)Values(500, 'C-Goal1')
Insert into #tblCompanyGoal (APID, Goal)Values(600, 'C-Goal2')
insert into #tblPMCompanyObjectives (ApID, Objective, GoalID)values(500, 'BF', 1)
insert into #tblPMCompanyObjectives (ApID, Objective, GoalID)values(500, 'LF', 1)
insert into #tblPMCompanyObjectives (ApID, Objective, GoalID)values(500, 'BFA', 2)
Insert into #tblPMCompanyStrategies(Strategies, ObjectID)Values('GTK', 1)
Insert into #tblPMCompanyStrategies(Strategies, ObjectID)Values('GTK2', 1)
Insert into #tblPMCompanyStrategies(Strategies, ObjectID)Values('ASK', 1)
Insert into #tblPMCompanyStrategies(Strategies, ObjectID)Values('WER', 2)
Insert into #tblPMCompanyStrategies(Strategies, ObjectID)Values('NFT', 2)
Insert into #tblPMCompanyStrategies(Strategies, ObjectID)Values('KRG', 3)
How can I accomplish this task? I have tried it with cursor but not succeeded.
After the insert into #tblCompanyGoal Table, use the SCOPE_IDENTITY value. It will automatically contain the new identity ID (for the newly inserted row) from the last insert statement. (You could also make a join like in the last insert, if you prefer without a variable.)
Then use this value in the following insert, as the new foreign key value.
The last join is a little bit tricky, you have to map the old ids with the new ones. I added some comments to help explain the logic.
This will give you a copy of the related data.
DECLARE #NewGoalID INT, #OldGoalID INT
SET #OldGoalID = 1
INSERT INTO #tblCompanyGoal(APID, Goal)
SELECT APID, Goal FROM #tblCompanyGoal WHERE GoalID=1
SET #NewGoalID = SCOPE_IDENTITY()
INSERT INTO #tblPMCompanyObjectives(ApID, Objective, GoalID)
SELECT ApID, Objective, #NewGoalID FROM #tblPMCompanyObjectives WHERE GoalID = #OldGoalID
INSERT INTO #tblPMCompanyStrategies(Strategies, ObjectID)
SELECT s.Strategies, no.ObjectID
-- get the old object rows
FROM #tblPMCompanyStrategies s
INNER JOIN #tblPMCompanyObjectives oo ON oo.objectid = s.objectid
INNER JOIN #tblCompanyGoal oc ON oc.GoalID = oo.GoalID AND oc.GoalID = #OldGoalID
-- get the matching rows new object IDs
INNER JOIN #tblPMCompanyObjectives no ON no.GoalID = #NewGoalID AND no.ApID=oo.ApID AND no.Objective=oo.Objective

Prohibit Inserting Rows With Default Constraint

Is there any way of how to prevent inserting data in specified columns in table and use only the default (constraint) values?
E.g. I have columns:
LogInsert (DF GETDATE())
LogUser (DF ORIGINAL_LOGIN())
both defined with DEFAULT constraint. I do not want to allow users to insert into those columns, but use default values here instead when inserting new row.
This should raise an error.
INSERT INTO T1
( C1
,C2
,LogInsert
,LogUser
)
VALUES ( 'A'
,'B'
,'20160101 10:53'
,'domain\user'
);
User should be able to perform the following script without error.
INSERT INTO T1
( C1, C2 )
VALUES ( 'A', 'B' );
You could always give your users a view to work against instead of a table. You can then either choose to hide the columns completely or (as here) make them computed so that they cannot insert a value into the column, via the view:
create table dbo._T1 (
ID int IDENTITY(1,1) not null,
Inserted datetime2 constraint DF__T1_Inserted DEFAULT (SYSDATETIME()) not null,
ABC varchar(10) not null,
constraint PK__T1 PRIMARY KEY (ID)
)
go
create view dbo.T1
with schemabinding
as
select
ID,
COALESCE(Inserted,SYSDATETIME()) as Inserted,
ABC
from dbo._T1
go
insert into dbo.T1 (ABC) values ('abc')
go
insert into dbo.T1 (ABC,Inserted) values ('def',SYSDATETIME())
Results:
(1 row(s) affected)
Msg 4406, Level 16, State 1, Line 19
Update or insert of view or function 'dbo.T1' failed because it contains a derived or constant field.
All of the users queries continue to just use T1. It just happens to be a view rather than a table.
In the above, the view uses COALESCE(Inserted,SYSDATETIME()). It doesn't really matter what's used here, and it doesn't need to match e.g. the default definition. All that's important is that some computation is performed on the Inserted column so that it becomes a read-only column in the view.
You can create a Check Constraint on the table, for example the below
CREATE TABLE [dbo].[T1]
(
[C1] VARCHAR(50)
,[LogInsert] DATETIME DEFAULT GETDATE()
,[LogUser] VARCHAR(500) DEFAULT ORIGINAL_LOGIN()
)
ALTER TABLE [T1] WITH CHECK ADD CONSTRAINT [CK_T1_LogInsert] CHECK ([LogInsert] = GETDATE())
ALTER TABLE [T1] WITH CHECK ADD CONSTRAINT [CK_T1_LogUser] CHECK ([LogUser] = ORIGINAL_LOGIN())
INSERT INTO [dbo].[T1] ([C1]) VALUES ('A')
INSERT INTO [dbo].[T1] ([C1]) VALUES ('B')
INSERT INTO [dbo].[T1] ([C1]) VALUES ('C')
SELECT * FROM [dbo].[T1]
--Will Fail
INSERT INTO [dbo].[T1] ([C1],[LogInsert]) VALUES ('D','2016-11-11 00:00')
INSERT INTO [dbo].[T1] ([C1],[LogUser]) VALUES ('D','Not Your UserName')
OR
You can force the user to only insert using a stored procedure and not allow that as a parameter, this can be done with a table variable too for bulk inserts.

SQL unique constraint on either of 2 columns

I have a table in SQL that I would like to have a unique constraint so that any of two values cannot already exist.
for example if I have 2 columns, I would like it to not insert if the value in column B does not exist in column A or column B.
Is this possible and if so how is it done?
example:
Column A | Column B
--------------------
4 | 6
I would want any object that tries to insert 4 or 6 not to be allowed into the table
Trigger with ROLLBACK TRANSACTION is the way to go.
create trigger dbo.something after insert as
begin
if exists ( select * from inserted where ...check here if your data already exists... )
begin
rollback transaction
raiserror ('some message', 16, 1)
end
end
You can create a function which takes in these values & create a check constraint on it (referencing your functions return values) to your table.
create table t11 (Code int, code2 int)
create function fnCheckValues (#Val1 int, #Val2 int)
Returns int /*YOu can write a better implementation*/
as
Begin
DECLARE #CntRow int
IF(#Val1 IS NULL OR #Val2 IS NULL) RETURN 0
select #CntRow = count(*) from t11
where Code in (#Val1,#Val2 ) or Code2 in (#Val1,#Val2 )
RETURN #CntRow
End
GO
alter table t11 Add constraint CK_123 check ([dbo].[fnCheckValues]([Code],[code2])<=(1))
When one want to enforce a multi-row constraint that is not offered by the database engine, the obvious solution is use of a trigger or stored procedure. This often does not work because the database isolates the transactions the triggers and stored procedures run in, allowing violations in the presense of concurrency.
Instead turn the constraint into something that the database engine will enforce.
CREATE TABLE dbo.T (A INT, B INT)
GO
CREATE TABLE dbo.T_Constraint_Helper (ColumnName sysname PRIMARY KEY)
INSERT INTO dbo.T_Constraint_Helper (ColumnName)
VALUES ('A'), ('B')
GO
CREATE VIEW T_Constraint_VW
WITH SCHEMABINDING AS
SELECT CASE CH.ColumnName WHEN 'A' THEN T.A ELSE T.B END AS Value
FROM dbo.T
CROSS JOIN dbo.T_Constraint_Helper CH
GO
CREATE UNIQUE CLUSTERED INDEX FunnyConstraint_VW_UK ON dbo.T_Constraint_VW (Value)
GO
INSERT INTO T VALUES (1, 2)
-- works
INSERT INTO T VALUES (2, 3)
-- Msg 2601, Level 14, State 1, Line 1
-- Cannot insert duplicate key row in object 'dbo.T_Constraint_VW' with unique index 'T_Constraint_VW_UK'. The duplicate key value is (2).
INSERT INTO T VALUES (4, 4)
-- Msg 2601, Level 14, State 1, Line 1
-- Cannot insert duplicate key row in object 'dbo.T_Constraint_VW' with unique index 'T_Constraint_VW_UK'. The duplicate key value is (4).
INSERT INTO T VALUES (5, 6)
-- works

How to add data to two tables linked via a foreign key?

If I were to have 2 tables, call them TableA and TableB. TableB contains a foreign key which refers to TableA. I now need to add data to both TableA and TableB for a given scenario. To do this I first have to insert data in TableA then find and retrieve TableA's last inserted primary key and use it as the foreign key value in TableB. I then insert values in TableB. This seems lika a bit to much of work just to insert 1 set of data. How else can I achieve this? If possible please provide me with SQL statements for SQL Server 2005.
That sounds about right. Note that you can use SCOPE_IDENTITY() on a per-row basis, or you can do set-based operations if you use the INSERT/OUTPUT syntax, and then join the the set of output from the first insert - for example, here we only have 1 INSERT (each) into the "real" tables:
/*DROP TABLE STAGE_A
DROP TABLE STAGE_B
DROP TABLE B
DROP TABLE A*/
SET NOCOUNT ON
CREATE TABLE STAGE_A (
CustomerKey varchar(10),
Name varchar(100))
CREATE TABLE STAGE_B (
CustomerKey varchar(10),
OrderNumber varchar(100))
CREATE TABLE A (
Id int NOT NULL IDENTITY(51,1) PRIMARY KEY,
CustomerKey varchar(10),
Name varchar(100))
CREATE TABLE B (
Id int NOT NULL IDENTITY(1123,1) PRIMARY KEY,
CustomerId int,
OrderNumber varchar(100))
ALTER TABLE B ADD FOREIGN KEY (CustomerId) REFERENCES A(Id);
INSERT STAGE_A VALUES ('foo', 'Foo Corp')
INSERT STAGE_A VALUES ('bar', 'Bar Industries')
INSERT STAGE_B VALUES ('foo', '12345')
INSERT STAGE_B VALUES ('foo', '23456')
INSERT STAGE_B VALUES ('bar', '34567')
DECLARE #CustMap TABLE (CustomerKey varchar(10), Id int NOT NULL)
INSERT A (CustomerKey, Name)
OUTPUT INSERTED.CustomerKey,INSERTED.Id INTO #CustMap
SELECT CustomerKey, Name
FROM STAGE_A
INSERT B (CustomerId, OrderNumber)
SELECT map.Id, b.OrderNumber
FROM STAGE_B b
INNER JOIN #CustMap map ON map.CustomerKey = b.CustomerKey
SELECT * FROM A
SELECT * FROM B
If you work directly with SQL you have the right solution.
In case you're performing the insert from code, you may have higher level structures that help you achieve this (LINQ, Django Models, etc).
If you are going to do this in direct SQL, I suggest creating a stored procedure that takes all of the data as parameters, then performs the insert/select identity/insert steps inside a transaction. Even though the process is still the same as your manual inserts, using the stored procedure will allow you to more easily use it from your code. As #Rax mentions, you may also be able to use an ORM to get similar functionality.