Update trigger working but unable to do Insert - sql-server

I have created a trigger which handles updates, and works well. But I am battling to work out how to handle Inserts.
This is my current trigger:
CREATE TRIGGER tr_PersonInCareSupportNeeds_History
ON PersonInCareSupportNeeds
FOR UPDATE
AS
BEGIN
INSERT INTO [dbo].[PersonInCareSupportNeeds_History]
([PersonInCareSupportNeedsID], [EventDate], [EventUser], [ChangedColumn], [PreviousValue], [NewValue])
SELECT i.[PersonInCareSupportNeedsID], GETDATE(), i.[LastUpdateUser], 'StartDate', CAST(d.[StartDate] AS VARCHAR), CAST(i.[StartDate] AS VARCHAR)
FROM PersonInCareSupportNeeds I INNER JOIN Deleted D
ON d.PersonInCareSupportNeedsID = I.PersonInCareSupportNeedsID
WHERE d.[StartDate] <> i.[StartDate]
UNION
-- new values
SELECT i.[PersonInCareSupportNeedsID], GETDATE(), i.[LastUpdateUser], 'EndDate', CAST(d.[EndDate] AS VARCHAR), CAST(i.[EndDate] AS VARCHAR)
FROM PersonInCareSupportNeeds I INNER JOIN DELETED D
ON d.PersonInCareSupportNeedsID = I.PersonInCareSupportNeedsID
WHERE d.[EndDate] <> i.[EndDate]
END
How can I change this to handle INSERTS as well. I will also hadd a column to handle the action type 'Updated' or 'Inserted'.

