I am trying to keep a record of all of the emails that get sent via xp_smtp mail component, but I don't know how to store the message. Storing a 1 because it failed doesn't do me much good.
DECLARE #rc INT
EXEC #rc = master.dbo.xp_smtp_sendmail...
--Once the email is sent put the results (pass or fail) into another table:
EXEC pr_InsertEmailLog #Body, #From, #Subject, #To, #rc, 'Error MESSAGE here'
The documentation say it only returns 0 or 1.
However, further down it mentions planned work with an output message parameter
Add an output parameter (#errmsg
OUTPUT) which contains the error
message in case of a failure to have
better control over the execution and
enable to XP to work in complete
silent mode. The parameter would only
contain data if the return code of the
XP is not equal to 0 (zero) indicating
some sort of failure.
YMMV, but some kind soul may have developed it further then this release.
Related
I have a piece of code I am not familiar with sql server, only oracle. Can someone tell me what this is doing? Thanks.
What is the #flowcontrol
what is set #flowcontrol = ##error? Why two ##?
Why is print twice? What does print do here?
What is raiseerror doing?
Use [ra8]
declare #flowcontrol integer
set #flowcontrol = ##error
if #flowcontrol = 0
begin
print ' '
print 'create temp[nw] table'
create table [dbo].[temp] ([feild] [varchar] (200) nulll
end
else
begin
print ' '
print ' '
raiserror('raiseerror: create temp[nw] failed',12,1) with seterror
end
go
What is the #flowcontrol
#flowcontrol in this case is an integer variable (declared above)
what is set #flowcontrol = ##error? Why two ##?
the 2 # means this is a special - or system - function
Why is print twice? What does print do here?
print is a function who write in the 'pipe' (messages parts in sql management studio). I think they are here to make some room in the messages to be more readable.
What is raiseerror doing?
Raiseerror, as its name sounds like - is raising an error.
Globally, this is creating an integer variable, checking errors on a previous instruction, if there are no error create a table, else raising an error.
##VARIABLE - means it is a global variable maintained by SQL Server. Such variables represent information specific to the server or a current user session.
This wiki page has a listing of them and sample values: Global Variables in SQL Server
Without going into why I would like to do this, is it possible (I'll be using a login trigger) to log out a user that has no write permissions to a certain database?
I am able to find the currently logged in users permission, I just need to know if it's possible to log them out?
DECLARE #HasPermission bit
SELECT #HasPermission = HAS_PERMS_BY_NAME('RTEST2.dbo.TestTableSize', 'OBJECT', 'INSERT');
IF #HasPermission = 0
SELECT 'Now this is where id want to log out the user'
One can prevent a user from logging in by executing a ROLLBACK from within a login trigger. As #DavidBrowneMicrosoft mentioned in his comment, it's also a good practice to use a PRINT or RAISERROR statement so that reason for the login failure is logged. This message will not be returned to the client but may be useful for troubleshooting.
IF #HasPermission = 0
BEGIN
PRINT 'User does not have permissions to login';
ROLLBACK;
END;
I notice when this error is triggered within the stored procedure it returns 50,000. Is there a way to modify this to say 50,999 so the front-end app can specifically pick the error up and not confuse it with anything else.
RAISERROR('Client already has an Active Visit!',16,1)
As per the documentation of RAISERROR (Transact-SQL):
The message is returned as a server error message to the calling
application or to an associated CATCH block of a TRY…CATCH construct.
New applications should use THROW instead.
Emphasis mine. (THROW (Transact-SQL))
I don't know what your SQL statement looks like, but, instead you can therefore do something like:
BEGIN TRY
--Your INSERT statement
SELECT 0/0; --Causes an error
END TRY
BEGIN CATCH
THROW 50099, 'Client already has an Active Visit!',1;
END CATCH
With RAISEERROR, if you use message as the first parameter then you can't specify an error ID and it is implicitly 50000. However, you can create a custom message with parameters and pass your code there. ie:
RAISERROR('Client already has an Active Visit! - Specific Err.Number:[%d]',16,1, 50999)
Also Try\Catch is the suggested method for new applications.
You need to specify the first parameter of the raiseerror function, like so:
--configure the error message
sp_addmessage #msgnum = 50999,
#severity = 16,
#msgtext = N'Client %s already has an Active Visit!';
GO
-- throw error
RAISERROR (50999, -- Message id.
16, -- Severity,
1, -- State,
N'123456789'); -- First argument supplies the string.
GO
Output will be
Msg 50999, Level 16, State 1, Line 8
Client 123456789 already has an Active Visit!
If you don't specify the error number, the raiserror will be assumed to be 50000. Documentation here...
We have a function that needs to select items from a linked server, like
Alter function dbo.ttest1()
returns int
as
begin
SELECT * FROM LINKED_SERVER.Database.Schema.Table
WHERE .....
RETURN 0
end
In case of the remote server is not available, the function will throw out the error saying connection failed, but I want to cover it and let it return a default value.
Since this is compile-level error, BEGIN TRY --- END TRY is not able to cover it, what's worse, since we are in function, EXEC(#string),sp_executesql, SELECT * FROM OPENQUERY('...'), sp_testlinkedserver none of them works. the worst, I cannot afford to take risk of changing it into an SP (the grammar SELECT dbo.ttest1() is penetrated everywhere in the project).
Are there any good solutions?
I have an application that is using the Service Broker is SQL 2008. About once a day the database's performance starts take a noticeable hit and I have determined that this is because of the Service Broker. If I hard reset all broker connections using the following commands:
ALTER DATABASE [RegencyEnterprise] SET OFFLINE WITH ROLLBACK IMMEDIATE
ALTER DATABASE [RegencyEnterprise] SET ONLINE
Then the performance returns to normal until about the next day. I have also noticed that when performance is poor, running the following query returns a large number (around 1000 currently) of conversations that are stuck in the STARTED_OUTBOUND state:
SELECT * FROM sys.conversation_endpoints
Also, the following queries don't return any entries in them:
SELECT * FROM sys.dm_qn_subscriptions
SELECT * FROM sys.transmission_queue
Performance seems to be alright where there are plenty of items returned by this query. The only time when there are problems are when there are connections that are STARTED_OUTBOUND that stay stuck in this state.
The only configuration I have done to the Service Broker on my SQL Server 2008 instance was to run the following command:
ALTER DATABASE RegencyEnterprise SET ENABLE_BROKER
Digging through the SQL error log, I have found this entry over 1000 times as well:
07/11/2013 01:00:02,spid27s,Unknown,The query notification dialog on conversation handle '{6DFE46F5-25E9-E211-8DC8-00221994D6E9}.' closed due to the following error: '<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8490</Code><Description>Cannot find the remote service 'SqlQueryNotificationService-cb4e7a77-58f3-4f93-95c1-261954d3385a' because it does not exist.</Description></Error>'.
I also see this error a dozen or so times throughout the log, though I believe I can fix this just by creating a master key in the database:
06/26/2013 14:25:01,spid116,Unknown,Service Broker needs to access the master key in the database '<Database name>'. Error code:26. The master key has to exist and the service master key encryption is required.
I am thinking the number of these errors may be related to the number of conversations that are staying stuck in the queue. Here is the C# code I am using to subscribe to the query notifications:
private void EstablishSqlConnection(
String storedProcedureName,
IEnumerable<SqlParameter> parameters,
Action sqlQueryOperation,
String serviceCallName,
Int32 timeout,
params MultipleResult[] results)
{
SqlConnection storeConnection = (SqlConnection) ((EntityConnection) ObjectContext.Connection).StoreConnection;
try
{
using (SqlCommand command = storeConnection.CreateCommand())
{
command.Connection = storeConnection;
storeConnection.Open();
SqlParameter[] sqlParameters = parameters.ToArray();
command.CommandText = storedProcedureName;
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(sqlParameters);
if (sqlQueryOperation != null)
{
// Register a sql dependency with the SQL query.
SqlDependency sqlDependency = new SqlDependency(command, null, timeout);
sqlDependency.OnChange += OnSqlDependencyNotification;
}
using (DbDataReader reader = command.ExecuteReader())
{
results.ForEach(result => result.MapResults(this, reader));
}
}
}
finally
{
storeConnection.Close();
}
}
Here is how I handle the notification:
public static void OnSqlDependencyNotification(object sender, SqlNotificationEventArgs e)
{
if (e.Info == SqlNotificationInfo.Invalid)
{
// If we failed to register the SqlDependency, log an error
<Error is loged here...>
// If we get here, we are not in a valid state to requeue the sqldependency. However,
// we are on an async thread and should NOT throw an exception. Instead we just return
// here, as we have already logged the error to the database.
return;
}
// If we are able to find and remove the listener, invoke the query operation to re-run the query.
<Handle notification here...>
}
Does anyone know what can cause the broker's connections to get in this state? Or what tools I could use to go about trying to figure out what is causing this? I currently only have a single web server that is registering to its notifications, so my scenario is not overly complex.
UPDATE:
Ok, so I have determined from this post that the error "Cannot find the remote service ... because it does not exist" is due to SqlDependency not cleaning up after itself properly. The broker is still trying to send notifications to my application after the service has ended. So now, it sounds like I just have to find a way to clear out whatever it is not properly cleaning up when my app starts before calling SqlDependency.Start(), but I have not found a way to do this other than my original method above, which takes the database offline and is not acceptable. Does anyone know know to clean this up?
I have found an acceptable approach to solving this issue. First, I migrated my code away from SqlDependency and I am now using SqlNotificationRequest instead. Doing this prevents Broker Queues and Services from being created/destroyed at unexpected times.
Even with this however, when my application exits there are still a few conversations that don't get marked as closed because the original endpoint that setup the notification is no longer there. Therefore, each time my server re-initializes my code I am clearing out existing conversations.
This adjustment has reduced the number of connections that I have on a daily bases from over 1000 and having to manually kill them, to having a max of about 20 at all times. I highly recommend using SqlNotificationRequest instead of SqlDependency.
I have found a way to clear out the conversations that are stuck. I retrieve all of the generated SqlDependency queues that still exist and iterate over the conversations that don't belong to any of these and end those conversations. Below is the code:
SET NOCOUNT OFF;
DECLARE #handle UniqueIdentifier
DECLARE #count INT = 0
-- Retrieve orphaned conversation handles that belong to auto-generated SqlDependency queues and iterate over each of them
DECLARE handleCursor CURSOR
FOR
SELECT [conversation_handle]
FROM sys.conversation_endpoints WITH(NOLOCK)
WHERE
far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)
DECLARE #Rows INT
SELECT #Rows = COUNT(*) FROM sys.conversation_endpoints WITH(NOLOCK)
WHERE
far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)
WHILE #ROWS>0
BEGIN
OPEN handleCursor
FETCH NEXT FROM handleCursor
INTO #handle
BEGIN TRANSACTION
WHILE ##FETCH_STATUS = 0
BEGIN
-- End the conversation and clean up any remaining references to it
END CONVERSATION #handle WITH CLEANUP
-- Move to the next item
FETCH NEXT FROM handleCursor INTO #handle
SET #count= #count+1
END
COMMIT TRANSACTION
print #count
CLOSE handleCursor;
IF #count > 100000
BEGIN
BREAK;
END
SELECT #Rows = COUNT(*) FROM sys.conversation_endpoints WITH(NOLOCK)
WHERE
far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)
END
DEALLOCATE handleCursor;
Started Outbound means 'SQL Server processed a BEGIN CONVERSATION for this conversation, but no messages have yet been sent.' (from Books Online)
It looks like you are creating conversations that are not then being used, so they never get closed.
Not entirely sure why that would be causing a degradation in performance though.