SQL Server 2008 R2 logging query execution times incorrect - sql-server

EDIT: I added all of the code for the logon trigger.
Why am I getting some incorrect results when trying to retrieve the total time it takes for a query to run? This is within the context of a logon trigger on SQL Server 2008 R2.
For testing purposes, I want to get the a rough estimate of the total time it takes for a logon trigger to run. Here is a small sample of the results:
LogonId AppId TotalLogonTime (in MS)
101 1 0
253 2 3
289 2 3
985 1 -3
325 1 0
How can the total time evaluate to be negative? Out of the 2.3 million executions so far, there have been 25 that resulted in a time of -3 ms. Here is the code:
CREATE trigger [trgAudit]
on all server
with execute as 'TriggerLogonUser'
for logon
as
begin
set nocount on
set transaction isolation level read committed
declare
#LoginTime datetime
,#IsWindowsUser bit
,#TotalLogonTimeMS int
,#ClientNetAddress varchar(48)
,#MacAddress nchar(12)
,#CurrentUserId int -- UserNames
,#ApplicationId int
,#HonId int --HostName
,#LogonId int --Logon
,#MacId int -- MacAddress
,#CnaId int -- ClientNetAddress
begin try
/*
*** Get the login time, user type, mac address, and client net address
*/
select
#LoginTime = getdate(),
#IsWindowsUser = case
when len(nt_domain) = 0
then 0
else 1
end,
#MacAddress = p.net_address,
#ClientNetAddress = convert(varchar(48),connectionproperty('client_net_address'))
from sys.sysprocesses p
where
p.spid = ##spid
/*
*** Client Net Address
*/
select top 1
#CnaId = CnaId
from master.sysmaintenance.ClientNetAddress with(index(IX_CnaAddress))
where #ClientNetAddress = CnaAddress
--if the ip does not exist, insert it.
if #CnaId is null
begin
insert master.sysmaintenance.ClientNetAddress(CnaAddress)
values (#ClientNetAddress)
select #CnaId = ##identity
end
/*
*** Applications
*/
select top 1
#ApplicationId = AppId
from master.sysmaintenance.Applications with(index(IX_AppName))
where app_name() = AppName
if #ApplicationId is null
begin
insert master.sysmaintenance.Applications (AppName)
values (app_name())
select #ApplicationId = ##identity
end
/*
*** HostName
*/
select top 1
#HonId = HonId
from master.sysmaintenance.HostName with(index(IX_HonName))
where HonName = host_name()
if #HonId is null
begin
insert master.sysmaintenance.HostName
values (host_name())
select #HonId = ##identity
end
/*
*** UserNames
*/
select top 1
#CurrentUserId = UsnId
from master.sysmaintenance.Usernames with(index(IX_UsnName))
where UsnName = original_login()
if #CurrentUserId is null
begin
insert master.sysmaintenance.Usernames
values (original_login())
select #CurrentUserId = ##identity
end
/*
*** MacAddress
*/
select top 1
#MacId = MacId
from master.sysmaintenance.MacAddress with(index(IX_MacAddress))
where MacAddress = #MacAddress
-- same logic is continued as in the applications
if #MacId is null
begin
insert master.sysmaintenance.MacAddress (MacAddress)
values (#MacAddress)
select #MacId = ##identity
end
/*
*** Get the total logon time
*/
select #TotalLogonTimeMS = datediff(ms,#LoginTime, getdate())
-- insert ids of the data gathered on the logon event into the logon table.
insert master.sysmaintenance.Logon ( LogAppId,
LogHonId,
IsWindowsLogon,
CurrentLogonId,
LogonDatetime,
LogCnaId,
LogMacId,
LogonTimeMS )
values ( #ApplicationId,
#HonId,
#IsWindowsUser,
#CurrentUserId,
#LoginTime,
#CnaId,
#MacId,
#TotalLogonTimeMS
)
end try
begin catch
print cast(error_number() as nvarchar(11))
print cast(error_severity() as nvarchar(11))
print cast(error_state() as nvarchar(11))
print cast(error_procedure() as nvarchar(126))
print cast(error_line() as nvarchar(11))
print cast(error_message() as nvarchar(2048))
end catch
end
Here is the DDL to the Logon Table:
CREATE TABLE [sysmaintenance].[Logon](
[LogonId] [bigint] IDENTITY(1,1) NOT NULL,
[LogAppId] [int] NULL,
[LogHonId] [int] NULL,
[LogMacId] [int] NULL,
[LogCnaId] [int] NULL,
[IsWindowsLogon] [bit] NULL,
[CurrentLogonId] [int] NULL,
[LogonDatetime] [datetime] NULL,
[LogonTimeMS] [int] NULL
) ON [PRIMARY]
CREATE UNIQUE CLUSTERED INDEX [PK_Logon] ON [sysmaintenance].[Logon]
(
[LogonId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
Any help or insight is appreciated.

GETDATE():
Returns the current database system timestamp as a datetime value without the database time zone offset. This value is derived from the operating system of the computer on which the instance of SQL Server is running.
Now, bearing in mind that computer clocks drift, I'd imagine that if the computer's time was corrected (either manually or automatically), the time reported by two successive calls to GETDATE() (that aren't part of a single query) could go backwards. All of the datetime methods ultimately rely on the computer's clock.
It's a shame you're not logging one of these GETDATE() results alongside the timing, so you could see if they all occurred at the same "time".

Related

Resync target servers in SQL Server

I am working in a SQL Server environment with one master server and many target servers. It sometimes happens that for a reason or another one a target server may go out of sync.
I have the choice when that occurs to manually run the following stored procedure to re-sync the target server:
exec sp_resync_targetserver #server_name = 'RMAPP11DV1\PROJECT'
My assignment is to automate the process so that we do not have to manually run it. I should write a script and schedule it as a job that should run a a schedule time to selectively find and re-sync only the target servers that are currently out of sync.
This is my approach so far. It is not working as expected (can not do the re-sync when ran), that is why I need any one output. Thanks in advance:
use msdb
set nocount on;
if exists (select * from tempdb.sys.all_objects where name like '%#targetstatus%') --deleting the table if it already exists
drop table #targetstatus
create table #targetstatus
(
server_id int not null,
server_name nvarchar(300) not null,
location nvarchar(350) null,
time_zone_adjustment int not null,
enlist_date datetime not null,
last_poll_date datetime not null,
status int not null,
unread_instructions int not null,
local_time datetime not null,
enlisted_by_nt_user nvarchar(100) not null,
poll_interval int not null
)
insert into #targetstatus
exec sp_help_targetserver
select * from #targetstatus
if exists (select * from tempdb.sys.all_objects where name like '%#needresync%') --deleting the table if it already exists
drop table #needresync
create table #needresync -- will hold the target servers needing to be re-synced
(
server_id int not null,
server_name nvarchar(300) not null,
location nvarchar(350) null,
time_zone_adjustment int not null,
enlist_date datetime not null,
last_poll_date datetime not null,
status int not null,
unread_instructions int not null,
local_time datetime not null,
enlisted_by_nt_user nvarchar(100) not null,
poll_interval int not null
)
insert into #needresync
select *
from #targetstatus
where status <> 1 -- we only want to run the syncing proc on the target with a status diff of #1
select * from #needresync
declare #target_server varchar(100);
set #target_server = ' '
while #target_server <> ' '
begin
set #target_server = (select max(server_name) from #needresync);
exec msdb.dbo.sp_resync_targetserver #server_name = '#target_server';
-- #target_server = #target_server + 1
end
You are not deleting the row out of #needresync. You must delete each row one by one inside the while loop.
However, a much easier method exists. You can use the systargetservers DMV without using any temp tables at all:
DECLARE #server sysname =
(SELECT TOP 1 FROM dbo.systargetservers WHERE status = 2); -- 2 = Re-sync Pending
WHILE (#server IS NOT NULL)
BEGIN
EXEC sp_resync_targetserver #server;
SET #server =
(SELECT TOP 1 FROM dbo.systargetservers WHERE status = 2);
END;

How can I increase the efficiency of this MSSQL Stored Procedure?

I am working with some communications and returning data to a PLC, and executing a stored procedure often.
I was wondering if I could get an opinion on how I might be able to increase the efficiency of this stored procedure?
As of now, it runs roughly ~1600 or so times a day, which is fine. But moving forward, it's likely to run upwards of 5000 or some times a day. I was wondering what might be the best method to reduce the number of SELECT statements, is it possible I can use a CASE statement?
Below is some of the example code.
#machine nvarchar(25),
#suppliercode nvarchar(1),
#barcode nvarchar(16),
#sqlpass int OUTPUT,
#sqlfail int OUTPUT,
#output int OUTPUT,
#lotnomatch int OUTPUT,
#partnomatch int OUTPUT,
#foundlot nvarchar(16) OUTPUT
AS
BEGIN
SET #output = (SELECT COUNT(ID) FROM dbo.MCData WHERE Barcode = #barcode AND Machine = #machine)
IF (#output >= 1)
BEGIN
SET #sqlpass = 1
UPDATE dbo.MCBarcodeData SET Interfaced = 1, InterfacedDatetime = GetDate(), InterfacedMachine = #machine WHERE Barcode = #barcode AND Machine = #machine
END
IF (#output = 0)
BEGIN
SET #lotnomatch = (SELECT COUNT(ID) FROM dbo.MCData WHERE Barcode = #barcode AND Machine != #machine)
END
IF (#lotnomatch = 1)
BEGIN
SET #foundlot = (SELECT Machine FROM dbo.MCData WHERE Barcode = #Barcode)
END
IF (#output = 0 AND #lotnomatch = 0)
BEGIN
SET #partnomatch = 1
END
END
GO
Edit: Attached if the Query Execution Plan
Calculating statistics from a whole picture perspective on a regular basis always gets worse over time with more and more data. If it is something that isn't done very often then it isn't so bad, but if your doing it very often then it will hurt in the long run.
What I like to do is maintain the statistics as data is modified in a separate table.
For example, you might want a separate table that has a composite primary key of barcode and machine and then have a column for number of records in mcdata that match your key and number of records in mcdata that have the same barcode but not the same machine. You can also have a column for the machine name for the one that doesn't match if there is one.
CREATE TABLE MCDataStats
(
Barcode nvarchar(16),
Machine nvarchar(25),
NumMatchBarcodeMachineInMCData int,
NumMatchBarcodeNotMachineInMCData int,
LotMachineName nvarchar(25),
PRIMARY KEY (Barcode, Machine)
)
Then you could just run a single simple select on that statistics table.
CREATE PROCEDURE procMCDataStats_GET
#Barcode nvarchar(16),
#Machine nvarchar(25)
AS
BEGIN
SELECT *
FROM MCDataStats
WHERE Barcode = #Barcode AND
Machine = #Machine
END
To maintain the statistics you would update them as data is modified. Like in your stored procedure to add a record to mcdata. You would then insert a record in your statistics table for that barcode and machine you don't have it already. Then you would increment the number of records in mcdata column and then increment the number of records in mcdata that have same barcode but not the same machine for records in your statistics table with that barcode, but not the same machine. If you incremented any records in statistics table with that barcode, but not the same machine then you can verify the stats that are there to determine if you want to populate the lot column. And if you remove any records, make sure you go through and decrement.
CREATE PROCEDURE procMCData_ADD
#Barcode nvarchar(16),
#Machine nvarchar(25)
AS
BEGIN
INSERT INTO MCData (Barcode, Machine, ...)
VALUES (#Barcode, #Machine, ...)
--Insert into MCDataStats if barcode and machine don't exist
INSERT INTO MCDataStats (Barcode, Machine, NumMatchBarcodeMachineInMCData,
NumMatchBarcodeNotMachineInMCData)
SELECT #Barcode, #Machine, 0, 0
WHERE NOT EXISTS (SELECT TOP 1 * FROM MCDataStats WHERE Barcode = #Barcode AND Machine = #Machine)
UPDATE data
SET Interfaced = 1, InterfacedDatatime = GETDATE(), InterfacedMachine = #Machine
FROM MCDataStats AS stats
LEFT JOIN MCBarcodeData AS data ON data.Barcode = stats.Barcode AND data.Machine = stats.Machine AND stats.NumMatchBarcodeMachineInMCData = 1
--Update stats for matching barcode and machine
UPDATE stats
SET NumMatchBarcodeMachineInMCData = NumMatchBarcodeMachineInMCData + 1
FROM MCDataStats AS stats
WHERE Barcode = #Barcode AND Machine = #Machine
--Update stats for matching barcode but not machine
UPDATE stats
SET NumMatchBarcodeNotMachineInMCData = NumMatchBarcodeNotMachineInMCData + 1,
LotMachineName = (CASE WHEN NumMatchBarcodeMachineInMCData = 0 AND NumMatchBarcodeNotMachineInMCData = 0 THEN #Machine ELSE NULL END)
FROM MCDataStats AS stats
WHERE Barcode = #Barcode AND Machine <> #Machine
END
In my opinion you should consider to add non-clustered index in your tables.
For example:
CREATE NONCLUSTERED INDEX IX_MCData_Barcode_Machine
ON dbo.MCData (Barcode ASC, Machine ASC)
GO
CREATE NONCLUSTERED INDEX IX_MCBarcodeData_BarcodeMachine
ON dbo.MCBarcodeData (Barcode ASC, Machine ASC)
INCLUDE (Interfaced, InterfacedDatetime, InterfacedMachine)
GO

Sql server not unique auto-increment column

Let's say I have a table called [dbo].[Order] in my MSSQL Server database like the following:
CREATE TABLE [dbo].[Order] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[NumberInMonth] INT NOT NULL,
[Amount] DECIMAL (18, 2) NOT NULL,
CONSTRAINT [PK_dbo.Order] PRIMARY KEY CLUSTERED ([Id] ASC)
);
I want to have NumberInMonth column to be:
auto-incremented, so that the database will guarantee that the INSERT operation will create a new record with the value of Number = < previous value of Number > + 1 without any effort on the side of the application code
not unique, so that i could have one order #18 in January and another one order #18 in February (which will differ by Id field)
And also I'd like to have a way to have a schedulled operation which will reset the counter of NumberInMonth every first day of a month
How do I achieve it?
A simple sequence can provide the auto-incrementing functionallity:
CREATE SEQUENCE seq_numberInMonth as int START WITH 1 INCREMENT BY 1;
CREATE TABLE [dbo].[Order] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[NumberInMonth] INT NOT NULL DEFAULT(next value for seq_numberInMonth),
[Amount] DECIMAL (18, 2) NOT NULL,
CONSTRAINT [PK_dbo.Order] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Test:
INSERT INTO [Order] (Amount) VALUES (12.0), (13.0), (14.0)
SELECT *
FROM [Order]
results:
Id NumberInMonth Amount
1 1 12,00
2 2 13,00
3 3 14,00
You can quite easily create a scheduled job to run every 1st of the month and reset the sequence:
ALTER SEQUENCE seq_numberInMonth RESTART WITH 1 ;
Creating the job in t-sql can be done like this: (didn't test it, but it should work according to the following links):
How to: Create a SQL Server Agent Job
Create a Job
USE msdb ;
GO
EXEC dbo.sp_add_job
#job_name = N'seq_numberInMonth reset' ;
GO
EXEC sp_add_jobstep
#job_name = N'seq_numberInMonth reset',
#step_name = N'1st',
#subsystem = N'TSQL',
#command = N'ALTER SEQUENCE seq_numberInMonth RESTART WITH 1 ;',
#retry_attempts = 5,
#retry_interval = 5 ;
GO
EXEC sp_add_schedule #schedule_name = 'every 1st of the month'
, #enabled = 1
, #freq_type = 16
, #freq_interval = 1
GO
EXEC sp_attach_schedule
#job_name = N'seq_numberInMonth reset',
#schedule_name = N'every 1st of the month';
GO
EXEC dbo.sp_add_jobserver
#job_name = N'seq_numberInMonth reset';
GO

SQL Server - delete using "where records in" causes blocking while direct record selection does not

I have a problem where a delete is running inside a transaction
Without committing the delete I run another delete in another transaction
If I use "delete where record in (select...)" the delete is blocked by the original transaction
However if I use "delete where record in (actual record)" the delete goes through straight away
CREATE TABLE [dbo].[test1] ([token] [varchar] (10) not null, [superToken] [varchar] (10) not NULL, [nr] INT NOT NULL)
ALTER TABLE [dbo].[test1] ADD CONSTRAINT [PK_test1] PRIMARY KEY CLUSTERED ([token], [nr]) ON [PRIMARY]
insert into dbo.test1 (token,supertoken,nr) values ('1','1', 123)
insert into dbo.test1 (token,supertoken,nr) values ('2','2', 456)
CREATE TABLE [dbo].[test2] ([token] [varchar] (10) not null, [supertoken] [varchar] (10) not NULL)
ALTER TABLE [dbo].[test2] ADD CONSTRAINT [PK_test2] PRIMARY KEY CLUSTERED ([token]) ON [PRIMARY]
insert into dbo.test2 (token, supertoken) values ('1','1')
insert into dbo.test2 (token, supertoken) values ('2','2')
/* Begin Step 1 */
-- run this query in window 1 - do not commit
BEGIN TRANSACTION
DELETE test1 FROM test1 WITH (NOLOCK) WHERE token IN (SELECT token FROM test2 WITH (NOLOCK) WHERE supertoken='1'))
-- run this query in window 2 - will cause blocking
BEGIN TRANSACTION
DELETE test1 FROM test1 WITH (NOLOCK) WHERE token IN (SELECT token FROM test2 WITH (NOLOCK) WHERE supertoken = '2')
-- can now rollback all transations
/* End step 1 */
/* Begin Step 2 */
-- run this query in window 1 - do not commit
BEGIN TRANSACTION
DELETE test1 FROM test1 WITH (NOLOCK) WHERE token IN ('1') -- what sub query in step 1, query 1 returns
-- run this query in window 2 - will NOT cause blocking
BEGIN TRANSACTION
DELETE test1 FROM test1 WITH (NOLOCK) WHERE token IN ('2') -- what sub query in step 1, query 2 returns
-- can now rollback all transations
/* End step 2 */
It looks like you can get around this by saving the parent.Token from table2 into a variable. This looks like it'll work since your tokens appear to be uniqueidentifiers, and because of this the subquery will return at most 1 result:
DECLARE #ChildToken UNIQUEIDENTIFIER
SELECT #ChildToken = parent.Token
FROM Table2 parent
WHERE parent.SuperToken = 'E38D2055-7D52-4C58-A50E-5297B8A58659'
DELETE child
FROM table1 child
WHERE child.Token = #ChildToken

Easy Trigger question

I would imagine that this would be an easy question for someone who works a lot with T-SQL and especially Triggers:
I want to enforce the following constraints on all updates and inserts to this table:
If DiscountTypeId = 1, then FlatFee must not be NULL.
If DiscountTypeId = 2, then DiscountRate must not be null.
If either one of these two conditions fail on an insert or update to the table, I'd like to return an appropriate error.
The trigger appears not to do anything yet. .Can you provide the necessary changes so it performs as described?
USE [PandaVisa2008]
GO
/****** Object: Table [dbo].[CustomerSpeed] Script Date: 11/04/2010 15:51:10 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[CustomerSpeed](
[CustomerSpeedId] [int] NOT NULL,
[CustomerId] [int] NULL,
[SpeedId] [int] NOT NULL,
[DiscountTypeId] [int] NOT NULL,
[FlatFee] [money] NULL,
[DiscountRate] [decimal](3, 3) NULL,
CONSTRAINT [PK_AgentFee] PRIMARY KEY CLUSTERED
(USE [PandaVisa2008]
GO
/****** Object: Trigger [dbo].[TRG_CustomerSpeed_OnInsertUpdate] Script Date: 11/04/2010 15:38:06 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[TRG_CustomerSpeed_OnInsertUpdate]
ON [dbo].[CustomerSpeed]
FOR INSERT, UPDATE
AS
BEGIN
DECLARE #DiscountTypeId INT
DECLARE #FlatFee MONEY
DECLARE #DiscountRate DECIMAL(3, 3)
SELECT
#DiscountTypeId = DiscountTypeId,
#FlatFee = FlatFee,
#DiscountRate = DiscountRate
FROM
inserted
IF #DiscountTypeId = 1
AND #FlatFee IS NULL
BEGIN
RAISERROR (N'If #DiscountTypeId is 1, FlatFee must not be NULL',
10,
1)
END
IF #DiscountTypeId = 2
AND #DiscountRate IS NULL
BEGIN
RAISERROR (N'If #DiscountTypeId is 2, #DiscountRate must not be NULL',
10,
1)
END
END
[CustomerSpeedId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[CustomerSpeed] WITH CHECK ADD CONSTRAINT [CK_CustomerSpeed] CHECK (([DiscountRate]>(0) AND [DiscountRate]<(1)))
GO
ALTER TABLE [dbo].[CustomerSpeed] CHECK CONSTRAINT [CK_CustomerSpeed]
GO
EDIT
I got it to work. I haven't read up on Triggers to remedy my fundamental lack of understanding, but t his seemed to work, although I believe that the Check Constraint is the better approach:
ALTER TRIGGER [dbo].[TRG_CustomerSpeed_OnInsertUpdate]
ON [dbo].[CustomerSpeed]
FOR INSERT, UPDATE
AS
BEGIN
IF EXISTS (SELECT
1
FROM
inserted I
WHERE I.DiscountTypeId = 1
AND I.FlatFee IS NULL)
BEGIN
ROLLBACK TRANSACTION
RAISERROR (N'If DiscountTypeId is 1, FlatFee must not be NULL',
10,
1)
END
IF EXISTS (SELECT
1
FROM
inserted I
WHERE I.DiscountTypeId = 2
AND I.DiscountRate IS NULL)
BEGIN
ROLLBACK TRANSACTION
RAISERROR (N'If DiscountTypeId is 2, DiscountRate must not be NULL',
10,
1)
END
/*
IF #DiscountTypeId = 2
AND #DiscountRate IS NULL
BEGIN
Rollback Transaction
RAISERROR (N'If #DiscountTypeId is 2, DiscountRate must not be NULL',
10,
1)
END
*/
END
Your comments are welcomed.
I'd use a CHECK constraint, not a triggers
ALTER TABLE Mytable WITH CHECK ADD
CONSTRAINT CK_MyTable_GoodName CHECK (
NOT (DiscountTypeId = 1 AND Flatfee IS NULL)
AND
NOT (DiscountTypeId = 2 AND DiscountRate IS NULL)
)
Also, need to consider "if DiscountTypeId <> 1, does Flatfee have to be NULL" etc
You fundamentally do not understand triggers. The very first thing you need to do is go read about triggers in Books Online with particular emphasis on learning about the inserted and deleted psuedotables. Next thing you need to know is a trigger should NEVER be written as if it will handle only one record at a time. Triggers operate on batches of records and trigger code must account for that.
I don't believe triggers can raise errors, problem #1.

Resources