I am trying to create an audit trigger without having to specifiy the column list more than once.
To this end, I want to product a temporary table of the content of the INSERTED or DELETED data in the trigger, then process that into an audit table.
If I use this:
IF #ChangeType = 'D'
SELECT * INTO #tmp FROM DELETED
ELSE
SELECT * INTO #tmp FROM INSERTED
Then I get a compilation error at the 2nd SELECT * INTO that the table #tmp already exists.
If I try and work around this using dynamic SQL:
SET #Sql = 'SELECT * INTO #tmp FROM '
IF #ChangeType = 'D'
SET #Sql = #Sq + 'DELETED'
ELSE
SET #Sql = #Sql + 'INSERTED'
EXEC (#Sql)
Then I get an error that the DELETED and INSERTED tables do not exist.
How can I get the INSERTED and DELETED tables in a trigger into a temporary or other in-memory table?
Try to create the temporary table outside the if, like:
SELECT TOP 0 * INTO #tmp FROM DELETED
IF #ChangeType = 'D'
INSERT INTO #tmp SELECT * FROM DELETED
ELSE
INSERT INTO #tmp SELECT * FROM INSERTED
This is a known problem due to the resolve-on-parse of the temp table object. With two SELECT - INTO statements in the same scope, SQL Server throws the towel.
SELECT * INTO #tmp FROM DELETED WHERE 1=0
IF #ChangeType = 'D'
INSERT #tmp SELECT * FROM DELETED
ELSE
INSERT #tmp SELECT * FROM INSERTED
I'd be interested as to why you need to copy the data into another table in the first place. But, that's off-topic...
Temporary table (#temp) are notionally stored on disc, and Table Variables (#temp) are notionally only in memory and may be more optimal for small tasks. (Assumes writes to the table will normally only affect small numbers of rows.)
Temporary tables, however, can be created using the SELECT INTO trick, avoiding the need to know the table definition in advance.
If you do know the table definition in advance, however, can't you simply use something such as the following?
DECLARE #temp TABLE (id AS INT, val as INT)
IF #ChangeType = 'D'
INSERT INTO #temp SELECT * FROM DELETED
ELSE
INSERT INTO #temp SELECT * FROM INSERTED
Personally, I'd even avoid using * if possible. Your subsequent queries will only use specific fields, so I'd only copy the fields I was using. This has the added benefit that if fields are added to the table, the code doesn't break...
DECLARE #temp TABLE (id AS INT, val as INT)
IF #ChangeType = 'D'
INSERT INTO #temp SELECT id, val FROM DELETED
ELSE
INSERT INTO #temp SELECT id, val FROM INSERTED
In my mind, the advantage of specifying the fields (which is what you wish to avoid), is that you can ensure that you always only copy what you need.
Related
I have a problem with this trigger. I would like it to update the requested information
only to the row in question (the one I just updated) and not the entire table.
CREATE TRIGGER [dbo].[after_update]
ON [dbo].[MYTABLE]
AFTER UPDATE
AS
BEGIN
UPDATE MYTABLE
SET mytable.CHANGED_ON = GETDATE(),
CHANGED_BY=USER_NAME(USER_ID())
How do I tell the trigger that this applies only to the row in question?
Here is my example after a test
CREATE TRIGGER [dbo].UpdateTasadoresName
ON [dbo].Tasadores
FOR UPDATE
AS
UPDATE Tasadores
SET NombreCompleto = RTRIM( Tasadores.Nombre + ' ' + isnull(Tasadores.ApellidoPaterno,'') + ' ' + isnull(Tasadores.ApellidoMaterno,'') )
FROM Tasadores
INNER JOIN INSERTED i ON Tasadores.id = i.id
The inserted special table will have the information from the updated record.
Try this (update, not after update)
CREATE TRIGGER [dbo].[xxx_update] ON [dbo].[MYTABLE]
FOR UPDATE
AS
BEGIN
UPDATE MYTABLE
SET mytable.CHANGED_ON = GETDATE()
,CHANGED_BY = USER_NAME(USER_ID())
FROM inserted
WHERE MYTABLE.ID = inserted.ID
END
you can call INSERTED, SQL Server uses these tables to capture the data of the modified row before and after the event occurs.I assume in your table the name of the key is Id
I think the following code can help you
CREATE TRIGGER [dbo].[after_update]
ON [dbo].[MYTABLE]
AFTER UPDATE
AS
BEGIN
UPDATE dbo.[MYTABLE]
SET dbo.[MYTABLE].CHANGED_ON = GETDATE(),
dbo.[MYTABLE].CHANGED_BY = USER_NAME(USER_ID())
FROM INSERTED
WHERE INSERTED.Id = dbo.[MYTABLE].[Id]
END
You should be able to access the INSERTED table and retrieve ID or table's primary key. Something similar to this example ...
CREATE TRIGGER [dbo].[after_update] ON [dbo].[MYTABLE]
AFTER UPDATE AS
BEGIN
DECLARE #id AS INT
SELECT #id = [IdColumnName]
FROM INSERTED
UPDATE MYTABLE
SET mytable.CHANGED_ON = GETDATE(),
CHANGED_BY=USER_NAME(USER_ID())
WHERE [IdColumnName] = #id
Here's a link on MSDN on the INSERTED and DELETED tables available when using triggers: http://msdn.microsoft.com/en-au/library/ms191300.aspx
It is very simple to do that,
First create a copy of your table that your want keep the log for
For example you have Table dbo.SalesOrder with columns SalesOrderId, FirstName,LastName, LastModified
Your Version archieve table should be dbo.SalesOrderVersionArchieve with columns SalesOrderVersionArhieveId, SalesOrderId, FirstName,LastName, LastModified
Here is the how you will set up a trigger on SalesOrder table
USE [YOURDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Karan Dhanu
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE TRIGGER dbo.[CreateVersionArchiveRow]
ON dbo.[SalesOrder]
AFTER Update
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.SalesOrderVersionArchive
SELECT *
FROM deleted;
END
Now if you make any changes in saleOrder table it will show you the change in VersionArchieve table
try this solution.
DECLARE #Id INT
DECLARE #field VARCHAR(50)
SELECT #Id= INSERTED.CustomerId
FROM INSERTED
IF UPDATE(Name)
BEGIN
SET #field = 'Updated Name'
END
IF UPDATE(Country)
BEGIN
SET #field = 'Updated Country'
END
INSERT INTO CustomerLogs
VALUES(#Id, #field)
// OR
-- If you wish to update existing table records.
UPDATE YOUR_TABLE SET [FIELD]=[VALUE] WHERE {CONDITION}
I didn't checked this with older version of sql server but this will work with sql server 2012.
Try this script to create a temporary table TESTTEST and watch the order of precedence as the triggers are called in this order: 1) INSTEAD OF, 2) FOR, 3) AFTER
All of the logic is placed in INSTEAD OF trigger and I have 2 examples of how you might code some scenarios...
Good luck...
CREATE TABLE TESTTEST
(
ID INT,
Modified0 DATETIME,
Modified1 DATETIME
)
GO
CREATE TRIGGER [dbo].[tr_TESTTEST_0] ON [dbo].TESTTEST
INSTEAD OF INSERT,UPDATE,DELETE
AS
BEGIN
SELECT 'INSTEAD OF'
SELECT 'TT0.0'
SELECT * FROM TESTTEST
SELECT *, 'I' Mode
INTO #work
FROM INSERTED
UPDATE #work SET Mode='U' WHERE ID IN (SELECT ID FROM DELETED)
INSERT INTO #work (ID, Modified0, Modified1, Mode)
SELECT ID, Modified0, Modified1, 'D'
FROM DELETED WHERE ID NOT IN (SELECT ID FROM INSERTED)
--Check Security or any other logic to add and remove from #work before processing
DELETE FROM #work WHERE ID=9 -- because you don't want anyone to edit this id?!?!
DELETE FROM #work WHERE Mode='D' -- because you don't want anyone to delete any records
SELECT 'EV'
SELECT * FROM #work
IF(EXISTS(SELECT TOP 1 * FROM #work WHERE Mode='I'))
BEGIN
SELECT 'I0.0'
INSERT INTO dbo.TESTTEST (ID, Modified0, Modified1)
SELECT ID, Modified0, Modified1
FROM #work
WHERE Mode='I'
SELECT 'Cool stuff would happen here if you had FOR INSERT or AFTER INSERT triggers.'
SELECT 'I0.1'
END
IF(EXISTS(SELECT TOP 1 * FROM #work WHERE Mode='D'))
BEGIN
SELECT 'D0.0'
DELETE FROM TESTTEST WHERE ID IN (SELECT ID FROM #work WHERE Mode='D')
SELECT 'Cool stuff would happen here if you had FOR DELETE or AFTER DELETE triggers.'
SELECT 'D0.1'
END
IF(EXISTS(SELECT TOP 1 * FROM #work WHERE Mode='U'))
BEGIN
SELECT 'U0.0'
UPDATE t SET t.Modified0=e.Modified0, t.Modified1=e.Modified1
FROM dbo.TESTTEST t
INNER JOIN #work e ON e.ID = t.ID
WHERE e.Mode='U'
SELECT 'U0.1'
END
DROP TABLE #work
SELECT 'TT0.1'
SELECT * FROM TESTTEST
END
GO
CREATE TRIGGER [dbo].[tr_TESTTEST_1] ON [dbo].TESTTEST
FOR UPDATE
AS
BEGIN
SELECT 'FOR UPDATE'
SELECT 'TT1.0'
SELECT * FROM TESTTEST
SELECT 'I1'
SELECT * FROM INSERTED
SELECT 'D1'
SELECT * FROM DELETED
SELECT 'TT1.1'
SELECT * FROM TESTTEST
END
GO
CREATE TRIGGER [dbo].[tr_TESTTEST_2] ON [dbo].TESTTEST
AFTER UPDATE
AS
BEGIN
SELECT 'AFTER UPDATE'
SELECT 'TT2.0'
SELECT * FROM TESTTEST
SELECT 'I2'
SELECT * FROM INSERTED
SELECT 'D2'
SELECT * FROM DELETED
SELECT 'TT2.1'
SELECT * FROM TESTTEST
END
GO
SELECT 'Start'
INSERT INTO TESTTEST (ID, Modified0) VALUES (9, GETDATE())-- not going to insert
SELECT 'RESTART'
INSERT INTO TESTTEST (ID, Modified0) VALUES (10, GETDATE())--going to insert
SELECT 'RESTART'
UPDATE TESTTEST SET Modified1=GETDATE() WHERE ID=10-- gointo to update
SELECT 'RESTART'
DELETE FROM TESTTEST WHERE ID=10-- not going to DELETE
SELECT 'FINISHED'
SELECT * FROM TESTTEST
DROP TABLE TESTTEST
First off, your trigger as you already see is going to update every record in the table. There is no filtering done to accomplish jus the rows changed.
Secondly, you're assuming that only one row changes in the batch which is incorrect as multiple rows could change.
The way to do this properly is to use the virtual inserted and deleted tables: http://msdn.microsoft.com/en-us/library/ms191300.aspx
Trigger
special kind of stored procedure
automatically execured/fired when some event Insert/Update/Delete Occures
use when we want some event to happen automatically on certain desirable scenarios
triggers makes use of 2 tables inserted/deleted table in ssms(memory)
ONLY availabe in context of trigger(CANNOT ACCESS Outside the Trigger
when we insert/delete using trigger, a copy of row is maintained in the inserted/deleted table
inserted table - contains updated data |
deleted table - contains old data
Trigger to Update "ModifiedOn" Date Automatically when record in table is modified(UPDATED)
CREATE TRIGGER [dbo].[Trg_TableName_UpdateModifiedOn]
ON [dbo].[TableName]
AFTER UPDATE
AS
BEGIN
UPDATE [dbo].[TableName]
SET [ModifiedOn] = GetDate()
FROM [inserted]
WHERE [inserted].[PrimaryKey] = [dbo].[TableName].[PrimaryKey];
END;
CREATE TRIGGER [dbo].[after_update] ON [dbo].[MYTABLE]
AFTER UPDATE
AS
BEGIN
DECLARE #ID INT
SELECT #ID = D.ID
FROM inserted D
UPDATE MYTABLE
SET mytable.CHANGED_ON = GETDATE()
,CHANGED_BY = USER_NAME(USER_ID())
WHERE ID = #ID
END
I have a very complicated table I'd like to take a temporary backup of whilst I make some changes. Normally, I'd just do the following:
SELECT *
INTO temp_User
FROM dbo.[User] AS u
Unfortunately I'm using Azure, and it appears this isn't supported:
Msg 40510, Level 16, State 1, Line 2 Statement 'SELECT INTO' is not
supported in this version of SQL Server.
Is there a way to re-create this feature into a function, potentially? I could do this by scripting the table, creating it and then inserting data using a select statement but given how frequently I use Azure, and how many databases I need to work on in this area this is very unwieldy.
Azure requires a clustered index on all tables, therefore SELECT INTO is not supported.
You'll have to:
CREATE TABLE temp_User () --fill in table structure
INSERT INTO temp_User
SELECT *
FROM dbo.[User]
To script table easily you can write your own or use one of the answers to this question:
Script CREATE Table SQL Server
Update: As Jordan B pointed out, V12 will include support for heaps (no clustered index requirement) which means SELECT INTO will work. At the moment V12 Preview is available, Microsoft of course only recommends upgrading with test databases.
The new Azure DB Update preview has this problem resolved:
The V12 preview enables you to create a table that has no clustered
index. This feature is especially helpful for its support of the T-SQL
SELECT...INTO statement which creates a table from a query result.
http://azure.microsoft.com/en-us/documentation/articles/sql-database-preview-whats-new/
Unfortunately it cant be done. Here is how I worked around it:
Open SQL Server Management Studio
Right click on the table
Select Script as ... Create Table
Edit the generated script to change the table name to what you specified in your query
Execute your query
INSERT INTO temp_User
SELECT * FROM dbo.[User]
You can try the above. It's basically a select that is applied to an insert statement
http://blog.sqlauthority.com/2011/08/10/sql-server-use-insert-into-select-instead-of-cursor/
Lets assume you have a table with Id, Column1 and Column2. Then this could be your solution
CREATE TABLE YourTableName_TMP ....
GO
SET IDENTITY_INSERT YourTableName_TMP ON
GO
INSERT INTO YourTableName_TMP
([Id] ,[Column1] ,[Column2])
SELECT [Id] ,[Column1] ,[Column2]
FROM
(
SELECT *
FROM
(
SELECT [Id] ,[Column1] ,[Column2] ROW_NUMBER() OVER(ORDER BY ID DESC) AS RowNum
FROM YourTableName
)
WHERE RowNum BETWEEN 0 AND 500000
)
GO
SET IDENTITY_INSERT YourTableName_TMP OFF
GO
First you create a temporary table and then you insert rows windowed. It's a mess, I know. My experiences are, that executing this using SQL Server Management Studio from a client makes approximately 200.000 rows a minute.
As wrote above - you need to rewrite your query from using select into to create table like
It is my sample. Was :
select emrID, displayName --select into
into #tTable
from emrs
declare #emrid int
declare #counter int = 1
declare #displayName nvarchar(max)
while exists (select * from #tTable)
begin
-- some business logic
select top 1 #displayName = displayname
from #tTable
group by displayname
update emrs set groupId = #counter where #displayName = displayname
delete #tTable
where #displayName = displayname
set #counter = #counter + 1
end
drop table #tTable
Modified :
CREATE TABLE #tTable ([displayName] nvarchar(max)) --create table
INSERT INTO #tTable -- insert to next select :
select displayName
from emrs
declare #emrid int
declare #counter int = 1
declare #displayName nvarchar(max)
while exists (select * from #tTable)
begin
-- some business logic
select top 1 #displayName = t.displayName
from #tTable as t
group by t.displayname
update emrs set groupId = #counter where #displayName = displayname
delete #tTable
where #displayName = displayname
set #counter = #counter + 1
end
drop table #tTable
Do not forget to drop your temp table.
Also, you can find more simple example with description here :
http://www.dnnsoftware.com/wiki/statement-select-into-is-not-supported-in-this-version-of-sql-server
I have a table like this
CREATE TABLE #tmp(ColSelect NVARCHAR(400),ColParValues XML)
that ColSelect contains SQL Select Statement and ColParValues contains some xml data for parameter value in ColSelect
for example ColSelectcontains:
"SELECT [$12]+19/[$16]-[$54]"
and col 2 contains name value pair that refer to ColSelect parameters
How can I update my table that replace each parameter with relevant value from ColParValues. I use this statement:
update #tmp
SET
ColSelect=REPLACE(ColSelect,c.value('#Value','nvarchar(10)'),c.value('#Res','DECIMAL(24,12)'))
FROM #tmp t1
CROSS APPLY t1.ColParValues.nodes('/root/r') AS n(c)
but this statement replace just one parameter value in each row.
And this is sample data link
This is one solution :
create table #tmp (colselect varchar(200),colparvalues xml)
insert into #tmp (colselect,colparvalues)
values ('select case when [$71]+[$29]+10<25 then 1 else 0 end'
,'<root><r Value="[$71]" Res="1"/><r Value="[$29]" Res="5"/></root>'),
('select case when [$95]*[$29]+10<25 then 1 else 0 end'
,'<root><r Value="[$95]" Res="3"/><r Value="[$29]" Res="5"/></root>')
WHILE ##ROWCOUNT >0
update #tmp
SET
ColSelect=REPLACE(ColSelect,c.value('#Value','nvarchar(10)'),c.value('#Res','DECIMAL(24,12)'))
FROM #tmp t1
CROSS APPLY t1.ColParValues.nodes('/root/r') AS n(c)
WHERE t1.colselect LIKE'%'+replace(c.value('#Value','nvarchar(10)'),'[','')+'%'
select * from #tmp
drop table #tmp
However, very much similar to cursor in performance. Check the performance. Use if ok.
i want use select statement on the a table and inserting result into a temp table variable, but i don't declare temp table with columns and i want use like this:
Declare #tmp table;
SELECT * INTO #tmp FROM myTable
this want declare columns and data types for #tmp
please help me
You can do this simply without the DECLARE command - which is not valid for #temp tables anyway, only #table variables. Did you try just the following without trying to define #tmp first:
SELECT * INTO #tmp FROM myTable;
With data:
select *
into #tmp
from myTable
No data:
select *
into #tmp
from myTable
where 0=1
BTW, you can not do this with table variables.
select *
into #tmp
from myTable
Table variables need to be declared with the columns.
I am trying the following query
if exists (select 1 from emp where eid = 6)
begin
if object_id('tempdb..#columntable') is not null
begin
drop table #columntable
end
create table #columntable (oldcolumns varchar(100))
end
else
begin
if object_id('tempdb..#columntable') is not null
begin
drop table #columntable
end
create table #columntable (newcolumns varchar(100))
end
But I am getting the error
Msg 2714, Level 16, State 1, Line 8
There is already an object named '#columntable' in the database.
Can anyone suggest why? The same query works fine if I do not write the else part.
This is a SQL Server parser error unfortunately (confirmed by Microsoft).
#DizGrizz is also right - SELECT .. INTO #SomeTable doesn't work if repeated in IF .. ELSE statements.
IF .. ELSE .. CREATE TABLE #SomeTempTable
In answer to the actual question, creating then altering the table works (you also only have to check and drop once)...
IF OBJECT_ID('tempdb..#MyTempTable') IS NOT NULL
BEGIN
DROP TABLE #MyTempTable
END
CREATE TABLE #MyTempTable (DummyColumn BIT)
IF EXISTS (SELECT 1 FROM EMP WHERE EID = 6)
BEGIN
ALTER TABLE #MyTempTable
ADD MyColumnType1 VARCHAR(100)
ALTER TABLE #MyTempTable
DROP COLUMN DummyColumn
END
ELSE
BEGIN
ALTER TABLE #MyTempTable
ADD MyColumnType2 VARCHAR(100)
ALTER TABLE #MyTempTable
DROP COLUMN DummyColumn
END
IF .. ELSE .. SELECT INTO #SomeTempTable
The issue I had however was the same as #DizGrizz: IF .. ELSE combined with SELECT .. INTO #SomeTable fails. As a workaround it's possible to select the top 0 rows (i.e. none) to create the table with the correct column types. (This insulates the script from column type changes and also avoids the pain of declaring every type.) INSERT INTO can then be used, provided IDENTITY_INSERT is set to ON to prevent errors:
IF OBJECT_ID('tempdb..#MyTempTable') IS NOT NULL
DROP TABLE #MyTempTable
-- This creates the table, but avoids having to declare any column types or sizes
SELECT TOP 0 KeyNm
INTO #MyTempTable
FROM dbo.MyDataTable2
-- Required to prevent IDENTITY_INSERT error
SET IDENTITY_INSERT #MyTempTable ON
IF #something = 1
BEGIN
-- Insert the actual rows required into the (currently empty) temp table
INSERT INTO #MyTempTable (KeyNm)
SELECT KeyNm
FROM dbo.MyDataTable2
WHERE CatNum = 2
END
ELSE
BEGIN
-- Insert the actual rows required into the temp table
INSERT INTO #MyTempTable (KeyNm)
SELECT KeyNm
FROM dbo.MyDataTable2
WHERE CatNum = 8
END
SET IDENTITY_INSERT #MyTempTable OFF
Temp tables are not dropped automatically at the end of a query, only when the current connection to the DB is dropped or you explicitly delete them with DROP TABLE #columntable
Either test for the existence of the table at the start of the query or alwayas delete it at the end (preferably both)
EDIT: As Matrin said in his comment, this is actually a parse error. You get the same error if you only parse the SQL as when you execute it.
To test that out I split up your query and tried:
if exists (select 1 from emp where id = 6)
create table #columntable (newcolumns varchar(100))
GO
if not exists (select 1 from emp where id = 6)
create table #columntable (oldcolumns varchar(100))
GO
The parser is happy with that. Interestingly if you change to using non-temp tables the original query parses fine (I realise the problems that would create, I was just interested to find out why the query would not parse).
This also occurs if you create the tables with SELECT INTO...as in
IF OBJECT_ID('tempdb..#MyTempTable', 'U') IS NOT NULL
DROP TABLE #MyTempTable
SELECT TOP 1 #MyVariable = ScaleValue
FROM MyDataTable1
WHERE ProductWeight > 1000
IF #MyVariable = 1
BEGIN
SELECT KeyNm
INTO #MyTempTable
FROM dbo.MyDataTable2
WHERE CatNum = 2
END
ELSE
BEGIN
SELECT KeyNm
INTO #MyTempTable
FROM dbo.MyDataTable2
WHERE CatNum = 8
END
The parser should not even attempt to detect this because, in many cases, it would be impossible for the parser to determine if the table would already exist. The code above is a perfect example...there would be no way for the parser to determine the value of #MyVariable.
I hope that someone has informed MS of this bug (I don't have their ear).
You can check if it exists by doing:
IF OBJECT_ID('tempdb..#columntable') IS NOT NULL
BEGIN
DROP TABLE #columntable
PRINT 'Dropped table...'
END
Use global temp tables and wrap the select in exec.
Example:
Fail
declare #a int = 1
if object_id('tempdb..##temp') is not null drop table ##temp
if(#a = 1) select * into ##temp from table_1
else if(#a = 2) select * into ##temp from table_2
Win
declare #a int = 1
if object_id('tempdb..##temp') is not null drop table ##temp
if(#a = 1) exec('select * into ##temp from table_1')
else if(#a = 2) exec('select * into ##temp from table_2')
This will fool the buggy parser that is trying to be smarter than it is.
And Microsoft - please fix this.
The error is wrong, remove the if clause and it runs through fine. Thus the problem is in the exists:
if object_id('tempdb..#columntable') is not null
begin
drop table #columntable
end
create table #columntable (oldcolumns varchar(100))
Well I got the answer...
As Martin said this is a parse/compile issue. So I Tried changing my script as below
if exists (select 1 from emp where eid = 6)
begin
if object_id('tempdb..#columntable') is not null
begin
drop table #columntable
end
create table #columntable (oldcolumns varchar(100))
end
go
if exists (select 1 from emp where eid = 1)
begin
if object_id('tempdb..#columntable') is not null
begin
drop table #columntable
end
create table #columntable (newcolumns varchar(100))
end
And this worked for me.
I have been experiencing this issue. My query consisted of several joined SELECT statements in the form of:
DROP TABLE IF EXISTS ##TempTableName
SELECT statement ...
So, every time I tried to alter SQL code I would get the above error. I have changed all my temp tables from ##global to #local and now I am able to alter my SQL as many times as needed. So the example above would become:
DROP TABLE IF EXISTS #TempTableName
SELECT statement ...