SQL Trigger Conditional Insert - number of supplied values error - sql

I've cobbled together a trigger based on some answers I found here that I think is close, but is returning a supplied value error that I'm not sure how to fix. In short I have a ParticipationStatus table that I want to add a record to that will include the UserID of the inserted and a status of 'Pending' whenever a user RoleID of '19' is inserted in the UserRole table.
The Participation_Status table has a few other fields, but the only one that is required is an auto_id column. Here is what I have so far that is throwing a "Column name or number of supplied values does not match table definition." Any suggestions would be greatly appreciated.
CREATE TRIGGER [dbo].[trg_insert_status_participation] ON [dbo].[UserRoles]
FOR INSERT AS
BEGIN
If (SELECT RoleID FROM INSERTED)=19
DECLARE #UserID int
DECLARE #Status nvarchar(50)
INSERT INTO ParticipationStatus
VALUES (#UserID, #Status)
SET #UserID = (select UserID from inserted)
SET #Status = 'Pending'
END

INSERTED can have more than one row. You need to code your trigger accordingly. It's actually much simpler this way.
CREATE TRIGGER [dbo].[trg_insert_status_participation] ON [dbo].[UserRoles]
FOR INSERT AS
BEGIN
INSERT INTO ParticipationStatus(UserID, Status)
SELECT UserID, 'Pending' FROM INSERTED
WHERE RoleID = 19
END

Related

AUDIT TRAIL OF TABLES

I want to do audit trail on specific table like
what inserted,updated,deleted in table and all this logs are save in one table
I am using sql server 2012 .
Can any one please help me with how to achieve this?
Please note - Use of cursor is restricted
create an after trigger on that table and insert the records into the log table .
create trigger <trigger_name> after insert/update/delete/ on
table <orig table>
begin
insert into the log tables ('all the fields that you require');
end
Try using CDC (Change Data Capture). A very helpful tool which will help to manage the Audit Trail
Read the article from MSDN
This can be achieve using Triggers. Trigger will make your DML operations slower, if large Insert, Delete and Update operations are happening on your table. If it's small table you can create TRIGGER like below, to log the rows to another table based on the action occurred.
You can make use of Inserted and Deleted magic tables which hold the rows which are being Inserted and Deleted inside a trigger.
There is another alternate if you need more control over auditing using CDC (Change Data Capture).
CREATE TABLE TrailTable
(
Id INT,
Name VARCHAR(100)
);
CREATE TABLE TrailTableLog
(
Id INT,
Name VARCHAR(100),
Action CHAR(3)
);
Insert Into TrailTable VALUES (1,'Vi');
Insert Into TrailTable VALUES (2,'Vr');
Insert Into TrailTable VALUES (3,'An');
Insert Into TrailTable VALUES (4,'Ma');
CREATE TRIGGER dbo.TRG_IDU_TrailTable
ON dbo.TrailTable
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Action as char(1);
SET #Action = (CASE WHEN EXISTS(SELECT * FROM INSERTED)
AND EXISTS(SELECT * FROM DELETED)
THEN 'U' -- Set Action to Updated.
WHEN EXISTS(SELECT * FROM INSERTED)
THEN 'I' -- Set Action to Insert.
WHEN EXISTS(SELECT * FROM DELETED)
THEN 'D' -- Set Action to Deleted.
ELSE NULL -- Skip. It may have been a "failed delete".
END)
IF(#Action = 'I')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'I' FROM INSERTED;
END
IF(#Action = 'D')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'D' FROM DELETED;
END
IF(#Action = 'U')
BEGIN
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'U-D' FROM INSERTED; -- Records Deleted to Update
INSERT INTO TRG_IDU_TrailTable (Id, Name, Action)
SELECT Id, Name, 'U-I' FROM INSERTED; --Records Inserted to Update
END
END

Update multiple column and row using temp table

I'm trying to update multiple column value on condition, but it's updating only single column. I'm not able to figure out why it's not updating all columns.
This is my SP.
CREATE PROCEDURE [dbo].[MIC_UpdateIdeaInline]
(
#UpdatedBy int,
#UpdatedOn Datetime,
#UpdateRecords [UpdateIdeaInline] READONLY
)
as
BEGIN
UPDATE MI
SET MI.IsPortalShare= CASE WHEN ColoumnName = 'IsPortalShare' THEN CAST(ur.ColValue as BIT) ELSE IsPortalShare END
,MI.Validity= CASE WHEN ColoumnName = 'Validity' THEN CAST(ur.ColValue as BIT) ELSE Validity END
,MI.DeadLine= CASE WHEN ColoumnName = 'DeadLine' THEN CAST(ur.ColValue as datetime) ELSE DeadLine END
,MI.UpdatedBy=#UpdatedBy, MI.UpdatedOn=#UpdatedOn
from [MIC_Idea] MI
Join #UpdateRecords ur ON MI.Id=ur.IdeaId
SELECT 1
END
These are my parameter while calling SP.
declare #p3 dbo.UpdateIdeaInline
insert into #p3 values(N'15',N'Validity',N'1')
insert into #p3 values(N'15',N'DeadLine',N'15-Jun-2017')
insert into #p3 values(N'14',N'Validity',N'0')
insert into #p3 values(N'14',N'DeadLine',N'15-Jun-2017')
exec MIC_UpdateIdeaInline #UpdatedBy=1,#UpdatedOn='2017-06-06 21:45:19.863',#UpdateRecords=#p3
go
UpdateIdeaInline is table type. It's first column contain
Id
ColumnName
ColumnValue
As You can see I want to update Column (validity and DeadLine) for same ids 14 and 15. But it's only updating validity column.
That's because your update is in single atomic transaction. Update statement updates the table twice, because ID is duplicated in UpdateRecords table. So on the second update it cannot read the value updated by the first update.
Example:
You have values 10 and 15.
Now you issue a single UPDATE joining with UpdateRecord table which has two identical IDs, but each ID for different column.
E.g. UpdateRecord has
'Col1', 30
'Col2', 40
After first update:
10-->30 (pending)
15-->15 (pending)
After second update:
10-->10 (pending)
15-->40 (pending)
After transaction is committed:
10-->10
15-->40

How to get a inserted id in other table from inside a trigger?

I have 3 tables tbl_Users, tbl_Protocol and tbl_ProtocolDetails and inside of my trigger on Users, I have to inserted into Protocol and then insert into ProtocolDetails, but I don't know how work the inserted scope.
Something like that:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #UserId = Int
DECLARE #ProtocolId = Int
DECLARE #UserDetail = NVARCHAR(255)
SELECT
#UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
FROM INSERTED
INSERT INTO tbl_Protocol (user_id, inserted_date)
VALUES (#UserId, GetDate())
-- Return Inserted Id from tbl_Protocol into #ProtocolDetail then
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
VALUES (#ProtocolId, #UserDetail)
END
Your trigger has a MAJOR flaw in that you seems to expect to always have just a single row in the Inserted table - that is not the case, since the trigger will be called once per statement (not once for each row), so if you insert 20 rows at once, the trigger is called only once, and the Inserted pseudo table contains 20 rows.
Therefore, code like this:
Select #UserId = user_id,
#UserDetail = user_detail + '#' + user_explanation
From INSERTED;
will fail, since you'll retrieve only one (arbitrary) row from the Inserted table, and you'll ignore all other rows that might be in Inserted.
You need to take that into account when programming your trigger! You have to do this in a proper, set-based fashion - not row-by-agonizing-row stlye!
Try this code:
CREATE TRIGGER tg_Users ON tbl_Users
AFTER INSERT, UPDATE AS
BEGIN
-- declare an internal table variable to hold the inserted "ProtocolId" values
DECLARE #IdTable TABLE (UserId INT, ProtocolId INT);
-- insert into the "tbl_Protocol" table from the "Inserted" pseudo table
-- keep track of the inserted new ID values in the #IdTable
INSERT INTO tbl_Protocol (user_id, inserted_date)
OUTPUT Inserted.user_id, Inserted.ProtocolId INTO #IdTable(UserId, ProtocolId)
SELECT user_id, SYSDATETIME()
FROM Inserted;
-- insert into the "tbl_ProtocolDetails" table from both the #IdTable,
-- as well as the "Inserted" pseudo table, to get all the necessary values
INSERT INTO tbl_ProtocolDetails (protocol_id, protocol_details)
SELECT
t.ProtocolId,
i.user_detail + '#' + i.user_explanation
FROM
#IdTable t
INNER JOIN
Inserted i ON i.user_id = t.UserId
END
There is nothing in this trigger that would handle a multiple insert/update statement. You will need to either use one scenario that will handle multiple records or check how many records were effected with a IF ##ROWCOUNT = 1 else statement. In your example, I would just use something like
insert into tbl_Protocol(user_id, inserted_date)
select user_id, user_detail + '#' + user_explanation
From INSERTED;
As for your detail table, I see Marc corrected his answer to include the multiple lines and has a simple solution or you can create a second trigger on the tbl_Protocol. Another solution I have used in the past is a temp table for processing when I have very complicated triggers.

IDENTITY_INSERT is already ON for table 'X'. Cannot perform SET operation for table 'Y'

I created a trigger that performs a check and automatically populates data into 2 tables. Only what happens the following error :
IDENTITY_INSERT is already ON for table 'X'. Cannot perform SET operation for table 'Y'.
I found this while researching the error:
"At any time, only one table in a session can have the IDENTITY_INSERT property set to ON.”
So the fix was easy:
SET IDENTITY_INSERT Table1 ON
-- insert statements for table1
SET IDENTITY_INSERT Table1 OFF
SET IDENTITY_INSERT Table2 ON
-- insert statements for table2
SET IDENTITY_INSERT Table2 OFF
SET IDENTITY_INSERT Table3 ON
-- insert statements for table3
SET IDENTITY_INSERT Table3 OFF
But as the data is populated via trigger is not possible to do so.
Does anyone have a solution to my problem please?
I apologize.
Thank you all.
Trigger-----
CREATE TRIGGER Alert ON registos AFTER INSERT AS
BEGIN
DECLARE #comp decimal = 0
DECLARE #id_sensores_em_alerta decimal
DECLARE #tempmin decimal = 0
DECLARE #current_max_idAlarme int = (SELECT MAX(IdAlarme) FROM alarmes)
DECLARE #maxidAlarme int
DECLARE #temp decimal = (SELECT s.lim_inf_temp from sensores s JOIN inserted i ON s.idSensor=i.idSensor )
-- Insert into alarmes from the inserted rows if temperature less than tempmin
INSERT alarmes (IdAlarme, descricao_alarme,data_criacao, idRegisto)
SELECT
ROW_NUMBER() OVER (ORDER BY i.idRegisto) + #current_max_idAlarme, 'temp Error', GETDATE(), i.idRegisto
FROM
inserted AS i
WHERE
i.Temperatura < #temp
SET #maxidAlarme = (SELECT MAX(IdAlarme) FROM alarmes)
INSERT INTO sensores_tem_alarmes(idSensor,idAlarme,dataAlarme)
SELECT i.idSensor, #maxidAlarme, GETDATE()
FROM inserted i
SET #comp += 1;
SET #id_sensores_em_alerta=1;
SET #id_sensores_em_alerta = (SELECT MAX(id_sensores_em_alerta) FROM sensores_em_alerta)
INSERT INTO sensores_em_alerta(id_sensores_em_alerta, idSensor, idAlarme, data_registo, numerosensoresdisparados)
SELECT #id_sensores_em_alerta, i.idSensor, #maxidAlarme, GETDATE(), #comp
FROM inserted i
end
DataBase----
I had a similar problem but it did not involve table triggers. I was running a script that refreshes data for multiple tables and I hit a foreign key reference error.
According to MSDN:
At any time, only one table in a session can have the IDENTITY_INSERT
property set to ON.
To resolve this, I ran SET IDENTITY_INSERT [dbo].[table_name] OFF for each table I was trying to insert into. Then I was able to refresh my tables again after I corrected the reference error.
Edit: I should also mention that you can just disconnect and then reconnect to reset your session.
Allow SQL Server to insert the identity values automatically for you. Since this is a trigger, there could multiple rows being inserted at a time. For one row inserts, you can use SCOPE_IDENTITY() function (http://msdn.microsoft.com/en-us/library/ms190315.aspx) to retrieve the identity value of your last inserted row. However, since we could have multiple rows inserted in a trigger, we will use the OUTPUT clause (http://msdn.microsoft.com/en-us/library/ms177564.aspx) to get back a list of the inserted IdAlarme values for each idRegisto.
I'm assuming that alarmes.IdAlarme and sensores_em_alerta.id_sensores_em_alerta are the two identity fields in this trigger. If that is the case, then this should work:
CREATE TRIGGER Alert ON registos AFTER INSERT AS
BEGIN
DECLARE #comp decimal = 0
DECLARE #id_sensores_em_alerta decimal
DECLARE #tempmin decimal = 0
DECLARE #temp decimal = (SELECT s.lim_inf_temp from sensores s JOIN inserted i ON s.idSensor=i.idSensor )
DECLARE #tblIdAlarme TABLE (idRegisto int not null, IdAlarme int not null);
-- Insert into alarmes from the inserted rows if temperature less than tempmin
-- IdAlarme is identity field, so allow SQL Server to insert values automatically.
-- The new IdAlarme values are retrieved using the OUTPUT clause http://msdn.microsoft.com/en-us/library/ms177564.aspx
INSERT alarmes (descricao_alarme,data_criacao, idRegisto)
OUTPUT inserted.idRegisto, inserted.IdAlarme INTO #tblIdAlarme(idRegisto, IdAlarme)
SELECT descricao_alarme = 'temp Error', data_criacao = GETDATE(), i.idRegisto
FROM inserted AS i
WHERE i.Temperatura < #temp
;
--It looks like this table needs a PK on both idSensor and idAlarme fields, or else you will get an error here
-- if an alarm already exists for this idSensor.
INSERT INTO sensores_tem_alarmes(idSensor,idAlarme,dataAlarme)
SELECT i.idSensor, a.IdAlarme, dataAlarme = GETDATE()
FROM inserted i
INNER JOIN #tblIdAlarme a ON i.idRegisto = a.idRegisto
;
--not sure what this is doing?? Will always be 1.
SET #comp += 1;
--id_sensores_em_alerta is an identity field, so allow SQL Server to insert values automatically
INSERT INTO sensores_em_alerta(idSensor, idAlarme, data_registo, numerosensoresdisparados)
SELECT i.idSensor, a.IdAlarme, data_registo = GETDATE(), numerosensoresdisparados = #comp
FROM inserted i
INNER JOIN #tblIdAlarme a ON i.idRegisto = a.idRegisto
;
END

Insert data into table when i am using trigger?

Here is a trigger
CREATE TRIGGER [dbo].[CheckApplyId]
ON [dbo].[AppliedStudent_event] INSTEAD OF INSERT
AS
DECLARE #studentId INT
DECLARE #compReq_Id INT
BEGIN
SELECT #studentId = studentId
FROM INSERTED
SELECT #compReq_Id = compReq_Id
FROM INSERTED
IF EXISTS(SELECT StudentId,
compreq_id
FROM AppliedStudent_event
WHERE StudentId = #studentId
AND compreq_id = #compReq_Id)
BEGIN
ROLLBACK
PRINT 'User Already Applied'
END
END
When in insert a data into a table using command:
INSERT INTO AppliedStudent_event (StudentId, compreq_id)
VALUES (3026, 1)
Message is:
(1 row(s) affected)
But when I execute a sql command no data is inserted in the table.
Can you please tell why are you using trigger because you use only assign the variable #studentId and #compReq_Id from inserted table.
That's a broken trigger because inserted can contain multiple (or no) rows - so a statement like SELECT #ScalarVariable = column from inserted is always wrong.
And it's unnecessary since you can just place a UNIQUE constraint on the StudentId and compreq_id columns:
ALTER TABLE AppliedStudent_event
ADD CONSTRAINT UQ_Student_Events
UNIQUE (StudentId,compreq_id)
And it's further broken because you've specified it as an instead of trigger - that says that your code is going to be responsible for the actual insert - but your code doesn't actually do that. That's why no data ends up in the table.
If you insist on doing it as a trigger, it's actually tricky to get everything correct (that's why I'd really recommend the UNIQUE constraint). It'll end up being something like this:
CREATE TRIGGER [dbo].[CheckApplyId]
ON [dbo].[AppliedStudent_event] INSTEAD OF INSERT
AS
IF EXISTS(select
StudentId,compreq_id,COUNT(*)
from inserted
group by StudentId,compreq_id
HAVING COUNT(*) > 1)
OR EXISTS (select *
from inserted i
inner join
AppliedStudent_event e
on
i.StudentId = e.StudentId and
i.compreq_id = e.compreq_id)
BEGIN
ROLLBACK
PRINT 'User Already Applied'
END
ELSE
BEGIN
INSERT INTO AppliedStudent_event(StudentId,compreq_id /* Other columns? */)
SELECT StudentId,compreq_id /* And again, other columns */
FROM inserted
END