I created a table audit trigger in oracle which I'm having a rough time converting it use for Sybase. I have two tables (a base table and a audit table) and what I'm looking for is a trigger which functions as such.
If a new record is inserted into the base table, the full new record is copied from the base table into the audit table along with the date of insert, the user that entered the record and a note that says 'New Record'.
It a record was updated in the base table, the full record from the base table is inserted into the audit table except for the updated column which has the new value (plus a note that says 'Updated Record')
Here is an example of what I created in Oracle:
CREATE TABLE baseTable
(
col1 int,
col2 varchar2(10),
col3 varchar2(50),
chg_date DATE,
chg_usr varchar(10),
PRIMARY KEY(col1)
);
CREATE TABLE auditTable
(
aud_id int,
col1 int,
col2 varchar2(10),
col3 varchar2(50),
chg_typ varchar(100)
chg_date DATE,
chg_usr varchar(30),
PRIMARY KEY (aud_id)
);
CREATE OR REPLACE TRIGGER auditBaseTable
AFTER INSERT OR UPDATE ON baseTable
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO auditTable (aud_id, col1, col2, col3, chg_typ, chg_date, chg_usr)
VALUES(seq.NEXTVAL, :NEW.col1, :NEW.col2, :NEW.col3, "New Record", SYSDATE, :NEW.chg_usr);
ELSEIF UPDATING ('col2') THEN --col1 is PK hence cannot be updated
INSERT INTO auditTable (aud_id, col1, col2, col3, chg_typ, chg_date, chg_usr)
VALUES(seq.NEXTVAL, :OLD.col1, :NEW.col2, :OLD.col3, "Col2 Updated", SYSDATE, :NEW.chg_usr);
...
...
ENDIF;
END;
I already understand that Sybase is not able to do triggers using 'AFTER'.
Any help you can provide will be greatly appreciated even if you can set me on the right track. Thank you.
You can create trigger
for insert, update, delete
No need to worry about before or after
Syntax:
create trigger trigger_name on table_name for update, insert, delete
<>
Related
I'm creating a table to store cars, and another table to store the time when the new car was added to the database, can someone please explain to me how to create the relationship to update time automatically when the car was created.
Create table Cars
(
CarID int Primary Key identity(1,1),
Make varchar(50),
Model varchar(50),
Colour varchar(59)
)
create Table TimeLogs
(
AddedOn SYSDATETIME(),
CarId int unique foreign key references Cars(CarId)
)
I would solve this by not using a second table for what should be a column in the Cars table. The table would be designed more appropriately like this.
Create table Cars
(
CarID int Primary Key identity(1,1),
Make varchar(50),
Model varchar(50),
Colour varchar(59),
AddedOn datetime default SYSDATETIME()
)
To automatically update one table whenever another table is updated, you need to use a TRIGGER.
You needs to use insert trigger for the same, as below
CREATE TRIGGER yourNewTrigger ON yourSourcetable
FOR INSERT
AS
INSERT INTO yourDestinationTable
(col1, col2 , col3, user_id, user_name)
SELECT
'a' , default , null, user_id, user_name
FROM inserted
go
I want to create a before delete trigger. When I delete a record from a table that record has to be inserted into a history table. How can I do this in SQL Server?
In this situation, you're probably better off doing a regular "after" trigger. This is the most common approach to this type of situation.
Something like
CREATE TRIGGER TRG_AUD_DEL
ON yourTable
FOR DELETE
AS
INSERT INTO my_audit_table (col1, col2, ...)
SELECT col1, col2...
FROM DELETED
What will happen is, when a record (or records!) are deleted from your table, the deleted row will be inserted into my_audit_table The DELETED table is a virtual table that contains the record(s) as they were immediately prior to the delete.
Also, note that the trigger runs as part of the implicit transaction on the delete statement, so if your delete fails and rolls back, the trigger will also rollback.
You could also use INSTEAD OF DELETE
CREATE TRIGGER dbo.SomeTableYouWhatToDeleteFrom
ON dbo.YourTable
INSTEAD OF DELETE
AS
BEGIN
-- Some code you want to do before delete
DELETE YourTable
FROM DELETED D
INNER JOIN dbo.YourTable T ON T.PK_1 = D.PK_1
END
It could be done in following steps for let’s say in this example I am using customer table:
CREATE TABLE CUSTOMERS(
ID INT NOT NULL,
NAME VARCHAR (20) NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR (25) ,
LAST_UPDATED DATETIME,
PRIMARY KEY (ID)
);
Create History:
CREATE TABLE CUSTOMERS_HIST(
ID INT NOT NULL,
NAME VARCHAR (20) NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR (25) ,
LAST_UPDATED DATETIME,
PRIMARY KEY (ID)
);
Trigger on source table like below on delete event:
CREATE TRIGGER TRG_CUSTOMERS_DEL
ON CUSTOMERS
FOR DELETE
AS
INSERT INTO CUSTOMERS_HIST (ID, NAME, AGE, ADDRESS, LAST_UPDATED)
SELECT ID, NAME, AGE, ADDRESS, LAST_UPDATED
FROM DELETED
Try a trigger that executes before the delete and throws an error when the condition is not met.
CREATE TRIGGER [dbo].[TableName_PreventDeleteAndUpdate]
ON dbo.TableName
FOR DELETE, UPDATE -- runs before deletes and updates
AS
BEGIN
IF (APP_NAME() <> 'SomeApp')
BEGIN
RAISERROR ('Only delete/update with SomeApp', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END
I have a Code (int) in my table, the ID is set to identity. How can I set a default value for my code to be filled by the same value az ID? I mean Identity.
You could use an after insert trigger:
create table TestTable (id int identity, col1 int)
go
create trigger TestTrigger on TestTable after insert
as begin
update TestTable
set col1 = id
where col1 is null
and id in (select id from inserted)
end
go
Test code:
insert TestTable default values
insert TestTable (col1) values (666)
insert TestTable default values
select * from TestTable
In general, I try to stay clear of triggers. In the long run using a stored procedure for insert is much more maintainable:
create procedure dbo.InsertTestRow(
#col1 int)
as
insert TestTable (col1) values (#col1)
if #col1 is null
begin
update TestTable
set col1 = id
where id = SCOPE_IDENTITY()
end
If it always has the same value - why don't you just drop that field. Otherwise it can be maintained with triggers (BEFORE INSERT one).
I'm looking for something in the
default value! If it is null it should
be filled with the same value as id
but if it is provided with some value,
it should keep that value
You could solve the issue by using coalesce in your queries instead.
create table T (ID int identity, ID2 int)
insert into T values (default)
insert into T values (null)
insert into T values (78)
select
ID,
coalesce(ID2, ID) as ID2
from T
Result
ID ID2
-- ---
1 1
2 2
3 78
Assuming your table's ID is an Identity column, you could consider using a constraint:
ALTER TABLE MyTable
ADD CONSTRAINT MyTableCodeDefault
DEFAULT IDENT_CURRENT('MyTable') FOR Code
This works for these use cases:
INSERT INTO MyTable DEFAULT VALUES
INSERT INTO MyTable ({columns NOT including 'Code'})
VALUES ({value list matching insert columns})
INSERT INTO MyTable (Code) VALUES (666)
INSERT INTO MyTable (Code) SELECT 8 UNION SELECT 13 UNION SELECT 21
But it does not work for bulk inserts:
INSERT INTO MyTable ({columns NOT including 'Code'})
SELECT {value list matching insert columns}
UNION
SELECT {value list matching insert columns}
UNION
SELECT {value list matching insert columns}
This restriction may seem onerous, but in my practical experience, it's rarely a problem. Most of the use cases I've encountered that need a default value involve user/UI 'convenience': don't force the user to pick a value if they don't want to.
OTOH, rarely do I encounter bulk insert situations where it's impractical to specify the value for the columns you're targeting.
You could use computed column, like this:
if object_id('TempTable') is not null drop table TempTable
create table TempTable (Id int identity(1,1), Code as Id)
insert into TempTable
default values
insert into TempTable
default values
insert into TempTable
default values
select * from TempTable
Of course if you have other columns, then you dont need default values:
if object_id('TempTable') is not null drop table TempTable
create table TempTable (Id int identity(1,1), Code as Id, SomethingElse int)
insert into TempTable (SomethingElse)
select 10 union all
select 11 union all
select 12
select * from TempTable
But, like zerkms said - why do you need two columns that are same?
If the field is an Identity field in SQL Server, the database engine will take care of its value. What we normally do is to read the record back (after inserting) to get to the generated Id.
EDIT: It sounds like you are trying to "override" the identity? If so, before you insert, run:
SET IDENTITY_INSERT [tableName] ON
You'll have to be careful not to insert a value that already exists. This can get tricky, though. So maybe consider removing the identity property altogether, and managing the default values yourself?
I am using the following code to check if the temporary table exists and drop the table if it exists before creating again. It works fine as long as I don't change the columns. If I add a column later, it will give an error saying "invalid column". Please let me know what I am doing wrong.
IF OBJECT_ID('tempdb..#Results') IS NOT NULL
DROP TABLE #Results
CREATE TABLE #Results
(
Company CHAR(3),
StepId TINYINT,
FieldId TINYINT,
)
select company, stepid, fieldid from #Results
--Works fine to this point
IF OBJECT_ID('tempdb..#Results') IS NOT NULL
DROP TABLE #Results
CREATE TABLE #Results
(
Company CHAR(3),
StepId TINYINT,
FieldId TINYINT,
NewColumn NVARCHAR(50)
)
select company, stepid, fieldid, NewColumn from #Results
--Does not work
I cannot reproduce the error.
Perhaps I'm not understanding the problem.
The following works fine for me in SQL Server 2005, with the extra "foo" column appearing in the second select result:
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
GO
CREATE TABLE #Results ( Company CHAR(3), StepId TINYINT, FieldId TINYINT )
GO
select company, stepid, fieldid from #Results
GO
ALTER TABLE #Results ADD foo VARCHAR(50) NULL
GO
select company, stepid, fieldid, foo from #Results
GO
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
GO
The statement should be of the order
Alter statement for the table
GO
Select statement.
Without 'GO' in between, the whole thing will be considered as one single script and when the select statement looks for the column,it won't be found.
With 'GO' , it will consider the part of the script up to 'GO' as one single batch and will execute before getting into the query after 'GO'.
Instead of dropping and re-creating the temp table you can truncate and reuse it
IF OBJECT_ID('tempdb..#Results') IS NOT NULL
Truncate TABLE #Results
else
CREATE TABLE #Results
(
Company CHAR(3),
StepId TINYINT,
FieldId TINYINT,
)
If you are using Sql Server 2016 or Azure Sql Database then use the below syntax to drop the temp table and recreate it. More info here MSDN
Syntax
DROP TABLE [ IF EXISTS ] [ database_name . [ schema_name ] . |
schema_name . ] table_name [ ,...n ]
Query:
DROP TABLE IF EXISTS #Results
CREATE TABLE #Results
(
Company CHAR(3),
StepId TINYINT,
FieldId TINYINT,
)
I think the problem is you need to add GO statement in between to separate the execution into batches. As the second drop script i.e. IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results did not drop the temp table being part of single batch. Can you please try the below script.
IF OBJECT_ID('tempdb..#Results') IS NOT NULL
DROP TABLE #Results
CREATE TABLE #Results
(
Company CHAR(3),
StepId TINYINT,
FieldId TINYINT,
)
GO
select company, stepid, fieldid from #Results
IF OBJECT_ID('tempdb..#Results') IS NOT NULL
DROP TABLE #Results
CREATE TABLE #Results
(
Company CHAR(3),
StepId TINYINT,
FieldId TINYINT,
NewColumn NVARCHAR(50)
)
GO
select company, stepid, fieldid, NewColumn from #Results
This could be accomplished with a single line of code:
IF OBJECT_ID('tempdb..#tempTableName') IS NOT NULL DROP TABLE #tempTableName;
This worked for me:
social.msdn.microsoft.com/Forums/en/transactsql/thread/02c6da90-954d-487d-a823-e24b891ec1b0?prof=required
if exists (
select * from tempdb.dbo.sysobjects o
where o.xtype in ('U')
and o.id = object_id(N'tempdb..#tempTable')
)
DROP TABLE #tempTable;
Now you can use the below syntax if you are using one of the new versions of SQL Server (2016+).
DROP TABLE IF EXISTS schema.yourtable(even temporary tables #...)
Just a little comment from my side since the OBJECT_ID doesn't work for me. It always returns that
`#tempTable doesn't exist
..even though it does exist. I just found it's stored with different name (postfixed by _ underscores) like so :
#tempTable________
This works well for me:
IF EXISTS(SELECT [name] FROM tempdb.sys.tables WHERE [name] like '#tempTable%') BEGIN
DROP TABLE #tempTable;
END;
This worked for me,
IF OBJECT_ID('tempdb.dbo.#tempTable') IS NOT NULL
DROP TABLE #tempTable;
Here tempdb.dbo(dbo is nothing but your schema) is having more importance.
pmac72 is using GO to break down the query into batches and using an ALTER.
You appear to be running the same batch but running it twice after changing it: DROP... CREATE... edit... DROP... CREATE..
Perhaps post your exact code so we can see what is going on.
I usually hit this error when I have already created the temp table; the code that checks the SQL statement for errors sees the "old" temp table in place and returns a miscount on the number of columns in later statements, as if the temp table was never dropped.
After changing the number of columns in a temp table after already creating a version with less columns, drop the table and THEN run your query.
I recently saw a DBA do something similar to this:
begin try
drop table #temp
end try
begin catch
print 'table does not exist'
end catch
create table #temp(a int, b int)
Note: This also works for ## temp tables.
i.e.
IF OBJECT_ID('tempdb.dbo.##AuditLogTempTable1', 'U') IS NOT NULL
DROP TABLE ##AuditLogTempTable1
Note: This type of command only suitable post SQL Server 2016.
Ask yourself .. Do I have any customers that are still on SQL Server 2012 ?
DROP TABLE IF EXISTS ##AuditLogTempTable1
My code uses a Source table that changes, and a Destination table that must match those changes.
--
-- Sample SQL to update only rows in a "Destination" Table
-- based on only rows that have changed in a "Source" table
--
--
-- Drop and Create a Temp Table to use as the "Source" Table
--
IF OBJECT_ID('tempdb..#tSource') IS NOT NULL drop table #tSource
create table #tSource (Col1 int, Col2 int, Col3 int, Col4 int)
--
-- Insert some values into the source
--
Insert #tSource (Col1, Col2, Col3, Col4) Values(1,1,1,1)
Insert #tSource (Col1, Col2, Col3, Col4) Values(2,1,1,2)
Insert #tSource (Col1, Col2, Col3, Col4) Values(3,1,1,3)
Insert #tSource (Col1, Col2, Col3, Col4) Values(4,1,1,4)
Insert #tSource (Col1, Col2, Col3, Col4) Values(5,1,1,5)
Insert #tSource (Col1, Col2, Col3, Col4) Values(6,1,1,6)
--
-- Drop and Create a Temp Table to use as the "Destination" Table
--
IF OBJECT_ID('tempdb..#tDest') IS NOT NULL drop Table #tDest
create table #tDest (Col1 int, Col2 int, Col3 int, Col4 int)
--
-- Add all Rows from the Source to the Destination
--
Insert #tDest
Select Col1, Col2, Col3, Col4 from #tSource
--
-- Look at both tables to see that they are the same
--
select *
from #tSource
Select *
from #tDest
--
-- Make some changes to the Source
--
update #tSource
Set Col3=19
Where Col1=1
update #tSource
Set Col3=29
Where Col1=2
update #tSource
Set Col2=38
Where Col1=3
update #tSource
Set Col2=48
Where Col1=4
--
-- Look at the Differences
-- Note: Only 4 rows are different. 2 Rows have remained the same.
--
Select Col1, Col2, Col3, Col4
from #tSource
except
Select Col1, Col2, Col3, Col4
from #tDest
--
-- Update only the rows that have changed
-- Note: I am using Col1 like an ID column
--
Update #tDest
Set Col2=S.Col2,
Col3=S.Col3,
Col4=S.Col4
From ( Select Col1, Col2, Col3, Col4
from #tSource
except
Select Col1, Col2, Col3, Col4
from #tDest
) S
Where #tDest.Col1=S.Col1
--
-- Look at the tables again to see that
-- the destination table has changed to match
-- the source table.
select *
from #tSource
Select *
from #tDest
--
-- Clean Up
--
drop table #tSource
drop table #tDest
Yes, "invalid column" this error raised from the line "select company, stepid, fieldid, NewColumn from #Results".
There are two phases of runing t-sql,
first, parsing, in this phase the sql server check the correction of you submited sql string, including column of table, and optimized your query for fastest retreival.
second, running, retreiving the datas.
If table #Results exists then parsing process will check the columns you specified are valid or not, else (table doesn't exist) parsing will be by passsed the checking columns as you specified.
When you change a column in a temp table, you must drop the table before running the query again. (Yes, it is annoying. Just what you have to do.)
I have always assumed this is because the "invalid column" check is done by parser before the query is run, so it is based on the columns in the table before it is dropped..... and that is what pnbs also said.
given this table definition
create table herb.app (appId int identity primary key
, application varchar(15) unique
, customerName varchar(35),LoanProtectionInsurance bit
, State varchar(3),Address varchar(50),LoanAmt money
,addedBy varchar(7) not null,AddedDt smalldatetime default getdate())
I believe changes will be minimal, usually only a single field, and very sparse.
So I created this table:
create table herb.appAudit(appAuditId int primary key
, field varchar(20), oldValue varchar(50),ChangedBy varchar(7) not null,AddedDt smalldatetime default getdate())
How in a trigger can I get the column name of the value of what was changed to store it? I know how to get the value by joining the deleted table.
Use the inserted and deleted tables. Nigel Rivett wrote a great generic audit trail trigger using these tables. It is fairly complex SQL code, but it highlights some pretty cool ways of pulling together the information and once you understand them you can create a custom solution using his ideas as inspiration, or you could just use his script.
Here are the important ideas about the tables:
On an insert, inserted holds the inserted values and deleted is empty.
On an update, inserted holds the new values and deleted holds the old values.
On a delete, deleted holds the deleted values and inserted is empty.
The structure of the inserted and deleted tables (if not empty) are identical to the target table.
You can determine the column names from system tables and iterate on them as illustrated in Nigel's code.
if exists (select * from inserted)
if exists (select * from deleted)
-- this is an update
...
else
-- this is an insert
...
else
-- this is a delete
...
-- For updates to a specific field
SELECT d.[MyField] AS OldValue, i.[MyField] AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.[MyPrimaryKeyField] = d.[MyPrimaryKeyField]
-- For your table
SELECT d.CustomerName AS OldValue, i.CustomerName AS NewValue, system_user AS User
FROM inserted i
INNER JOIN deleted d ON i.appId = d.appId
If you really need this kind of auditing in a way that's critical to your business look at SQL Server 2008's Change Data Capture feature. That feature alone could justify the cost of an upgrade.
something like this for each field you want to track
if UPDATE(Track_ID)
begin
insert into [log].DataChanges
(
dcColumnName,
dcID,
dcDataBefore,
dcDataAfter,
dcDateChanged,
dcUser,
dcTableName
)
select
'Track_ID',
d.Data_ID,
coalesce(d.Track_ID,-666),
coalesce(i.Track_ID,-666),
getdate(),
#user,
#table
from inserted i
join deleted d on i.Data_ID=d.Data_ID
and coalesce(d.Track_ID,-666)<>coalesce(i.Track_ID,-666)
end
'Track_ID' is the name of the field, and d.Data_ID is the primary key of the table your tracking. #user is the user making the changes, and #table would be the table your keeping track of changes in case you're tracking more than one table in the same log table
Here's my quick and dirty audit table solution. (from http://freachable.net/2010/09/29/QuickAndDirtySQLAuditTable.aspx)
CREATE TABLE audit(
[on] datetime not null default getutcdate(),
[by] varchar(255) not null default system_user+','+AppName(),
was xml null,
[is] xml null
)
CREATE TRIGGER mytable_audit ON mytable for insert, update, delete as
INSERT audit(was,[is]) values(
(select * from deleted as [mytable] for xml auto,type),
(select * from inserted as [mytable] for xml auto,type)
)