views:

473

answers:

6

I'd like to send an email for each row of a result set using sp_send_dbmail.

What is the appropriate way to accomplish this without using loops?

Edit: I'm not insisting a loop is not proper here, but is there a set based way to do this. I've tried creating a function, but a function cannot call a stored proc inside it. Only another func or extended sp (which I'd rather not do either).

+2  A: 

This case is exactly what loops are good for (and designed for).

Since you do things that fall out of database scope, it's perfectly legitimate to use loops for them.

Databases are designed to store data and perform the queries against these data which return them in most handy way.

Relational databases can return data in form of rowsets.

Cursors (and loops that use them) are designed to keep a stable rowset so that some things with each of its rows can be done.

By "things" here I mean not pure database tricks, but real things that affect the outer world, the things the database is designed for, be it displaying a table on a webpage, generating a financial report or sending an email.

It's bad to use cursors for pure database tasks (like transforming one rowset to another), but it's perfectly nice to use them for the things like that one you described.

Set based methods are designed to work within a single transaction.

If your set-base query will fail for some reason, you database will revert to the state in was before, but you cannot "rollback" a sent email. You won't be able to keep track of your messages in case of an error.

Quassnoi
I am aware that it is OK to use a loop for this type of thing, but was wondering if there was another way. Hopefully someone finds your extensive text helpful :P
Sam
Set based operations are just not good for this. I tried to do the things like the one you are describing in PostgreSQL about 10 years ago (printing restaurant bills), and that turned out to be a nightmare for the reasons I described and lots of more. Just use loops :)
Quassnoi
A: 

Not the best practice but if you want to avoid loops:

You could create a "SendMails" table, with a trigger on Insert

The sp_send_dbmail is called from inside the trigger

then you do:

Truncate Table SendMails

insert into SendMails (From, To, Subject,text) Select field1,field2,field3,field4 from MyTable
Burnsys
A: 

It must be a row-by-row operation if you need an email per row. It's not a standard set based action.

Either you WHILE through it in SQL or you "for each" in a client language

I would not send emails from triggers BTW: your transaction is open while the trigger executes

gbn
The new email in sql server does not actually wait on the email to be sent. It is placed in a queue and processed by a separate .exe
Sam
I still would not do it in a trigger, nor would I loop in a trigger
gbn
A: 

Set up a data-driven subscription in SQL Server Reporting Services :-D

Sounds like an SSRS requirement to me - TSQL isn't really designed for reporting in and of itself.

Aaron Alton
A: 

The best way to accomplish this is to put your email sending logic in a user defined function.

Then you would simply call SELECT MyEmailFunc(emailaddress) FROM MyTable

It avoids loops and you can even use it in an update statement to show that the email was sent. For example:

UDPATE MyTable SET SENT = MyEmailFunc(emailaddress) WHERE sent = 0

Chris Lively
What if the query will fail in between? You will lose your resultset and don't even know what was your last email that succeeded.
Quassnoi
Only functions and extended stored procedures can be executed from within a function.
Sam
Just use the UPDATE statement. The function should have error catching code and send an appropriate result.
Chris Lively
@Sam: Yes... This code should live in a sproc.
Chris Lively
@Chris: I mean what if your `UPDATE` will fail? Say, the table is locked, or you run out of space.
Quassnoi
@Quassnoi: The update statement will only execute the function in the event it's able to aquire a lock; so that's not an issue. As far as running out of disk space, nothing is going to work then so you have bigger problems.
Chris Lively
This is not making sense. You cannot have a proc inside of a function.
Sam
@Sam: Just for you. You start with a function that accepts an email address as a parameter and performs the actual sending of the mail. You then have a stored procedure which contains EITHER a SELECT statement or an UPDATE statement which passes the email address to the proc. Presto, no loop.
Chris Lively
A: 

Chirs Lively - I tried using this but still get the 'Only functions and extended stored procedures can be executed from within a function' error message.

Sheryl
Yes. I don't think you can send emails from a standard scalar UDF in SQL Server. You can't even generate random numbers in these because of invalid use of a side effecting operator. Maybe a CLR function would work. I'd just use a loop though.
Martin Smith