SQL trigger question: why is this a syntax error? - sql

I would to create a trigger for Sybase, but it shows an error.
What I want to do is, when a delete operation is done on the [student] table, to check whether there is any record related to student on [account], and if so, raise an exception.
There seems to be a lack of support by Sybase. Their official seem not people to visit.
*CREATE TRIGGER AccountChecker
BEFORE DELETE ON student
REFERENCING OLD AS old_student
FOR EACH ROW
BEGIN
DECLARE #acc CHAR(4);
DECLARE #acc_not_null EXCEPTION FOR SQLSTATE '99999';
SELECT #acc=account.account_number FROM account
WHERE account.student_id = old_student.student_id;
IF #acc IS NOT NULL
BEGIN
SIGNAL acc_not_null
END
END*

Sybase supports foreign keys and primary keys, both via procedures such as sp_primarykey and sp_foreignkey and via declarative SQL constraints. What you are seeking to do is exactly what a foreign key from [account] referencing [student] should do.
The Sybase SQL User Guide (Adaptive Server 15, if it matters) illustrates a 'delete restricted' trigger (with somewhat different indentation):
create trigger deltitle
on titles
for delete
as
if (select count(*)
from deleted, salesdetail
where salesdetail.title_id =
deleted.title_id) > 0
begin
rollback transaction
print "You cannot delete a title with sales."
end
I'm not convinced that rollback is a good idea; an exception is probably better.
The notation you are trying to use hews more closely to the SQL standard than the documented Sybase-supported notation.

Doesn't Sybase support foreign keys?

Related

Database level Trigger on table creation to prevent creation of tables without Primary Keys

