How to Create a Trigger On [table] Delete in MS SQL Server - sql-server

I am taking a database class and I am completely lost on this one. I hope you can help me out. Here is the SQL trigger I have created based on our text book. It says the examples are all SQL server 2012 based, but when I test the trigger it cannot recognize some of the syntaxes. Is there a way to do this differently? The SHIPMENT_ITEM is child of SHIPMENT, and its a M-M relationship. Thanks :)
CREATE TRIGGER SHIPMENT_ITEM_SHIPMENT_DeleteCheck
ON [dbo].[DeleteShipmentItemView]
FOR DELETE
DECLARE
rowCount Int;
BEGIN
/* to determine if shipment item is last one in shipment */
SELECT Count (*) into rowCount
FROM [dbo].[SHIPMENT_ITEM]
WHERE [dbo].[SHIPMENT_ITEM].[ShipmentID] = old:[ShipmentID];
/*delte shipment item row regardless of wheter shipment is deleted */
DELETE [dbo].[SHIPMENT_ITEM]
WHERE [dbo].[SHIPMENT_ITEM].[ShipmentID] = old:[ShipmentID];
IF (rowCount = 1)
THEN
/*last shipment item in shipment, delete shipment */
DELETE [dbo].[SHIPMENT]
WHERE [dbo].[SHIPMENT].[ShipmentID] = old:[ShipmentID];
END IF;
END;
Level 15, State 1,
Procedure SHIPMENT_ITEM_SHIPMENT_DeleteCheck, Line 5 Incorrect syntax near the keyword 'SET'.
Procedure SHIPMENT_ITEM_SHIPMENT_DeleteCheck, Line 10 Incorrect syntax near the keyword 'rowCount'.
Procedure SHIPMENT_ITEM_SHIPMENT_DeleteCheck, Line 16 Incorrect syntax near 'old:'.
Procedure SHIPMENT_ITEM_SHIPMENT_DeleteCheck, Line 16 The label 'old' has already been declared. Label names must be unique within a query batch or stored procedure.
Procedure SHIPMENT_ITEM_SHIPMENT_DeleteCheck, Line 22 Incorrect syntax near 'old:'.
Procedure SHIPMENT_ITEM_SHIPMENT_DeleteCheck, Line 22 The label 'old' has already been declared. Label names must be unique within a query batch or stored procedure.

There are several things that doesn't match t-sql syntax in your trigger.
DECLARE
rowCount Int;
Variables in t-sql must start with a # sign.
The correct syntax would be DECLARE #rowCount Int;
SELECT Count (*) into rowCount
in t-sql, Select into will create a new table. you probably want SELECT #rowCount = Count (*) instead.
[dbo].[SHIPMENT_ITEM].[ShipmentID] = old:[ShipmentID];
Actually, I'm not sure what you want to accomplish in this line, but it's not t-sql. I think you want to get the number of rows that was deleted. If that is the case, it's SELECT COUNT(*) FROM deleted.
The way I understand your trigger, your goal is to delete a record in SHIPMENT table if there are no corresponding records in the SHIPMENT_ITEM table for it.
I would probably do something like this:
CREATE TRIGGER SHIPMENT_ITEM_SHIPMENT_DeleteCheck
ON dbo.SHIPMENT_ITEM
FOR DELETE
AS
DELETE
FROM dbo.SHIPMENT s
WHERE EXISTS (
SELECT 1
FROM deleted d
WHERE d.ShipmentID = s.ShipmentID
)
AND NOT EXISTS(
SELECT 1
FROM dbo.SHIPMENT_ITEM si
WHERE si.ShipmentID = s.ShipmentID
)
GO
Notes:
This trigger contains a where clause with both exists and not exists sub queries. This ensures that you delete only the shipment's records that corresponded to the shipment_item that you have just deleted, and only if there are no other shipment_item records with the same shipmnet id.
The trigger in the question is for a view, this is for the table itself. note that not all views in sql server are updatable, so you might need to create the trigger on the view and not directly on the shipment_item table. Also note that you can't use after triggers on a view in sql server
For more information, check the Create trigger page on MSDN.