Funny, but I just happened to be playing with this:
create trigger dbo.Things_Log on dbo.Things after Delete, Insert, Update as
declare #Now as DateTimeOffset = SysDateTimeOffset();
-- Determine the action that fired the trigger.
declare #Action VarChar(6) =
case
when exists ( select 42 from inserted ) and exists ( select 42 from deleted ) then 'update'
when exists ( select 42 from inserted ) then 'insert'
when exists ( select 42 from deleted ) then 'delete'
else NULL end;
if #Action is NULL
return;
-- Assign a unique value to group the log rows for this trigger firing.
declare #TriggerId as Int;
update TriggerIds
set #TriggerId = TriggerId += 1;
-- Log the data.
if #Action in ( 'delete', 'update' )
insert into ThingsLog
select #Action + '-deleted', #TriggerId, #Now, dbo.OriginalLoginName(), ThingId, ThingName
from deleted;
if #Action in ( 'insert', 'update' )
insert into ThingsLog
select #Action + '-inserted', #TriggerId, #Now, dbo.OriginalLoginName(), ThingId, ThingName
from inserted;
go
-- Logging triggers should always fire last.
execute sp_settriggerorder #triggername = 'dbo.Things_Log', #order = 'Last', #stmttype = 'DELETE';
execute sp_settriggerorder #triggername = 'dbo.Things_Log', #order = 'Last', #stmttype = 'INSERT';
execute sp_settriggerorder #triggername = 'dbo.Things_Log', #order = 'Last', #stmttype = 'UPDATE';
go
The context:
create function [dbo].[OriginalLoginName]()
returns NVarChar(128)
as
begin
-- Returns the original login used to create the current session: Domain\username or sqlusername.
-- This function is not affected by impersonation.
-- Requires granting execute access to [public] and represents a diminutive security hole.
declare #Result as NVarChar(128);
select #Result = original_login_name
from sys.dm_exec_sessions
where session_id = ##SPID;
return #Result;
end;
go
CREATE TABLE [dbo].[Things](
[ThingId] [int] IDENTITY(1,1) NOT NULL,
[ThingName] [varchar](16) NOT NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[ThingsLog](
[ThingsLogId] [int] IDENTITY(1,1) NOT NULL,
[Action] [varchar](16) NOT NULL,
[TriggerId] [int] NOT NULL,
[TriggerTime] [datetimeoffset](7) NOT NULL,
[OriginalLoginName] [nvarchar](128) NOT NULL,
[ThingId] [int] NOT NULL,
[ThingName] [varchar](16) NOT NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[TriggerIds](
[TriggerId] [int] NULL
) ON [PRIMARY]
GO
insert into dbo.TriggerIds ( TriggerId ) values ( 0 );
Logging triggers should be configured to fire last. This prevents logging actions that may be subsequently rolled back by other triggers. For bonus points, a query that can report logging triggers that are not configured to fire last (assuming you have a consistent naming convention, e.g. TableName_Log, for the triggers):
select PO.name as TableName, O.name as TriggerName, TE.type_desc,
case when O.name like PO.name + '_Log%' then 1 else 0 end as LoggingTrigger,
case when O.name like PO.name + '_Log%' and TE.is_last = 0 then 1 else 0 end as Misconfigured,
'' as [-], PO.type_desc as TableType, T.is_disabled, TE.is_first, TE.is_last, T.is_instead_of_trigger
from sys.objects as O inner join
sys.triggers as T on T.object_id = O.object_id inner join
sys.objects as PO on PO.object_id = T.parent_id inner join
sys.trigger_events as TE on TE.object_id = T.object_id
where
PO.type = 'U' and -- User table.
T.parent_class = 1 and -- Object or column trigger.
T.is_disabled = 0 and -- Is not disabled.
T.is_instead_of_trigger = 0 -- AFTER, not INSTEAD OF, trigger.
order by PO.name, O.name, TE.type_desc;
It can be incorporated in a stored procedure that corrects the firing order of logging triggers.

Related

How to insert data into a temporary table using an existing table and new columns

I am trying to insert data into a temporary table within my stored procedure. The data is selected from an existing table and creating new columns with concatenated data. I'm getting an error that the column name or number of supplied values does not match table definition. I'm pretty certain that the code in my application is correct so I believe the issue is with the way I'm storing the data in a temporary table.
Here is my proc:
AS
BEGIN
CREATE TABLE #TempTable
(
[ID] [varchar](10),
[FIRST_NAME] varchar(50),
[LAST_NAME] varchar(50),
[WEBSITE_LINK] varchar(200)
)
INSERT INTO #TempTable
SELECT USER.ID,USER.FIRSTNAME AS [FIRST_NAME], USER.LASTNAME AS
[LAST_NAME]
FROM USER
WHERE USER.Registered = 'Yes'
DECLARE #Link1 NVARCHAR(100)
DECLARE #Link2 VARCHAR(10)
DECLARE #Link3 NVARCHAR(4)
SET #Link1 = 'http://www.mywebsite.com/user/'
SET #Link2 = (SELECT USER.ID FROM USER WHERE USER.Registered =
'Yes')
SET #Link3 ='/document.doc'
SET #WEBSITE_LINK = (SELECT concat(#Link1,#Link2,#Link3 )AS
[WEBSITE_LINK])
DROP TABLE #TempTable
END
I think this is your problem:
SET #Link2 = (SELECT USER.ID FROM USER WHERE USER.Registered = 'Yes')
What if there are six of them? A single variable can't hold all of them. You can do:
SELECT TOP(1) #Link2 = USER.ID FROM USER WHERE USER.Registered = 'Yes' ORDER BY [SOMETHING];
If the goal is to create a temp table with a full [WEBSITE_LINK], you can do that without all those variables:
BEGIN
CREATE TABLE #TempTable
(
[ID] [varchar](10),
[FIRST_NAME] varchar(50),
[LAST_NAME] varchar(50),
[WEBSITE_LINK] varchar(200)
)
INSERT INTO #TempTable
SELECT DISTINCT u.ID
, [FIRST_NAME] = u.FIRSTNAME
, [LAST_NAME] = u.LASTNAME
, [WEBSITE_LINK] = 'http://www.mywebsite.com/user/' +
CAST(u.ID AS VARCHAR(10)) +
'/document.doc'
FROM [USER] u
WHERE u.Registered = 'Yes'
-- Do something with these values...
DROP TABLE #TempTable
END