I'm trying to come up with a way to prevent analysts from creating tables without a Primary key. I know how to create a database level trigger, and I know how to query to find whether or not a table has a primary key, but I was hoping that SQL Server has a 'Created' table, much in the same way it has 'inserted'/'updated' tables used in ON INSERT/ON UPDATE triggers.
Hypothetically, if SQL Server did have this 'Created' table, my trigger would look like this:
CREATE TRIGGER PKViolations
ON DATABASE
FOR CREATE_TABLE
AS
IF EXISTS (SELECT 1
FROM Created
WHERE OBJECTPROPERTY(OBJECT_ID,'TableHasPrimaryKey') = 0)
BEGIN
PRINT 'Please include a Primary Key. Transaction has been rolled back.'
ROLLBACK;
END
Instructing analysts on importance of primary keys has helped, but tables are still being created without PKs, any insight is greatly appreciated!
You can use EVENTDATA() to get the ObjectName. Then you can use your query to examine if there is a primary key.
SELECT NewTableName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
You can read more about EVENTDATA here. https://learn.microsoft.com/en-us/sql/t-sql/functions/eventdata-transact-sql
Here is a fully functional example:
CREATE TRIGGER PKViolations
ON DATABASE
FOR CREATE_TABLE
AS
declare #NewTableName nvarchar(255)
SELECT #NewTableName = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(255)')
IF OBJECTPROPERTY(OBJECT_ID(#NewTableName),'TableHasPrimaryKey') = 0
BEGIN
PRINT 'Please include a Primary Key. Transaction has been rolled back.'
ROLLBACK;
END
go

How to make a trigger that will not allow duplicate data?

It is necessary to make the addition of a unique line in what ever field.
I write this:
alter trigger Triger1
on Week_Schedule for insert
as
if ##ROWCOUNT=1
begin
if exists (
select *
from inserted as i, Week_Schedule as w
where i.Week_Number = w.Week_Number
)
begin
rollback tran
print 'Week number already exists!'
end
end
but something goes wrong. By adding even a unique line pops up a warning with the text of my trigger. How can I fix this?
There is no need to check ##ROWCOUNT. You have written the logic correctly using inserted, so you don't need some weird work-around like that.
If I understand correctly, just enclose the body of the trigger in a begin/end block:
alter trigger Triger1
on Week_Schedule for insert
as
begin
if exists (select 1
from inserted i join
Week_Schedule w
on i.Week_Number = w.Week_Number
)
begin
rollback tran
print 'Неделя с таким номером уже существует!'
end;
end;
Then you can add additional statements in the begin/end block.
EDIT:
As noted in a comment, the above will roll-back all inserts (as opposed to just those with a single row as in the original question).
I strongly suspect that the real solution to the OPs issue is some combination of foreign key, unique, and perhaps check constraints. However, that would be an answer to another question, which I encourage the OP to ask.
As mentioned in the comments, you should consider a unique constraint for this. Triggers can cause a lot of un-intended consequences and is un-necessary overhead for this particular case.
ALTER TABLE dbo.Week_Schedule
ADD CONSTRAINT YOURCONSTRAINTNAMEHERE UNIQUE (Week_number)

SQL Server prevent all records from being updated or deleted

I am running SQL Server 2008. I written code that should be pretty safe to prevent any records being deleted or updated, but would be much happier if I could do this at the database level. Is it possible to mark a table so that once a row has been inserted it can never be modified or deleted?
Edit per comments. It seems you are actually looking for Versioning which really shouldn't be done via triggers but it can be with a performance impact and a Lot more coding.
Most appropriate method to combat your concern. Maintain transactional backups every X# of minutes so you can roll back if it gets messed up.
However sql-server does have 2 change tracking methods built in that you could explore.
Change Tracking - which simply identifies if a record was modified, and is really useful when synchronizing changes to a DB. Basically it increments a BIGINT for every row for every operation so you just have to check for records greater than the previous synchronize number.
Change Data Capture - this will capture the insert/update/delete and the state of the record (row).
For your particular worry Change Data Capture might be plausible and way more easy to maintain than triggers. I haven't tried it myself because Change Tracking was enough for my needs but here is a link to Microsoft's Documentation https://msdn.microsoft.com/en-us/library/cc645937(v=sql.110).aspx
If you insist on Triggers here would be an example, you will have to maintain the original primary key for referential integrity or you might not know which change caused the problem
CREATE TABLE TblName (
PrimaryKeyID INT IDENTITY(1,1) NOT NULL PRIMARY KEY
,Col1 INT NULL
,Col2 INT NULL
,OriginalPrimaryKeyId INT NULL
,CreateDate DATETIME DEFAULT(GETDATE())
,UpdateDate DATETIME DEFAULT(GETDATE())
,IsLatestVersion BIT NOT NULL DEFAULT(1)
)
GO
CREATE TRIGGER dbo.TrigForInsertEnforceVersionColTblName ON dbo.TblName
FOR INSERT
AS
BEGIN
BEGIN TRY
IF (SELECT COUNT(*) FROM TblName WHERE IsLatestVersion <> 1 AND OriginalPrimaryKeyId IS NULL) > 0
BEGIN
;THROW 51000, 'Attempted to insert a record identified as Previous Version without referencing another record', 1
END
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION
--this will mean the loss of some Primary Keys but it is better than an
--INSTEAD of INSERT because you wont have to handle the insert code
;THROW
END CATCH
END
GO
CREATE TRIGGER dbo.TriggerName ON dbo.TblName
INSTEAD OF UPDATE, DELETE
AS
BEGIN
BEGIN TRY
IF EXISTS (
SELECT *
FROM
TblName t
INNER JOIN inserted i
ON t.PrimaryKeyID = i.PrimaryKeyID
AND (t.OriginalPrimaryKeyId <> i.OriginalPrimaryKeyId
OR t.IsLatestVersion <> i.IsLatestVersion)
)
BEGIN
;THROW 51000, 'OriginalPrimaryKeyId Column or IsLatestVersion Column was attempted to be updated', 1
END
--don't have to test count can just run the update statement
IF ((SELECT COUNT(*) FROM inserted) > 0)
BEGIN
--It's an UPDATE Operations so insert new row but maintain original primary key
--so you know what the new row is a version of
INSERT INTO dbo.TblName (Col1, Col2, OriginalPrimaryKeyId)
SELECT
i.Col1
,i.Col2
,OriginalPrimaryKeyId = CASE
WHEN t.OriginalPrimaryKeyId IS NULL THEN t.PrimaryKeyID
ELSE t.OriginalPrimaryKeyId
END
FROM
inserted i
INNER JOIN TblName t
ON i.PrimaryKeyID = t.PrimaryKeyID
END
UPDATE t
SET IsLatestVersion = 0
,UpdateDate = GETDATE()
FROM
TblName t
INNER JOIN deleted d
ON t.PrimaryKeyID = d.PrimaryKeyID
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0 ROLLBACK TRANSACTION
;THROW
END CATCH
END
Permissions discussion:
For anything row level and to block everyone including database owner role or administrators you would have to create a trigger. But those roles could always remove the trigger too and modify the table. Perhaps simple Permissions would be enough such as
on an entire role, which would be best if you put the users in a role
GRANT INSERT ON SchemaName.TableName TO RoleName
DENY UPDATE ON SchemaName.TableName TO RoleName
DENY DELETE ON SchemaName.TableName TO RoleName
OR for specific users same commands just change RoleName to username
DENY UPDATE ON SchemaName.TableName TO UserName
This would grant the ability to insert but revoke ability to update or delete a record.
You can also deny execute, alter, and a bunch more here is Microsoft's documentation: https://msdn.microsoft.com/en-us/library/ms173724.aspx
Using a trigger instead of security permissions is a lot messier and if someone with enough permissions wants to make a change they still can it will just slow them down but not by much. So if you are worried about that ability make sure you have good backups.

SQL Server: Does 'DROP TABLE' inside transaction causes an implicit commit?

My question is kind of easy but i'm still doubting after I created this transaction. If I execute the following code:
BEGIN TRANSACTION
DROP TABLE Table_Name
Can I perform a ROLLBACK TRANSACTION that recovers the dropped table? I'm asking because I don't know what happens in the 'Object Explorer' and I didn't found any question of this topic, so I think that it could be a useful issue.
DROP TABLE can be rolled back and it does not auto-commit.
This is incredibly easy to test.
create table TransactionTest
(
ID int identity primary key clustered,
SomeValue varchar(20)
)
insert TransactionTest
select 'Here is a row'
begin transaction
drop table TransactionTest
rollback transaction
select * from TransactionTest
I just want to add that I tried in Oracle 11g, Mysql 5.7 and MSSQL 2016. It only rolled back (worked) with MSSQL RDBMS. I would expect that most other RDBMS won't support it since it execute schema changes.
ORACLE PL/SQL EX:
savepoint mysave;
DROP TABLE test_table;
ROLLBACK TO mysave;
select * from test_table;

How can I do a BEFORE UPDATED trigger with sql server?

I'm using Sqlserver express and I can't do before updated trigger. There's a other way to do that?
MSSQL does not support BEFORE triggers. The closest you have is INSTEAD OF triggers but their behavior is different to that of BEFORE triggers in MySQL.
You can learn more about them here, and note that INSTEAD OF triggers "Specifies that the trigger is executed instead of the triggering SQL statement, thus overriding the actions of the triggering statements." Thus, actions on the update may not take place if the trigger is not properly written/handled. Cascading actions are also affected.
You may instead want to use a different approach to what you are trying to achieve.
It is true that there aren't "before triggers" in MSSQL. However, you could still track the changes that were made on the table, by using the "inserted" and "deleted" tables together. When an update causes the trigger to fire, the "inserted" table stores the new values and the "deleted" table stores the old values. Once having this info, you could relatively easy simulate the "before trigger" behaviour.
Can't be sure if this applied to SQL Server Express, but you can still access the "before" data even if your trigger is happening AFTER the update. You need to read the data from either the deleted or inserted table that is created on the fly when the table is changed. This is essentially what #Stamen says, but I still needed to explore further to understand that (helpful!) answer.
The deleted table stores copies of the affected rows during DELETE and
UPDATE statements. During the execution of a DELETE or UPDATE
statement, rows are deleted from the trigger table and transferred to
the deleted table...
The inserted table stores copies of the affected rows during INSERT
and UPDATE statements. During an insert or update transaction, new
rows are added to both the inserted table and the trigger table...
https://msdn.microsoft.com/en-us/library/ms191300.aspx
So you can create your trigger to read data from one of those tables, e.g.
CREATE TRIGGER <TriggerName> ON <TableName>
AFTER UPDATE
AS
BEGIN
INSERT INTO <HistoryTable> ( <columns...>, DateChanged )
SELECT <columns...>, getdate()
FROM deleted;
END;
My example is based on the one here:
http://www.seemoredata.com/en/showthread.php?134-Example-of-BEFORE-UPDATE-trigger-in-Sql-Server-good-for-Type-2-dimension-table-updates
sql-server triggers
T-SQL supports only AFTER and INSTEAD OF triggers, it does not feature a BEFORE trigger, as found in some other RDBMSs.
I believe you will want to use an INSTEAD OF trigger.
All "normal" triggers in SQL Server are "AFTER ..." triggers. There are no "BEFORE ..." triggers.
To do something before an update, check out INSTEAD OF UPDATE Triggers.
To do a BEFORE UPDATE in SQL Server I use a trick. I do a false update of the record (UPDATE Table SET Field = Field), in such way I get the previous image of the record.
Remember that when you use an instead trigger, it will not commit the insert unless you specifically tell it to in the trigger. Instead of really means do this instead of what you normally do, so none of the normal insert actions would happen.
Full example:
CREATE TRIGGER [dbo].[trig_020_Original_010_010_Gamechanger]
ON [dbo].[T_Original]
AFTER UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #Old_Gamechanger int;
DECLARE #New_Gamechanger int;
-- Insert statements for trigger here
SELECT #Old_Gamechanger = Gamechanger from DELETED;
SELECT #New_Gamechanger = Gamechanger from INSERTED;
IF #Old_Gamechanger != #New_Gamechanger
BEGIN
INSERT INTO [dbo].T_History(ChangeDate, Reason, Callcenter_ID, Old_Gamechanger, New_Gamechanger)
SELECT GETDATE(), 'Time for a change', Callcenter_ID, #Old_Gamechanger, #New_Gamechanger
FROM deleted
;
END
END
The updated or deleted values are stored in DELETED. we can get it by the below method in trigger
Full example,
CREATE TRIGGER PRODUCT_UPDATE ON PRODUCTS
FOR UPDATE
AS
BEGIN
DECLARE #PRODUCT_NAME_OLD VARCHAR(100)
DECLARE #PRODUCT_NAME_NEW VARCHAR(100)
SELECT #PRODUCT_NAME_OLD = product_name from DELETED
SELECT #PRODUCT_NAME_NEW = product_name from INSERTED
END