I have a database table from which same data under a certain condition are lost at a specific time daily as if such statement is performed:
delete * from table where category=1
I'd like to list all delete actions on this table through a SQL script to know how records are deleted exactly and by which statement, user and time of deletion.
Does anyone have such script? Or did anyone have similar case and can advise?
The SQL version is Server 2008 Enterprise Edition.
If this is just a short-term debugging issue, the easiest way to address this is probably to run SQL Server Profiler, with filters set to capture the data you're interested in. No code changes that way.
For best performance, try to run SQL Profiler on a machine other than the DB server, if you can.
Use AFTER DELETE trigger on the table to log deletions in another table with user and time it was performed.
Using some advanced tricks you can extract the query text which deleted the rows, but I'm not sure that it is possible inside a trigger.
The trigger might look like this
CREATE TABLE YourLogTable
(
ID int identity primary key,
Date datetime NOT NULL DEFAULT GETDATE(),
[User] nvarchar(128) NOT NULL DEFAULT suser_sname(),
[SqlText] NVARCHAR(MAX),
[any other interesting columns from deleted rows]
)
GO
CREATE TRIGGER [TR.AD#YourTable]
ON YourTable
AFTER DELETE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sqlText NVARCHAR(MAX)
SELECT #sqlText = txt.Text
FROM sys.dm_exec_connections c
CROSS APPLY sys.dm_exec_sql_text(c.most_recent_sql_handle) txt
WHERE session_id = ##SPID
INSERT YourLogTable([SqlText], [any other interesting columns from deleted rows])
SELECT #SqlText, [any other interesting columns from deleted rows]
FROM DELETED
END
Related
sql server 2008
May anyone share their expertise how to achieve the below scenario
I have two table. active and history both have same structure.
What I am trying to achieve, whenever new record insert to active table, whatever
records existing in active table move to history.
I am trying to hold only current active records
I have a column called fetch date and if any record have fetch date not same
as getdate must be moved to history table. those record should not present
in Active table.
Thanks
you have several syntax error there.
I am merely correcting the syntax
CREATE PROCEDURE BACKUP_TB
#tbname varchar(MAX)
AS
BEGIN
SET nocount ON;
DECLARE #query varchar(MAX)
SET #query = 'select * into '+ #tbname + '_backup from ' + #tbname + '';
EXEC (#query)
SET nocount OFF;
END
few issues you should take note
SELECT INTO <table name> will create new table and insert the result. It will only works once. Then second time you run it, it will give error as the target table already existed. You should use pre-create all necessary tables and use insert into backup_table ( <column list> ) select <column list> from original_table
Avoid using SELECT *, it is best practice to specify the column name explicitly
If you have to use Dynamic SQL, do read up more about it. Example : The Curse and Blessings of Dynamic SQL. And take note of possible SQL Injection attack.
I have database on Sql Server 2008 R2.
On that database a delete query on 400 Million records, has been running for 4 days , but I need to reboot the machine. How can I force it to commit whatever is deleted so far? I want to reject that data which is deleted by running query so far.
But problem is that query is still running and will not complete before the server reboot.
Note : I have not set any isolation / begin/end transaction for the query. The query is running in SSMS studio.
If machine reboot or I cancelled the query, then database will go in recovery mode and it will recovering for next 2 days, then I need to re-run the delete and it will cost me another 4 days.
I really appreciate any suggestion / help or guidance in this.
I am novice user of sql server.
Thanks in Advance
Regards
There is no way to stop SQL Server from trying to bring the database into a transactionally consistent state. Every single statement is implicitly a transaction itself (if not part of an outer transaction) and is executing either all or nothing. So if you either cancel the query or disconnect or reboot the server, SQL Server will from transaction log write the original values back to the updated data pages.
Next time when you delete so many rows at once, don't do it at once. Divide the job in smaller chunks (I always use 5.000 as a magic number, meaning I delete 5000 rows at the time in the loop) to minimize transaction log use and locking.
set rowcount 5000
delete table
while ##rowcount = 5000
delete table
set rowcount 0
If you are deleting that many rows you may have a better time with truncate. Truncate deletes all rows from the table very efficiently. However, I'm assuming that you would like to keep some of the records in the table. The stored procedure below backs up the data you would like to keep into a temp table then truncates then re-inserts the records that were saved. This can clean a huge table very quickly.
Note that truncate doesn't play well with Foreign Key constraints so you may need to drop those then recreate them after cleaned.
CREATE PROCEDURE [dbo].[deleteTableFast] (
#TableName VARCHAR(100),
#WhereClause varchar(1000))
AS
BEGIN
-- input:
-- table name: is the table to use
-- where clause: is the where clause of the records to KEEP
declare #tempTableName varchar(100);
set #tempTableName = #tableName+'_temp_to_truncate';
-- error checking
if exists (SELECT [Table_Name] FROM Information_Schema.COLUMNS WHERE [TABLE_NAME] =(#tempTableName)) begin
print 'ERROR: already temp table ... exiting'
return
end
if not exists (SELECT [Table_Name] FROM Information_Schema.COLUMNS WHERE [TABLE_NAME] =(#TableName)) begin
print 'ERROR: table does not exist ... exiting'
return
end
-- save wanted records via a temp table to be able to truncate
exec ('select * into '+#tempTableName+' from '+#TableName+' WHERE '+#WhereClause);
exec ('truncate table '+#TableName);
exec ('insert into '+#TableName+' select * from '+#tempTableName);
exec ('drop table '+#tempTableName);
end
GO
You must know D(Durability) in ACID first before you understand why database goes to Recovery mode.
Generally speaking, you should avoid long running SQL if possible. Long running SQL means more lock time on resource, larger transaction log and huge rollback time when it fails.
Consider divided your task some id or time. For example, you want to insert large volume data from TableSrc to TableTarget, you can write query like
DECLARE #BATCHCOUNT INT = 1000;
DECLARE #Id INT = 0;
DECLARE #Max = ...;
WHILE Id < #Max
BEGIN
INSERT INTO TableTarget
FROM TableSrc
WHERE PrimaryKey >= #Id AND #PrimaryKey < #Id + #BatchCount;
SET #Id = #Id + #BatchCount;
END
It's ugly more code and more error prone. But it's the only way I know to deal with huge data volume.
I have a database that needs from time to time an update.
It may also happens that there are new data while the update runs.
In MySQL there is a option
INSERT INTO IGNORE
I can't find something like this in T-SQL.
No Problem to update ID 1-4 but then there is a new record for ID 5.
The UPDATE query don't work here.
And when I try to INSERT all data again I get a DUPLICATE KEY error.
Additional Infos:
I've forgotten to say that my data come from external sources. I call an API to get data from it. From there I have to insert these data into my database.
I have to admit that I don't understand MERGE. So my solution for now is to use TRUNCATE first and then insert all data again.
Not the best solution but MERGE works, so far I understand it, with two tables. But I have only one table. And to create a table temporarly to use MERGE and later drop that table is in my eyes a bit to much for my little table with 200 records in it.
You can use MERGE keyword. Basically, you need to specify the column(s) on which to join the source of data with target table, and depending on whether it is matching (existing record) or not matching (new record), you run an UPDATE or INSERT.
Reference: http://msdn.microsoft.com/en-us/library/bb510625.aspx
Is a stored procedure an option?
CREATE PROCEDURE dbo.Testing (#ID int, #Field1 varchar(20))
AS
BEGIN
UPDATE tblTesting
SET Field1 = #Field1
WHERE ID = #ID
IF ##ROWCOUNT = 0
INSERT INTO tblTesting (ID, Field1) SELECT #ID, #Field1
END
I created computer audit application. When I ran my application, it shows computer accessories on browser like computerName, osVersion, lastAudit, model, totalMemory, processor, userName.
I have created a database in SQL Server 2008 with one table Computers. When a value is inserted into that table, I need to update the table value in the column. In an attempt to try this, I'm using a trigger. However, I do not fully understand how triggers work.
Can someone please show me how to accomplish this.
My table has these columns:
id, computerName, osVersion, lastAudit, model, totalMemory, processor, userName
I know that in this code something wrong or missing but I am not able to complete this. Please help me in this regard.
CREATE TRIGGER update_trigger
ON computers
AFTER UPDATE
AS
BEGIN
declare #id as int
declare #computerName as varchar(100)
declare #osVersion as varchar(100)
declare #lastAudit as datetime
declare #model as varchar(100)
declare #totalMemory float
declare #processor as varchar(100)
declare #userName as varchar(100)
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
if update(id)
BEGIN
insert into computers values(#id,#computerName,#osVersion,#lastAudit,#model,
#totalMemory,#processor,#userName,'Update')
SET NOCOUNT ON;
END
GO
If you want to simply update one or more columns of your existing table when new rows are being inserted (or when they are updated? Not quite clear...), try a trigger like this:
CREATE TRIGGER trigUpdateTable
ON dbo.Computers
AFTER INSERT -- or AFTER UPATE or AFTER INSERT, UPDATE
AS
BEGIN
-- do whatever you want to do on INSERT and/or UPDATE
UPDATE
dbo.Computers
SET
LastAudit = GETDATE()
FROM
dbo.Computers c
INNER JOIN
Inserted i ON c.id = i.id
One very important point to remember: SQL Server triggers are not called per row that is affected - but per statement, and if your INSERT or UPDATE statement affects multiple rows, you'll have multiple entries in the Inserted pseudo table and you need to be able to deal with that fact in your trigger
Brief history:
I'm writing a stored procedure to support a legacy reporting system (using SQL Server Reporting Services 2000) on a legacy web application.
In keeping with the original implementation style, each report has a dedicated stored procedure in the database that performs all the querying necessary to return a "final" dataset that can be rendered simply by the report server.
Due to the business requirements of this report, the returned dataset has an unknown number of columns (it depends on the user who executes the report, but may have 4-30 columns).
Throughout the stored procedure, I keep a column UserID to track the user's ID to perform additional querying. At the end, however, I do something like this:
UPDATE #result
SET Name = ppl.LastName + ', ' + ppl.FirstName
FROM #result r
LEFT JOIN Users u ON u.id = r.userID
LEFT JOIN People ppl ON ppl.id = u.PersonID
ALTER TABLE #result
DROP COLUMN [UserID]
SELECT * FROM #result r ORDER BY Name
Effectively I set the Name varchar column (that was previously left NULL while I was performing some pivot logic) to the desired name format in plain text.
When finished, I want to drop the UserID column as the report user shouldn't see this.
Finally, the data set returned has one column for the username, and an arbitrary number of INT columns with performance totals. For this reason, I can't simply exclude the UserID column since SQL doesn't support "SELECT * EXCEPT [UserID]" or the like.
With this known (any style pointers are appreciated but not central to this problem), here's the problem:
When I execute this stored procedure, I get an execution error:
Invalid column name 'userID'.
However, if I comment out my DROP COLUMN statement and retain the UserID, the stored procedure performs correctly.
What's going on? It certainly looks like the statements are executing out of order and it's dropping the column before I can use it to set the name strings!
[Edit 1]
I defined UserID previously (the whole stored procedure is about 200 lies of mostly irrelevant logic, so I'll paste snippets:
CREATE TABLE #result ([Name] NVARCHAR(256), [UserID] INT);
Case sensitivity isn't the problem but did point me to the right line - there was one place in which I had userID instead of UserID. Now that I fixed the case, the error message complains about UserID.
My "broken" stored procedure also works properly in SQL Server 2008 - this is either a 2000 bug or I'm severely misunderstanding how SQL Server used to work.
Thanks everyone for chiming in!
For anyone searching this in the future, I've added an extremely crude workaround to be 2000-compatible until we update our production version:
DECLARE #workaroundTableName NVARCHAR(256), #workaroundQuery NVARCHAR(2000)
SET #workaroundQuery = 'SELECT [Name]';
DECLARE cur_workaround CURSOR FOR
SELECT COLUMN_NAME FROM [tempdb].INFORMATION_SCHEMA.Columns WHERE TABLE_NAME LIKE '#result%' AND COLUMN_NAME <> 'UserID'
OPEN cur_workaround;
FETCH NEXT FROM cur_workaround INTO #workaroundTableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #workaroundQuery = #workaroundQuery + ',[' + #workaroundTableName + ']'
FETCH NEXT FROM cur_workaround INTO #workaroundTableName
END
CLOSE cur_workaround;
DEALLOCATE cur_workaround;
SET #workaroundQuery = #workaroundQuery + ' FROM #result ORDER BY Name ASC'
EXEC(#workaroundQuery);
Thanks everyone!
A much easier solution would be to not drop the column, but don't return it in the final select.
There are all sorts of reasons why you shouldn't be returning select * from your procedure anyway.
EDIT: I see now that you have to do it this way because of an unknown number of columns.
Based on the error message, is the database case sensitive, and so there's a difference between userID and UserID?
This works for me:
CREATE TABLE #temp_t
(
myInt int,
myUser varchar(100)
)
INSERT INTO #temp_t(myInt, myUser) VALUES(1, 'Jon1')
INSERT INTO #temp_t(myInt, myUser) VALUES(2, 'Jon2')
INSERT INTO #temp_t(myInt, myUser) VALUES(3, 'Jon3')
INSERT INTO #temp_t(myInt, myUser) VALUES(4, 'Jon4')
ALTER TABLE #temp_t
DROP Column myUser
SELECT * FROM #temp_t
DROP TABLE #temp_t
It says invalid column for you. Did you check the spelling and ensure there even exists that column in your temp table.
You might try wrapping everything preceding the DROP COLUMN in a BEGIN...COMMIT transaction.
At compile time, SQL Server is probably expanding the * into the full list of columns. Thus, at run time, SQL Server executes "SELECT UserID, Name, LastName, FirstName, ..." instead of "SELECT *". Dynamically assembling the final SELECT into a string and then EXECing it at the end of the stored procedure may be the way to go.