Related

There is already an object named 'name' in the database - stored procedure error - SQL Server

I have two tables called timeus and usdpay in my SQL Server database. These two tables get updated every week.
I did small transformation and combined these two tables and created a new table called fluslabor.
I created fluslabor using the stored procedure shown here, and it is working:
CREATE PROCEDURE [dbo].[sp_uslabor]
AS
BEGIN
SET NOCOUNT ON
IF OBJECT_ID(N'dbo.fluslabor', N'U') IS NOT NULL
TRUNCATE TABLE [dbo].[fluslabor];
SELECT ut.employee_code
,ut.employee_name
,ut.department_desc
,ut.location_desc
,ut.hour
,ut.projects_code
,ut.in_effective_time
,ut.out_effective_time
,ut.date
,ut.id
,p.rate
,(p.rate * ut.hour ) as Labour_Cost
INTO fluslabor
FROM timeus ut
LEFT JOIN usdpay p ON (TRIM(ut.id) = TRIM(p.id) AND ut.date = p.date)
WHERE ut.projects_code NOT LIKE '0%'
END
Today I got new data updated in my two tables timeus and usdpay.
When I execute my stored procedure, SQL Server is throwing this error:
Msg 2714, Level 16, State 6, Procedure SP_uslabor, Line 12 [Batch Start Line 38]
There is already an object named 'fluslabor' in the database.
I need to truncate my table every time and load the new data. I checked the similar post, they said to use drop table option. I don't want to drop the table, just want to truncate and execute the procedure
Can anyone advise what is the issue here please?
The problem here is that the table fluslabor already exists in the database. what you are trying above the insert is checking the object existence and then truncating the same
There are two possible approaches that you can try here.
Instead if the TRUNCATE do a DROP TABLE. But This will also remove the existing user permissions on the table if you have provided specific custom access to the table to any of the users
IF OBJECT_ID(N'dbo.fluslabor', N'U') IS NOT NULL DROP TABLE [dbo].[fluslabor];
The safest approach will be change the SELECT .. INTO statement and convert it into INSERT INTO like this
INSERT INTO fluslabor ( <List your Destination columns> ) SELECT <List your Source columns> FROM <Source Query>
the 2nd approach will have the records loaded along with keeping all the existing permissions
IF EXISTS (SELECT 1 FROM sys.objects WHERE type = 'P' AND name = 'your SP name')
BEGIN
DROP PROCEDURE "your SP name";
END
GO
CREATE PROCEDURE "your SP name"
AS
BEGIN
.
.
.
.
try this one I guess this will help you.

Is it possible to write to the same temp table using SELECT INTO?