sql trigger - restrict by time

I want this trigger to work only between a certain time and another time (say 6am-10pm). please help!
ALTER TRIGGER [db].[el] ON [Reports].[db].[stat]
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
INSERT INTO [Reports].[db].[el]
(
[StationID]
,[Count]
)
SELECT i.StationID,
i.[EmptyDockCount],
GETDATE(),
NULL,
NULL,
i.[LastUpdateDate],
FROM INSERTED i
INNER JOIN DELETED d
on d.StationID = i.StationID
INNER JOIN DBOS.dbo.StationDim bsd
ON bsd.StationID = i.StationID
WHERE i.[Count] = 0
AND d.[count] <> 0
;
Try like this,
This is the key statement CONVERT(TIME, Getdate()) BETWEEN '6:00:00.0000000' AND '22:00:00.0000000'
ALTER TRIGGER [db].[el]
ON [Reports].[db].[stat]
AFTER INSERT, UPDATE
AS
SET NOCOUNT ON;
IF CONVERT(TIME, Getdate()) BETWEEN '6:00:00.0000000' AND '22:00:00.0000000'
BEGIN
INSERT INTO [Reports].[db].[el]
([StationID],
[Count])
SELECT i.StationID,
i.[EmptyDockCount],
Getdate(),
NULL,
NULL,
i.[LastUpdateDate]
FROM INSERTED i
INNER JOIN DELETED d
ON d.StationID = i.StationID
INNER JOIN DBOS.dbo.StationDim bsd
ON bsd.StationID = i.StationID
WHERE i.[Count] = 0
AND d.[count] <> 0
END;

Sql Server trigger triggers with empty inserted and deleted tables

