How to properly create an insert trigger with SQL Server - sql

I am trying to create a trigger to send INSERT information from base table (Hub) to a log table.
The Log table contains the following columns:
ChangeID, Date, User, Table, Action, Description
The Hub table has 3 columns:
Date, Mat, Hub
I'm using this T-SQL code for my trigger:
CREATE TRIGGER tg_test
ON Hub
FOR INSERT
AS
BEGIN
DECLARE #sys_usr CHAR(30);
SET #sys_usr = SYSTEM_USER;
SET NOCOUNT ON;
INSERT INTO Logs_Table (Date, User, Table, Action, Description)
SELECT
(CURRENT_TIMESTAMP,
SYSTEM_USER,
'Hub',
'INSERT',
CONCAT('Mat: ', i.Mat, '; Hub: ', i.Hub))
FROM
INSERTED AS i;
The Logs_Table was created with following SQL:
CREATE TABLE Logs_Table
(
ChangeID INT IDENTITY PRIMARY KEY,
Date DATETIME,
User VARCHAR(200),
Table VARCHAR(200),
Action VARCHAR(100),
Description VARCHAR(MAX)
)
When I try to run the command to execute the query to create the trigger I get the following error:
ProgrammingError: (102, Incorrect syntax near ','.
DB-Lib error message 20018, severity 15:
General SQL Server error: Check messages from the SQL Server
Does anybody know where that syntax error is?

You shouldn't have ( and ) around the list of columns in your SELECT.
INSERT dbo.Logs_Table([Date], [User], [Table], Action, Description)
SELECT -- remove this (
CURRENT_TIMESTAMP,
SYSTEM_USER,
'Hub',
'INSERT',
CONCAT('Mat: ', i.Mat, '; Hub: ', i.Hub) -- remove this )
FROM INSERTED AS i;
Next, your trigger has a BEGIN but seems to be missing an END.
You should also try to avoid using generic and reserved words like Date, User, and Table for column names, and always use schema prefix. It also might make sense to apply defaults to the columns in the log table that take built-in functions, so you don't have to reference them in the trigger at all.
CREATE TABLE dbo.Logs_Table
(
ChangeID INT IDENTITY PRIMARY KEY,
[Date] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
[User] VARCHAR(200) NOT NULL DEFAULT SYSTEM_USER,
[Table] VARCHAR(200),
Action VARCHAR(100),
Description VARCHAR(MAX)
);
This really simplifies the insert in your trigger. A couple of other comments, [User] and [Table] should really be nvarchar, and I was lazy but default constraints should be named.

Related

SQL Creating a procedure, but it doesn't see my tables

I created a new database called sample1. In it I created 3 tables: "address, contact and buyer"
CREATE TABLE address
(
address_id int NOT NULL identity(1,1) primary key,
street_name varchar(30)
);
CREATE TABLE contact
(
contact_id int NOT NULL identity(1,1) primary key,
phone_num varchar(20) NOT NULL,
address_id int NOT NULL foreign key REFERENCES address(address_id)
);
CREATE TABLE buyer
(
username char(20) NOT NULL primary key,
name char(40) NOT NULL,
contact_id int NOT NULL foreign key REFERENCES contact(contact_id)
);
The tables were created successfully, now I'm trying to create a stored procedure as follows:
create procedure csc
(
#username char(20),
#address_id int,
#contact_id int
)
as
set nocount on;
insert into [dbo].[buyer] values (#username);
insert into [dbo].[address_id] values (#address_id);
insert into [dbo].[contact_id] values (#contact_id);
go
But I get the following errors:
Msg 137, Level 15, State 2, Line 4
Must declare the scalar variable "#username".
Msg 137, Level 15, State 2, Line 10
Must declare the scalar variable "#username".
Msg 137, Level 15, State 2, Line 11
Must declare the scalar variable "#address_id".
Msg 137, Level 15, State 2, Line 12
Must declare the scalar variable "#contact_id".
My questions / errors:
Are my tables logical? Should I be using buyer username as the PK for address / contact ID (my teacher suggested this, but I think it's bad practice)
When creating my procedure, I get red lines on [dbo].[buyer], [dbo].
[address_id], [dbo].[contact_id], can I ignore these?
How in the world do I fix the errors?
Thanks in advance.
1, are my tables logical? should I be using buyer username as the PK
for address / contact ID (my teacher suggested this, but I think it's
bad practice)
It depends whether a buyer is always defined by their username and that won't get updated. This is an age old debate between natural and surrogate keys, you can have a google and find some pretty convincing arguments for either side. If you think it's bad practice then you'll need to be able to explain why you think this. I would certainly avoid using the char datatype unless you really wanted to use it, it will blank pad your data up to it's specified length.
2, when creating my procedure, I get red lines on [dbo].[buyer],
[dbo]. [address_id], [dbo].[contact_id], can I ignore these?
That's because when SSMS last looked, those tables do not exist. If you press CTRL+SHIFT+R, you will refresh intelligence and it will check again. It'll still fail though because address_id nor contact_id are tables.
3, how in the world do I fix the errors?
You fix the table names. But once you've done that you'll reach another error as you aren't specifying what column your values should be inserted into and there are multiple it can use. The problem is that your code wants to insert your primary key columns but with no additional data - so really you need to complete your code so that all the required data is input and the columns they are inserting into are explicitly mentioned.
e.g.
insert into [dbo].[buyer] (username, name, contact_id) values (#username, #name, #contact_id);
But.. you are inserting the contact row at the same time and you've declared it as an identity column, should you really know the value of this column already or should you be inserting the other values into the table and generating a new contact_id with it?
Perhaps your procedure should really start
create procedure csc(
#username char(20),
#name char(40),
#street_name varchar(30),
#phone_num varchar(20)
)
And then build up your insert statements from that data. Something like:
as
set nocount on;
declare #address_id int;
declare #contact_id int;
insert into [dbo].[address] (street_name) values (#street_name);
set #address_id = SCOPE_IDENTITY() ;
insert into [dbo].contact (phone_num, address_id) values (#phone_num, #address_id);
set #contact_id = SCOPE_IDENTITY() ;
insert into [dbo].[buyer] (username, name, contact_id) values (#username, #name, #contact_id);
go
And to demo:
exec csc 'andy','andy','Nice Road',42
select * from buyer;
select * from contact;
select * from address;
username name contact_id
-------------------- ---------------------------------------- -----------
andy andy 3
(1 row affected)
contact_id phone_num address_id
----------- -------------------- -----------
3 42 3
(1 row affected)
address_id street_name
----------- ------------------------------
3 Nice Road
(1 row affected)
It looks like you've got a couple of typos. Try it like this
create procedure csc
#username char(20),
#address_id int,
#contact_id int
as
set nocount on;
insert into [dbo].[buyer] values (#username);
insert into [dbo].[address] values (#address_id);
insert into [dbo].[contact] values (#contact_id);
go
Use a begin/end block:
create procedure csc (
#username char(20),
#address_id int,
#contact_id int
) as
begin
set nocount on;
insert into [dbo].[buyer] values (#username);
insert into [dbo].[address_id] values (#address_id);
insert into [dbo].[contact_id] values (#contact_id);
end;
go

Trigger on View not firing on SQL Server 2012

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');

SQL Server 2008 R2: Trigger on `TEXT` column

I have the table which consist of column with datatype text.
Table EmployeeMaster
CREATE TABLE [EmployeeMaster]
(
EmpID int identity(1,1),
EmpName varchar(20),
EmpPhone int,
EmpAddress TEXT
);
And I want to create audit log on this table.
Audit Table: EmployeeMaster_Audit
CREATE TABLE [EmployeeMaster_Audit]
(
EmpID int,
EmpName varchar(20),
EmpPhone int,
EmpAddress VARCHAR(MAX)
);
Writing trigger for INSERT.
Trigger:
CREATE TRIGGER [dbo].[EmployeeMaster_Insert]
ON [dbo].[EmployeeMaster]
FOR INSERT
AS
INSERT INTO [dbo].[EmployeeMaster_Audit]
([EmpID], [EmpName], [EmpPhone], [EmpAddress])
SELECT CONVERT(int,[EmpID]) as [EmpID],[EmpName],[EmpPhone],CONVERT(varchar(max),[EmpAddress]) AS [EmpAddress] FROM INSERTED
GO
Error Details: While creating trigger getting following error:
Cannot use text, ntext, or image columns in the 'inserted' and 'deleted' tables.
My Try: CONVERT(varchar(max),[EmpAddress])
Since the trigger is fired after the insert, you can simply query back to the EmployeeMaster to get the inserted data. Something like this:
CREATE TRIGGER [dbo].[EmployeeMaster_Insert]
ON [dbo].[EmployeeMaster]
FOR INSERT
AS
INSERT INTO [dbo].[EmployeeMaster_Audit] ([EmpID], [EmpName], [EmpPhone], [EmpAddress])
SELECT EM.[EmpID]
, EM.[EmpName]
, EM.[EmpPhone]
, CONVERT(varchar(max), EM.[EmpAddress]) AS [EmpAddress]
FROM INSERTED I
INNER JOIN dbo.[EmployeeMaster] EM
ON EM.[EmpID] = I.[EmpID]
GO
This is assuming you cannot change the text datatype, see Zohar's answer.
The correct solution to the problem would be to replace the text column with a varchar(max) column.
The Image, Text, and nText data types are deprecated since 2008 version introduced varbinary(max), varchar(max) and nvarchar(max).
For more information, read Microsoft official documentation page on ntext, text, and image (Transact-SQL):
IMPORTANT! ntext, text, and image data types will be removed in a future version of SQL Server. Avoid using these data types in new development work, and plan to modify applications that currently use them. Use nvarchar(max), varchar(max), and varbinary(max) instead.

Finding what value was inserted into an auto-incrementing field

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.

SQL INSERT stored procedure not working

Create Proc CrearNuevoAnuncio
#Titulo varchar(250),
#Precio int,
#Descripcion varchar(250),
#IDCategoria int,
#IDImagen int,
#Login varchar(200)
AS
INSERT INTO Anuncio VALUES(
#Titulo,
#Precio,
#Descripcion,
#IDCategoria,
#IDImagen,
#Login
)
The error is because the table anuncio has 1 more attribute: "idAnuncio". It's the primary key, and it's the indentity (autoincrement).
So, how can I handle this is the missing thing is an Identity. I don't want to pass that parameter from my front-end.
You need to specify the explicit list of columns in your insert statement:
INSERT INTO
Anuncio(Titulo, Precio, Descripcion, IDCategoria, IDImagen, Login)
VALUES
(#Titulo, #Precio, #Descripcion, #IDCategoria, #IDImagen, #Login)
Otherwise, SQL Server will try to insert values for ALL columns which fails here. It cannot insert a value into an IDENTITY column - that column will be set automatically by SQL Server upon inserting a new row, and is guaranteed to be unique.
Is the ID field of type "INT IDENTITY" ? In that case, you could access the value of the newly inserted ID like this:
DECLARE #NewID INT
SET #NewID = SCOPE_IDENTITY()
and possibly return it from the stored proc:
RETURN #NewID
Marc
You need to specify which value goes to which field.
INSERT INTO Anuncio (field1, field2, ...) VALUES (#Titulo, ...)