I am trying to write to a temp table , the source data can come from two different sources but they do have the same schema. the source of data is selected by a conditional statement IF ELSE only one can execute, but sql doesn't seem to like that . it complains the table already exists. here is the piece of code. "Msg 2714, Level 16, State 1, Procedure sp_xxx, Line 37 [Batch Start Line 0]
There is already an object named '#my_temp_table' in the database. "
IF #flag = 0
SELECT * INTO #my_temp_table
FROM source_A
ELSE
BEGIN
--even tho the temp table will not exist i am trying to by pass the error
IF OBJECT_ID('tempdb..#my_temp_table ') IS NOT NULL
DROP TABLE #my_temp_table
SELECT * INTO #my_temp_table
FROM source_B
END
There are a couple of things going on here that are or will be issues for you.
SQL Server's SELECT...INTO logic is how Microsoft chose to implement the more standard CREATE TABLE AS... syntax (which is then followed by a SELECT statement in most dialects). You can only create that table once, and then it's there.
Local temp tables (with a single #) continue to exist until your session disconnects from the instance. So if you try to run the above code twice without disconnecting, it'll fail.
You're using SELECT *, which is going to bite you when somebody changes the schema of one source table.
All that said, you don't need all that logic. You can do what you want with one query.
SELECT
<Column List>
INTO #my_temp_table
FROM
source_A
WHERE #flag = 0
UNION
SELECT
<Column List>
FROM
source_B
WHERE #flag <> 0;

Procedure for Altering Table and updating keeps failing with invalid column

I'm trying to add new columns to a table then update the table and set the new column with a date format change of the old column.
I have my procedure set out as follows:
begin
alter table [dbo].[mytable]
add New_Field1 varchar(24)
end
......
update [dbo].[SMR06_TARGET]
set New_Field1 = convert(varchar(24),Old_Field1,103)
.....
I have multiple alter table statements at the top of the table and update statements at the bottom for each new column. I think this is a rule with SQL keeping DDL at top and DML at bottom.
Ok so everytime I execute this to create the procedure it fails with incorrect column name New_Field1. I really can't peg down what is causing this. I've tried different variations of BEGIN....END tried commenting out the apprent offending statement, then it runs, then it fails again with the next statement.
I'm reckoning it's something to do with the way the statement(s) are terminated. I'm not sure as haven't done this type of procedure statement before with mixed DDL/DML.
Any hints would be most welcome.
Thanks
Andrew
You need to batch the statement that adds the column separately from the statement that updates it.
BEGIN TRANSACTION
GO
ALTER TABLE [dbo].[mytable]
ADD New_Field1 varchar(24) NULL
GO
UPDATE [dbo].[mytable]
SET New_Field1 = convert(varchar(24),Old_Field1,103)
GO
ALTER TABLE dbo.Batch SET (LOCK_ESCALATION = TABLE)
GO
COMMIT
The entire batch is reviewed by the parser before it starts executing the first line. Adding Old_Field1 is in the same batch as the reference to use Old_Field1. At the time the parser considers the statement containing Old_Field1, the statement to add Old_Field1 has not been executed, so that field does not yet exist.
If you're running in SSMS, include GO between each statement to force multiple batches. If you're running this in another tool that can't use GO, you'll need to submit each statement individually to ensure that they are fully executed before the next step is parsed.

SQL Server Triggers: How can I obtain a value used in the query string in my trigger's where clause?

I need to create a trigger in SQL Server which is conditionally fired when a user attempts to delete a row in the table.
I have 2 tables in the database Airlines:
Passenger - it has information on passengers. I need to create trigger on this table, obviously
Record - it records the flight(s) a passenger has been on
My trigger should work as follows:
If a passenger has never been on a flight, it should be deleted if attempted.
But if he/she has NOT been on a flight, it should restrict the action and print the number of times he has been on any flight.
The only thing (I hope) I am struggling with is:
How would I specify any WHERE clause inside a query in the trigger if I do not know which particular passenger I need to look for until a user attempts to delete it?
So, long story short: is there any way to obtain a value passed in a query's WHERE clause to be used in a trigger?
Thank you very much for your time!
Here is my code:
ALTER TRIGGER Restrict_Delete
ON Records
INSTEAD OF DELETE
AS
BEGIN
DECLARE #r_count INT
SET #r_count = (SELECT DISTINCT COUNT(*)
FROM Passenger P, Records R
WHERE P.passenger_id = R.passenger_id
AND P.passenger_id = ???)
IF #r_count > 0
BEGIN
ROLLBACK TRAN
PRINT ('Permission denied. ' + CAST (#r_count AS Varchar(3)) + ' record(s) exist.')
END
ELSE
PRINT 'No records exist. Record deleted!'
END
How do I determine the passenger_id in my query?
You can use the deleted and inserted tables!
These are special tables that exist in triggers and contain records copied from the actual tables. When you change a row in a table, a copy of the old row goes into the deleted table, and a copy of the new row goes into the inserted table. Since you're just deleting, you only need to use the deleted table.
Here's how the SQL could look inside your trigger:
DECLARE #r_count INT
SET #r_count = Count(*)
FROM Records R -- You don't actually need the Passenger table for this.
WHERE r.Passenger_id IN (
select d.Passenger_id
from Deleted d
)
IF #r_count > 0
BEGIN
Rollback Tran
PRINT ('Permission denied. ' + CAST (#r_count AS Varchar(3)) + ' record(s) exist.')
END
ELSE
PRINT 'No records exist. Record deleted!'
Something you need to be aware of, though: a trigger is called once per statement, not once per record. So if you delete two passengers with one DELETE statement, you'll get only one trigger call. The logic you had (and I adapted) will check for any record that was deleted by that DELETE statement. You could get quite a large number for #r_count if you're doing a bulk delete!
If you need to code around that, try to avoid using a cursor actually in your trigger: it will make deletes very slow.
Also, be aware that the PRINT statement will appear in SSMS and can be retrieved in ADO.NET with a bit of fiddling around, but doesn't appear in traces or get returned as part of a recordset. If you need to log this failure, you're going to need to write to a database table.

If exists UPDATE else INSERT for each row of a table

I have a table named tableFrom want to insert into a table named tableTo. The insert worked well, but if I insert the same values again, I have a duplicate key error. So I want to update only the rows already existing. I know the command ON DUPLICATE with MySQL, unfortunately missing in SQL Server.
If I want to only check for only one precise row, it is easy:
IF EXISTS PK = #PK
But I am trying to do it for a whole table, and I don't know if it is possible. I thought of cheking each row with cursor, I am new to SQL.
Here is what I came up with:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
IF EXISTS (
SELECT
1
FROM
tableFrom F,
tableTo T
WHERE
T.product = F._product
)
BEGIN
UPDATE
tableTo
SET
T.something = F.something
FROM
tableTo T
INNER JOIN
tableFrom F
ON
T.product = F._product
END
ELSE
BEGIN
INSERT INTO tableTo
(product,
something)
SELECT
F._product,
F.something
FROM
tableFrom F
END
COMMIT TRANSACTION
The UPDATE part is working fine, but no INSERT done.
EDIT1:
Tried this code:
MERGE tableTo AS T
USING tableFrom AS S
ON (T.product= S._product)
WHEN NOT MATCHED BY TARGET
THEN INSERT(product, something) VALUES(S._product, S.something)
WHEN MATCHED
THEN UPDATE SET T.Something= S.Something
Have the following error: "Incorrect syntax near 'MERGE'. You may need to set the compatibility level of the current database to a higher value to enable this feature. See help for the SET COMPATIBILITY_LEVEL option of ALTER DATABASE."
EDIT2:
I googled the above error message and it appeared to be due to a missing semi-colon at the end of the very last line before the MERGE statement. The MERGE command is working perfect!
It's not missing. SQL Server implements the standard MERGE statement which allows you to specify what happens when a match occurs or not between a source and a target. Check the documentation for examples.
Matches are made using a condition that can involve many columns. MERGE allows you to execute an INSERT, UPDATE or DELETE in the following cases:
A match is found based on the condition
A match occurs only in the source
A match occurs only in the target
This way you can update existing rows, insert rows that exist only in the source, delete rows that appear only in the target.
In your case, you could do something like:
MERGE tableTo AS T
USING tableFrom AS S
ON (T.product= S._product)
WHEN NOT MATCHED BY TARGET
THEN INSERT(product, something) VALUES(S._product, S.something)
WHEN MATCHED
THEN UPDATE SET T.Something= S.Something
OUTPUT $action, Inserted.*, Deleted.*;
This statement will insert or update rows as needed and return the values that were inserted or overwritten with the OUTPUT clause.
Use the Merge operation.
From the MSDN documentation:
Performs insert, update, or delete operations on a target table based
on the results of a join with a source table. For example, you can
synchronize two tables by inserting, updating, or deleting rows in one
table based on differences found in the other table.

Resources