I have defined a trigger on a table that is triggered
AFTER INSERT, DELETE, UPDATE
There are cases where the trigger fires, with both INSERTED AND DELETED tables being empty. How can this be possible?
For the records, that's the trigger
CREATE TRIGGER [dbo].[AuditUsersTrigger] ON [dbo].[Users]
AFTER INSERT, DELETE, UPDATE
AS
BEGIN
SET NOCOUNT ON
DECLARE #type nchar(1), #hasChanges bit
SET #hasChanges = 1
IF EXISTS (SELECT * FROM INSERTED)
IF EXISTS (SELECT * FROM DELETED)
BEGIN
SELECT #type = 'U'
IF EXISTS (
SELECT *
FROM INSERTED i
INNER JOIN DELETED d ON
i.Name = d.Name AND
i.Pwd = d.Pwd AND
...
) SELECT #hasChanges = 0
END
ELSE
SELECT #type = 'I'
ELSE
SELECT #type = 'D'
IF #type = 'D' OR (#type = 'U' AND #hasChanges = 1)
BEGIN
INSERT AuditUsers (
New, Id, Name, ...
)
SELECT
0, Id, Name, ...
FROM DELETED
IF #type = 'D'
BEGIN
INSERT AuditUsers (New)
SELECT 1
END
END
IF #type = 'I' OR (#type = 'U' AND #hasChanges = 1)
BEGIN
IF #type = 'I'
BEGIN
INSERT AuditUsers (New)
SELECT 0
END
INSERT AuditUsers (
New, Id, Name, ...
)
SELECT
0, Id, Name, ...
FROM INSERTED
END
IF Trigger_Nestlevel() < 2
BEGIN
DECLARE #clientId TABLE (id INT)
DECLARE #clientCode NVARCHAR(50), #shopId INT;
IF #type = 'I' OR #type = 'U'
BEGIN
SELECT #clientCode = ClientCode, #shopId = ShopId FROM INSERTED;
INSERT INTO #clientId SELECT id FROM Clients WHERE code = #clientCode;
IF NOT EXISTS (SELECT 1 FROM #clientId)
BEGIN
INSERT Clients (name, code, active, shopId) OUTPUT INSERTED.id INTO #clientId
VALUES (#clientCode, #clientCode, 1, #shopId);
END
UPDATE Users SET ClientId = (SELECT TOP 1 id FROM #clientId) WHERE ClientCode = #clientCode;
END
END
END
This is documented behaviour
DML triggers execute when a user tries to modify data through a data manipulation language (DML) event. DML events are INSERT, UPDATE, or DELETE statements on a table or view. These triggers fire when any valid event is fired, regardless of whether or not any table rows are affected.
If you have a recurring loop, whereby table A has a trigger that affects table B, and table B has a trigger that affects table A, you can manage this using TRIGGER_NESTLEVEL, or by checking if either inserted or deleted contain any rows before actually doing anything.

Sql Server error [SQLState 42000] (Error 325)

I have a script that utilizes the new Merge Output clause. I've run it in 3 different instances (all non-Production environments) and it works great. When I tried running it in our production environment, I get the error:
Executed as user: xxx\xxx. 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. [SQLSTATE 42000] (Error
325) 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. [SQLSTATE 42000] (Error 325). The step failed.
I've checked the versions of each instance and they are all 10.0.4000.0. All of the non-system databases are set to compatibility level 90 (2005), and system databases are set to 100 (2008). What else do I need to check to see where my production instance is different from the other non-Production instances?
Here's the query:
Declare #user varchar(20),
#message varchar(max)
Set #user = 'ISS20120917-144'
Create Table #data
(
CustomerEventID_Surrogate Int Identity (1,1) Not Null Primary Key,
CustomerNumber Int Not Null,
ConvictionEventID Int Not Null,
CustomerEventHierarchyID Int Not Null,
SanctionEventID Int Not Null,
ReferenceNumber varchar(40) Null,
ConvictionACDID Int Null,
State_Code varchar(2) Not Null,
County_ID Int Null,
CitationCDLHolderValueID Int Null,
Hazmat Bit Null,
CMV Bit Null,
PassengerEndorsement Bit Null,
OccurrenceDate DateTime Not Null,
ConvictionDate DateTime Not Null,
CourtOrder Bit Null
)
Create Table #surrogatemap
(
CustomerEventID_Surrogate Int Not Null,
NewCustomerEventID Int Not Null
)
Create Table #surrogateHIDmap
(
NewCustomerEventID Int Not Null,
NewHistoryEventDetailID Int Not Null
)
Begin Tran
Begin Try
Insert Into #data
Select ce.Cust_No,
ce.CustomerEventID,
ceh.CustomerEventHierarchyID,
ceSAN.CustomerEventID,
ce.ReferenceNumber,
hed.ACDID,
hed.State_Code,
hed.County_ID,
hed.CitationCDLHolderValueID,
hed.Hazmat,
hed.CMV,
hed.PassengerEndorsement,
hed.OccurrenceDate,
Case When cd.ConvictionDate IS NOT NULL Then cd.ConvictionDate
Else hed.OccurrenceDate
End As [ConvictionDate],
hed.CourtOrder
From IADS..CustomerEvent ce
Inner Join IADS..HistoryEventDetail hed On hed.CustomerEventID = ce.CustomerEventID
And hed.EndDate IS NULL
Inner Join IADS..CustomerEventCode cec On cec.CustomerEventCodeID = hed.CustomerEventCodeID
And cec.CustomerEventCodeID <> -51
Left Outer Join IADS..ConvictionDetail cd On cd.HistoryEventDetailID = hed.HistoryEventDetailID
Inner Join IADS..CustomerEventHierarchy ceh On ceh.CustomerEventID = ce.CustomerEventID
And ceh.EndDate IS NULL
Inner Join IADS..CustomerEvent ceSAN On ceSAN.CustomerEventID = ceh.RelatedCustomerEventID
And ceSAN.CustomerEventDispositionID IS NULL
Inner Join IADS..CustomerSanctionDetail csd On csd.CustomerEventID = ceSAN.CustomerEventID
And csd.SanctionDiscardedReasonID IS NULL
Inner Join IADS..SanctionReasonCode src On src.SanctionReasonCodeID = csd.SanctionReasonCodeID
And src.SanctionReasonCodeID = -320
Where ce.CustomerEventDispositionID IS NULL
Merge Into IADS..CustomerEvent
Using #data As src On 1 = 0
When Not Matched Then
Insert
(
CustomerEventCategoryID,
Cust_No,
ReferenceNumber,
CreatedBy,
CreatedDate,
UpdatedBy,
UpdatedDate
)
Values
(
-2,
src.CustomerNumber,
src.ReferenceNumber,
#user,
GetDate(),
#user,
GetDate()
)
Output
src.CustomerEventID_Surrogate,
inserted.CustomerEventID
Into #surrogatemap;
Select sm.NewCustomerEventID,
-8 As [HistoryEventTypeID],
-51 As [CustomerEventCodeID],
131 As [ACDID],
d.State_Code,
d.County_ID,
d.CitationCDLHolderValueID,
d.OccurrenceDate,
d.ConvictionDate,
d.Hazmat,
d.CMV,
d.CourtOrder,
GETDATE() As [EffectiveDate],
#user As [UpdatedBy],
GETDATE() As [UpdatedDate],
d.ConvictionACDID,
d.PassengerEndorsement
Into #hiddata
From #data d
Inner Join #surrogatemap sm On sm.CustomerEventID_Surrogate = d.CustomerEventID_Surrogate
Merge Into IADS..HistoryEventDetail
Using #hiddata As src On 1 = 0
When Not Matched Then
Insert
(
CustomerEventID,
HistoryEventTypeID,
CustomerEventCodeID,
ACDID,
State_Code,
County_ID,
CitationCDLHolderValueID,
OccurrenceDate,
Hazmat,
CMV,
CourtOrder,
EffectiveDate,
UpdatedBy,
UpdatedDate,
UnderlyingACDID,
PassengerEndorsement
)
Values
(
src.NewCustomerEventID,
src.HistoryEventTypeID,
src.CustomerEventCodeID,
src.ACDID,
src.State_Code,
src.County_ID,
src.CitationCDLHolderValueID,
src.OccurrenceDate,
src.Hazmat,
src.CMV,
src.CourtOrder,
src.EffectiveDate,
src.UpdatedBy,
src.UpdatedDate,
src.ConvictionACDID,
src.PassengerEndorsement
)
Output
src.NewCustomerEventID,
inserted.HistoryEventDetailID
Into #surrogateHIDmap;
Insert Into IADS..CustomerEventHierarchy
(
CustomerEventID,
RelatedCustomerEventID,
EffectiveDate,
UpdatedBy,
UpdatedDate
)
Select sm.NewCustomerEventID,
d.SanctionEventID,
GETDATE(),
#user,
GETDATE()
From #data d
Inner Join #surrogatemap sm On sm.CustomerEventID_Surrogate = d.CustomerEventID_Surrogate
Insert Into IADS..CourtFineDetail
(
HistoryEventDetailID,
ConvictionDate
)
Select s.NewHistoryEventDetailID,
d.ConvictionDate
From #hiddata d
Inner Join #surrogateHIDmap s On s.NewCustomerEventID = d.NewCustomerEventID
-- Remove the tie to the SUS077
Update IADS..CustomerEventHierarchy
Set EndDate = GETDATE(),
UpdatedBy = #user,
UpdatedDate = GETDATE()
Where CustomerEventHierarchyID In (Select CustomerEventHierarchyID From #data)
-- Build temp table containing the records that have already purged
Select ce.Cust_No,
ce.CustomerEventID,
ceh.CustomerEventHierarchyID
Into #disposedRecords
From IADS..CustomerEvent ce
Inner Join IADS..HistoryEventDetail hed On hed.CustomerEventID = ce.CustomerEventID
And hed.EndDate IS NULL
Inner Join IADS..CustomerEventCode cec On cec.CustomerEventCodeID = hed.CustomerEventCodeID
And hed.CustomerEventCodeID <> -51
Inner Join IADS..CustomerEventHierarchy ceh On ceh.CustomerEventID = ce.CustomerEventID
And ceh.EndDate IS NULL
Inner Join IADS..CustomerEvent ceSAN On ceSAN.CustomerEventID = ceh.RelatedCustomerEventID
And ceSAN.CustomerEventDispositionID IS NOT NULL
Inner Join IADS..CustomerSanctionDetail csd On csd.CustomerEventID = ceSAN.CustomerEventID
And csd.SanctionReasonCodeID = -320
Where ce.CustomerEventDispositionID IS NOT NULL
Order By ce.CustomerEventDispositionDate Desc
-- Un-purge all of the records that were previously tied to a SUS077
Update IADS..CustomerEvent
Set CustomerEventDispositionID = Null,
CustomerEventDispositionComment = Null,
CustomerEventDispositionDate = Null,
UpdatedBy = #user,
UpdatedDate = GETDATE()
Where CustomerEventID In (Select CustomerEventID From #disposedRecords)
-- Remove the records from the PURGEEventsReadyForPurge table
Delete
From IADS..PURGEEventsReadyForPurge
Where CustomerEventID In (Select CustomerEventID From #disposedRecords)
-- Remove tie of purged records
Update IADS..CustomerEventHierarchy
Set EndDate = GETDATE(),
UpdatedBy = #user,
UpdatedDate = GETDATE()
Where CustomerEventHierarchyID In (Select CustomerEventHierarchyID From #disposedRecords)
Delete From IADS..PURGEEventsReadyForPurge Where PURGEEventsReadyForPurgeID In
(
Select PURGEEventsReadyForPurgeID
From IADS..PURGEEventsReadyForPurge p
Inner Join IADS..CustomerEvent ce On ce.CustomerEventID = p.CustomerEventID
And ce.CustomerEventDispositionID IS NULL
Inner Join IADS..CustomerEventCategory ceg On ceg.CustomerEventCategoryID = ce.CustomerEventCategoryID
Left Outer Join IADS..CustomerEventHierarchy ceh On ceh.CustomerEventID = ce.CustomerEventID
Left Outer Join IADS..CustomerEventHierarchy ceh2 On ceh2.RelatedCustomerEventID = ce.CustomerEventID
Where p.PurgeDate IS NOT NULL
)
Drop Table #disposedRecords
Drop Table #hiddata
Drop Table #surrogateHIDmap
Drop Table #surrogatemap
Drop Table #data
Commit
End Try
Begin Catch
Drop Table #disposedRecords
Drop Table #hiddata
Drop Table #surrogateHIDmap
Drop Table #surrogatemap
Drop Table #data
Rollback
End Catch
You can try any of these two things..
1. Update the compatiability level to 100.
ALTER DATABASE [dbname] SET COMPATIBILITY_LEVEL = 100
2. End the MERGE statement and the statement previous to MERGE with a semicolon (;)
Hope it works.

Need to speed up SQL Server SP that uses system metadata

Let me apologize in advance for the length of this question. I don't see how to ask it without giving all the definitions.
I've inherited a SQL Server 2005 database that includes a homegrown implementation of change tracking. Through triggers, changes to virtually every field in the database are stored in a set of three tables. In the application for this database, the user can request the history of various items, and what's returned is not just changes to the item itself, but also changes in related tables. The problem is that in some cases, it's painfully slow, and in some cases, the request eventually crashes the application. The client has also reported other users having problems when someone requests history.
The tables that store the change data are as follows:
CREATE TABLE [dbo].[tblSYSChangeHistory](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[date] [datetime] NULL,
[obj_id] [int] NULL,
[uid] [varchar](50) NULL
This table tracks the tables that have been changed. Obj_id is the value that Object_ID() returns.
CREATE TABLE [dbo].[tblSYSChangeHistory_Items](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[h_id] [bigint] NOT NULL,
[item_id] [int] NULL,
[action] [tinyint] NULL
This table tracks the items that have been changed. h_id is a foreign key to tblSYSChangeHistory. item_id is the PK of the changed item in the specified table. action indicates insert, delete or change.
CREATE TABLE [dbo].[tblSYSChangeHistory_Details](
[id] [bigint] IDENTITY(1,1) NOT NULL,
[i_id] [bigint] NOT NULL,
[col_id] [int] NOT NULL,
[prev_val] [varchar](max) NULL,
[new_val] [varchar](max) NULL
This table tracks the individual changes. i_id is a foreign key to tblSYSChangeHistory_Items. col_id indicates which column was changed, and prev_val and new_val indicate the original and new values for that field.
There's actually a fourth table that supports this architecture. tblSYSChangeHistory_Objects maps plain English descriptions of operations to particular tables in the database.
The code to look up the history for an item is incredibly convoluted. It's one branch of a very long SP. Relevant parameters are as follows:
#action varchar(50),
#obj_id bigint = 0,
#uid varchar(50) = '',
#prev_val varchar(MAX) = '',
#new_val varchar(MAX) = '',
#start_date datetime = '',
#end_date datetime = ''
I'm storing them to local variables right away (because I was able to significantly speed up another SP by doing so):
declare #iObj_id bigint,
#cUID varchar(50),
#cPrev_val varchar(max),
#cNew_val varchar(max),
#tStart_date datetime,
#tEnd_date datetime
set #iObj_id = #obj_id
set #cUID = #uid
set #cPrev_val = #prev_val
set #cNew_val = #new_val
set #tStart_date = #start_date
set #tEnd_date = #end_date
And here's the code from that branch of the SP:
create table #r (obj_id int, item_id int, l tinyint)
create clustered index #ri on #r (obj_id, item_id)
insert into #r
select object_id(obj_name), #iObj_id, 0
from dbo.tblSYSChangeHistory_Objects
where obj_type = 'U' and descr = cast(#cPrev_val AS varchar(150))
declare #i tinyint, #cnt int
set #i = 1
while #i <= 4
begin
insert into #r
select obj_id, item_id, #i
from dbo.vSYSChangeHistoryFK a with (nolock)
where exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id and l = #i - 1)
and not exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id)
set #cnt = ##rowcount
insert into #r
select rel_obj_id, rel_item_id, #i
from dbo.vSYSChangeHistoryFK a with (nolock)
where object_name(obj_id) not in (<this is a list of particular tables in the database>)
and exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id and l between #i - 1 and #i)
and not exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id)
set #i = case #cnt + ##rowcount when 0 then 100 else #i + 1 end
end
select date, obj_name, item, [uid], [action],
pkey, item_id, id, key_obj_id into #tCH_R
from dbo.vSYSChangeHistory a with (nolock)
where exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id)
and (#cUID = '' or uid = #cUID)
and (#cNew_val = '' or [action] = #cNew_val)
declare ch_item_cursor cursor for
select distinct pkey, key_obj_id, item_id
from #tCH_R
where item = '' and pkey <> ''
open ch_item_cursor
fetch next from ch_item_cursor
into #cPrev_val, #iObj_id, #iCol_id
while ##fetch_status = 0
begin
set #SQLStr = 'select #val = ' + #cPrev_val +
' from ' + object_name(#iObj_id) + ' with (nolock)' +
' where id = #id'
exec sp_executesql #SQLStr,
N'#val varchar(max) output, #id int',
#cNew_val output, #iCol_id
update #tCH_R
set item = #cNew_val
where key_obj_id = #iObj_id
and item_id = #iCol_id
fetch next from ch_item_cursor
into #cPrev_val, #iObj_id, #iCol_id
end
close ch_item_cursor
deallocate ch_item_cursor
select date, obj_name,
cast(item AS varchar(254)) AS item,
uid, [action],
cast(id AS int) AS id
from #tCH_R
order by id
return
As you can see, the code uses a view. Here's that definition:
ALTER VIEW [dbo].[vSYSChangeHistoryFK]
AS
SELECT i.obj_id, i.item_id, c1.parent_object_id AS rel_obj_id, i2.item_id AS rel_item_id
FROM dbo.vSYSChangeHistoryItemsD AS i INNER JOIN
sys.foreign_key_columns AS c1 ON c1.referenced_object_id = i.obj_id AND c1.constraint_column_id = 1 INNER JOIN
dbo.vSYSChangeHistoryItemsD AS i2 ON c1.parent_object_id = i2.obj_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d1 ON d1.i_id = i.min_id AND d1.col_id = c1.referenced_column_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d1k ON d1k.i_id = i2.min_id AND d1k.col_id = c1.parent_column_id AND ISNULL(d1.new_val,
ISNULL(d1.prev_val, '')) = ISNULL(d1k.new_val, ISNULL(d1k.prev_val, '')) --LEFT OUTER JOIN
UNION ALL
SELECT i0.obj_id, i0.item_id, c01.parent_object_id AS rel_obj_id, i02.item_id AS rel_item_id
FROM dbo.vSYSChangeHistoryItemsD AS i0 INNER JOIN
sys.foreign_key_columns AS c01 ON c01.referenced_object_id = i0.obj_id AND c01.constraint_column_id = 1 AND col_name(c01.referenced_object_id,
c01.referenced_column_id) = 'ID' INNER JOIN
dbo.vSYSChangeHistoryItemsD AS i02 ON c01.parent_object_id = i02.obj_id INNER JOIN
dbo.tblSYSChangeHistory_Details AS d01k ON i02.min_id = d01k.i_id AND d01k.col_id = c01.parent_column_id AND ISNULL(d01k.new_val,
d01k.prev_val) = CAST(i0.item_id AS varchar(max))
And finally, that view uses one more view:
ALTER VIEW [dbo].[vSYSChangeHistoryItemsD]
AS
SELECT h.obj_id, m.item_id, MIN(m.id) AS min_id
FROM dbo.tblSYSChangeHistory AS h INNER JOIN
dbo.tblSYSChangeHistory_Items AS m ON h.id = m.h_id
GROUP BY h.obj_id, m.item_id
Working with the Profiler, it appears that view vSYSChangeHistoryFK is the big culprit, and my testing suggests that the particular problem is in the join between the two copies of vSYSChangeHistoryItemsD and the foreign_key_columns table.
I'm looking for any ideas on how to give acceptable performance here. The client reports sometimes waiting as much as 15 minutes without getting results. I've tested up to nearly 10 minutes with no result in at least one case.
If there were new language elements in 2008 or later that would solve this, I think the client would be willing to upgrade.
Thanks.
Wow that's a mess. Your big gain should be in removing the cursor. I see 'where exists' - that's nice and efficient b/c as soon as it finds one match it aborts. And I see 'where not exists' - by definition that has to scan everything. Is it finding the top 4? You can do better with using ROW_NUMBER() OVER (PARTITON BY [whatever makes it unique] ORDER BY [whatever your id is]. It's hard to tell. select object_id(obj_name), #iObj_id, 0 makes it seem like only the #i=1 loop actually does anything (?)
If that is what it's doing, you could write it as
SELECT * from
(
select ROW_NUMBER() OVER (PARTITION BY obj_id ORDER BY item_id desc) as Row,
obj_id, item_id
FROM bo.vSYSChangeHistoryFK a with (nolock)
where obj_type = 'U' and descr = cast(#cPrev_val AS varchar(150))
) paged
where Row between 1 and 4
ORDER BY Row
A DBA level change that could help would be to set up a partitioning scheme based on date. Roll over to a new partition every so often. Put the old partitions on different disks. Most queries may only need to hit the recent partition, which will be say 1/5th the size that it used to be, making it much faster without changing anything else.
Not a full answer, sorry. That mess would take hours to parse

Resources