There is a table in our SQL Server 2012 to generate and send emails. Its simplified structure is as follows:
CREATE TABLE [dbo].[EmailRequest]
(
[EmailRequestID] [int] NOT NULL,
[EmailAddress] [varchar](1024) NULL,
[CCEmailAddress] [varchar](1024) NULL,
[EmailReplyToAddress] [varchar](128) NULL,
[EmailReplyToName] [varchar](128) NULL,
[EmailSubject] [varchar](max) NULL,
[EmailBody] [varchar](max) NULL,
[Attachments] [varchar](max) NULL,
[CreateDateTime] [datetime] NULL,
[_EmailSent] [varchar](1) NULL,
[_EmailSentDateTime] [datetime] NULL,
CONSTRAINT [PK_EmailRequest]
PRIMARY KEY CLUSTERED ([EmailRequestID] ASC)
)
I don't have any control over that table or the database where it sits; it is provided "as is".
Different programs and scripts insert records into the table at random intervals. I suspect most of them do this with queries like this:
INSERT INTO [dbo].[EmailRequest] ([EmailRequestID], ... <other affected columns>)
SELECT MAX([EmailRequestID]) + 1, <constants somehow generated in advance>
FROM [dbo].[EmailRequest];
I run a big SQL script which at some conditions must send emails as well. In my case the part responsible for emails looks like this:
INSERT INTO [dbo].[EmailRequest] ([EmailRequestID], ... <other affected columns>)
SELECT MAX([EmailRequestID]) + 1, <values collected from elsewhere>
FROM [dbo].[EmailRequest]
JOIN db1.dbo.table1 ON ...
JOIN db1.dbo.table2 ON ... and so on;
The "select" part takes its time, so when it actually inserts data the calculated MAX([EmailRequestID]) + 1
value may become redundant and cause primary key violation (rare event, but nevertheless annoying one).
The question: is there a way to design the query so it calculates MAX([EmailRequestID])+1
later, just before insert
?
One of the options might be:
INSERT INTO [dbo].[EmailRequest] ([EmailRequestID], ... <other affected columns>)
SELECT
(SELECT MAX([EmailRequestID]) + 1
FROM [dbo].[EmailRequest]), <values collected from elsewhere>
FROM db1.dbo.table1
JOIN db1.dbo.table2 ON ... and so on;
but I am not sure if it brings any advantages.
So there may be another question: is there a way to see "time-lapse" of query execution?
Testing is a challenge, because no one sends request to the test database, so I will never get PK violation in there.
Thank you.
Some amazing results from testing the accepted answer. The elapsed time for original (real) query - 2000...2800 ms; same query without "insert" part - 1200...1800 ms. Note: the "select" statement collects information from three databases.
The test query retains real "select" statement (removed below):
Declare @mailTable table
(mt_ID int,
mt_Emailaddress varchar(1024),
mt_CCEmailAddress varchar(1024),
mt_EmailSubject varchar(max),
mt_EmailBody varchar(max)
);
insert into @mailTable
select row_number() over (ORDER BY (SELECT NULL)),
am.ul_EMail, ... -- EmailAddress - the rest is removed
FROM <real live tables>;
insert into dbo.EmailRequest
(EmailRequestID, _MessageID, EmailType, EmailAddress, CCEmailAddress,
BulkFlag, EmailSubject, EmailBody, EmailReplyToAddress,
CreateDateTime, SQLServerUpdated, SQLServerDateTime, _EmailSent)
select (select Max(EmailRequestID)+1 from dbo.EmailRequest),
0, '*TEXT', -- _MessageID, EmailType
mt_Emailaddress,
mt_CCEmailAddress,
'N', -- BulkFlag
mt_EmailSubject, -- EmailSubject
mt_EmailBody, -- EmailBody
'', GetDate(), '0', GetDate(), '0'
FROM @mailTable;
Elapsed time on 10 runs for first part - 48 ms (worst), 8 (best); elapsed time for second part, where collision may occur - 85 ms (worst), 1 ms (best)